I've always thought of the Active Directory (AD DS) as a box of Legos: give any two kids an identical set and they will come up with something completely different. Good from a design and flexibility standpoint, but challenging if you implement a design and later the requirements change and/or new features are added to AD DS; a design overhaul could be costly.
Take into consideration the following scenarios:
- Your AD DS design utilizes Organizational Units (OU) for user group management (i.e., all users in the Marketing group go into the Marketing OU, Sales into the Sales OU, etc.). This has made assigning policies via Group Policy Objects (GPO) easy, but now a manager in the Marketing department wants to restrict access to a file share to only those users within the Marketing OU.
- With Windows 2008 (finally) comes File-Grained Password Policies (FGPP). At long last you can now assign a different password policy to groups of users that might not fit into the overall domain password policy. A desire now is to create a different password policy and apply it to the IT department, of which (as in scenario 1) users are all in the IT OU.
In both these instances, the desire is to have OU membership as the gateway to a security right or policy, but in both these instances, you can't fulfill the requirement under the AD DS rules; NTFS security permissions and share permissions can only be assigned to Security Groups (SG), and the same restriction applies to Fine-Grained Password Policies. Microsoft has a concept that has been around for a while but is now picking up momentum as FGPPs are introduced: Shadow Groups. Shadow Groups are SGs that have a membership which shadows an OU's membership, the idea being that you can then continue to maintain the ease of OU-based management while making that management style compatible with the way AD DS works. Although Microsoft has this concept, and makes reference to that concept in many articles and postings, there is no tool provided by them to make Shadow Groups happen.
The concept behind managing a Shadow Group is relatively straightforward: Create a SG, mirror the SG membership to the desired OU, and maintain the SG membership as new objects are added/removed from the reference OU. Easy to think about, not always so easy to implement, especially if the reference OU has a large number of objects, has a membership that changes often, or has many users actively managing the OU. In order to be effective, the SG's membership needs to mirror the OU's membership in as close to real-time as possible, which if done by a human would be a large administrative task.
The best way to approach this is programmatically, either via script or more robustly through a utility. Lets explore the concepts and task outline.
Step One: Discover The OU Membership
As the reference OU contains the master membership list, the first step is to obtain the membership of the OU. The below snippet provides an example of how to do this.
02 Set addsOU = GetObject("LDAP://OU=[OUName],DC=[Domain],DC=[Ext]")
03 For Each addsObj In addsOU
04 If addsObj.class = "user" Then
05 dOU.Add addsObj.distinguishedName,"0"
06 End If
In the above example, line 02 connects to the desired OU and enumeration of the members of that OU is done in lines 03 through 07. This is considered a "one level" enumeration, in that only child objects at the root level are enumerated; membership of any child OUs are skipped, and membership of any discovered SG is skipped. Industry best practice typically call for OUs to consist of only two non-OU object types: user or computer objects and SG objects. This does not mean that your OU will be structured that way, so filtering of objects by type is usually done for a Shadow Group. Line 04 ensures that if the enumerated object isn't a user object, then it is skipped. That value can be changed to "computer" to only allow computer objects, or lines 03 and 06 can be removed altogether to eliminate filtering and add all child objects. If that is done, care should be taken that if the OU also hosts the shadow group object, additional testing needs to be done to skip over the shadow group SG as you don't want to run into the a situation where you are trying to add the group to itself.
Instead of constantly enumerating the OU object, our script is cataloging the master membership list into a dictionary object. This will also provide easier evaluation in future steps.
Step Two: Discover SG Membership
Our SG is to shadow the membership of the reference OU. Although it would be possible to always just wipe the SG's membership and repopulate with the reference OU's membership, this can result in a lot of AD DS replication and is otherwise inefficient. An easier way is to obtain the current SG membership for later comparison against the OU membership. The process is very similar to the enumeration of the OU.
02 Set addsSG = GetObject("LDAP://CN=[Group],OU=[OUName],DC=[Domain],DC=[Ext]")
04 aryMembers = addsSG.GetEx("member")
05 For Each strMember In aryMembers
06 dSG.Add strMember,"0"
In this snippet, again we connect to the shadow group SG in line 02, and obtain the list of members in lines 03 and 04. Each discovered group member is then added to a new dictionary object in line 06, which will make future evaluation simpler.
Step Three: Create The Delta Change List
At this point we have both a workable list of OU members, and the current list of shadow group SG members. We next need to create a delta change list, which reduces the amount of AD DS replication as we can pinpoint only those objects that need adding or removing.
02 ‘1) If OU member not in SG, add OU member
03 For Each strKey in dOU.Keys
04 If Not dSG.Exists(strKey) then
05 dDelta.Add strKey,"ADD"
06 End If
08 ‘2) If SG member not in OU, remove SG member
09 For Each strKey in dSG.Keys
10 If Not dOU.Exists(strKey) then
11 dDelta.Add strKey,"REMOVE"
12 End If
In short, if the member of the OU is not found in the shadow group, add it. If the member of the shadow group SG is not found in the OU, remove it. These discoveries are recorded in our delta dictionary object on line 05 and 11. At the end of this process, our delta change list is ready to go.
Step Four: Implement The Delta Changes
02 If dDelta(strKey) = "ADD" then
03 addsSG.Add "LDAP://" & strkey
05 addsSG.Remove "LDAP://" & strkey
06 End If
The delta change list enumeration should be quick, performing an add or remove as previously discovered.
Step Five: Schedule The Sync
The above process can obviously be run on-demand, but that still requires a level of administrative effort that could be better served elsewhere. Automating the task will allow the shadow group to be updated in the background regardless of who makes the change or when.
There are a couple of ways in which you might want to accomplish this. The first method is to trigger the script to run when a change has been made to the reference OU. Pre Windows 2008, you could use a tool called EventTriggers to create a process that triggers a process when a specific event is logged to the event log. Windows 2008 and beyond has this built into the Event Log itself, with the same basic flow of event log occurrence triggering a defined process. With this approach, the obstacle to overcome is scope. Although changes to OUs can be logged to an Event Log, if you have more than one domain controller, you would have to monitor more than one event log. This can require extensive monitoring unless you setup a Source Initiated Subscription, which is beyond the scope of this article, but in short will allow a single computer to monitor all events on a group of DCs and thus limit the number of monitoring tasks. For either of these setups, the legwork to set it up may be overkill.
The second method is simply using the Task Scheduler to schedule a reoccurring task. With this method, a single computer blindly triggers the script every x minutes. The simplicity of this method is easy to see, the downside is that it does steal some cycles on the host computer constantly regardless of the need. Of course, you can always reduce the frequency to meet the true needs of the parent OU.
The above has been a very simplified approach to managing a single shadow group. But in production there are additional considerations you should think about and implement as needed.
- Instances. The example deals with a single shadow group, and a single triggering mechanism. If your environment requires multiple shadow groups, the single instance script will soon become inefficient as you will wind up with multiple copies of a script with very little difference between them, and each copy requiring its own triggering mechanism. A needed change will then require editing multiple files, and you run the risk of missing a script or introducing an error on one that is difficult to track back. If your environment requires more than one shadow group, consider making the process more dynamic. Include a mapping file (csv/txt/xml) that includes the source OUs and shadow groups, and have your script read the mapping information and perform the sync for each mapping within your file. This will reduce the overall administrative setup needed.
- One Level vs SubTree. The example deals with a single OU mapped to a single shadow group. Although this may work well for most situations, if your environment has child OUs under a primary OU and you need to include the members of the child OUs as well as the members of the parent OU, this script example won't work as the group membership would only contain the found members of the last OU processed. In this event, your process will need to combine the membership of all desired child OUs into the primary OU membership list or will need to create a new security group and nest the shadow groups within the new parental security group, using the parental security group for permission assignments.
- Object Renaming. This is my biggest pet peeve with LDAP in general: AD DS always presents you with the objects current name and location, but if you programmatically access the object by it's LDAP path, any change in location or name of that object within AD DS will break your process. This is because AD DS considers the LDAP path (distinguished name) nothing other than an attribute of the object. AD DS tracks each object by it's GUID, and as that is an identifier and everything else is an attribute, it never looses the object when you rename or move it. Because of this external weakness, I recommend structuring your process to not use the LDAP path for the OU and SG, but rather use those objects GUIDs. This way, any rename or move of either object will not break your script. Programmatic access to the object via it's GUID is easier than via it's distinguished name. In comparison to Step One's script example line 02, the access syntax for a GUID is: "LDAP://<GUID=[GUID]>"
- Change Log. This example simply mirrors membership; adds and removes are not tracked. Many environments require stricter change management or at least change tracking than the presented example provides. Although possible to implement a Directory Services Access and Account Management auditing via GPO, it then requires additional administrative discovery (unless you have implemented centralized event monitoring) to find these changes. As you can see in the script example, you already have a list of changes, so writing those changes out to a central log file is an easy add that will self-document the changes for future analysis.
There are two downloads associated with this article. One download is a full functional example of the VBScript outlined above, and the other is a full graphical utility which simplifies the group creation and tracking, reporting, and syncing. Both are available from Dx21.com