How to handle built-in domain groups with ADMT

If you ever performed a migration with the Active Directory Migration Tool (ADMT) it probably has come to your attention that you can’t migrate built-in domain groups, the reason for this is simple – those are groups has well-known SIDs ore more specifically well-known RIDs – meaning that their RIDs are the same in any domain.

The ADMT Migration Log will state the following:

Table 1: Migration Log

ADMT Migration Log – Sample

Active Directory Migration Tool – scripted group migration started.
WRN1:0135 The object ‘Users’ is a built-in or well-known account. The object will not be migrated.
WRN1:0135 The object ‘Domain Guests’ is a built-in or well-known account. The object will not be migrated.
WRN1:0135 The object ‘Group Policy Creator Owners’ is a built-in or well-known account. The object will not be migrated.
WRN1:0135 The object ‘Pre-Windows 2000 Compatible Access’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Performance Log Users’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Windows Authorization Access Group’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Remote Desktop Users’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Enterprise Admins’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Schema Admins’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Guests’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Domain Users’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Terminal Server License Servers’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Domain Admins’ is a built-in or well-known account. The object will not be migrated.

WRN1:0135 The object ‘Administrators’ is a built-in or well-known account. The object will not be migrated

 

The problem
This causes an issue if you want to preserve access to resources in the source domain, once a user account has been migrated to the target domain, this is usually accomplished by using sIDHistory – (Migrated users and groups inherit their source SID in the sIDHistory attribute – You can read more here: Using SID History to Preserve Resource Access)

If you have ACLs (Access Control Lists) that contain ACEs (Access Control Entries) either granting or denying rights on a File Share to for example the “Domain Users” group (a group with a well-known SID) in “source.dom” and a user “John” who is member of “Domain Users” are migrated from “source.dom” to “target.dom” with sIDHistory (since built-in groups aren’t migrated – John would know lose the presence of the “Domain Users” group in “source.dom” in his token and can therefore no longer access resources in “source.dom” that was granted to him by the “Domain Users” group, the example below.

The solution using Security Translation
The Active Directory Migration Tool (ADMT) has a feature called Security Translation – briefly described; Security Translation can translate security references on objects in Windows NT:

  • Files and Folders (NTFS)
  • Shares (Share Permissions)
  • Printers
  • Registry
  • Local Groups
  • User Profiles
  • User Rights

Usually security translation is taking place once a resource has been migrated from the source domain to the target domain, running the security translation will effectively replace SIDs (sIDHistory) for Security Principals from the source domain that has been migrated to SIDs in the target domain, this is referred to as the “Replace” operation.

We are going to use the “Add” operation in order to solve the issue with built-in groups – as we can’t migrate built-in groups.

We’re going to create a “fictive” “Domain Users” group in the target domain (Making source that all users being migrated from the source domain are being members of this group once migrated) and we’re going to use the Security Translation to grant this “fictive” “Domain Users” the exactly same permissions as the “Domain Users” group in the source domain has using the security Translation Wizard.

Step-by-Step

  1. Let’s create group in “target.dom” named: “Source_Domain Users” – obtain the SID of the newly created group, as well obtin the SID of the “Domain Users” group in “source.dom”.Create a SID Mapping file that we’re going to use in ADMT and the Security Translation Wizard.
    The format of this file is <source sid>,<target sid> in this case that is <real domain users group in source>, <fictive domain users group in target>

    Table 2: SIDMappingFile

SIDMappingFile Sample

S-1-5-21-1429931347-2825349181-4189204843-513,S-1-5-21-3530694592-736403133-1610603870-21311

Save the SIDMapping file as “SOURCE_TARGET_Domain Users.txt”

Star the Security Translation Wizard in the Active Directory Migration Tool (ADMT):

  1. Select the “Other objects specified in a file” option and type the path to the mapping file we created in Step 1.
  2. Select the appropriate target and source domains.
  3. You should now select all active member servers within the source domain (because you probably don’t know exactly which servers that contain ACEs (Access Control Entries) with the Domain Users group.Note: I recommend gathering a list of active member servers in the source domain and processing them in batches of 20-30.
  4. Select where you want to translate security.
    Note:I recommend the selected only, as it’s very unlikely that the others contain ACLs/ACEs with Domain Users. (You more you select – the longer time it takes to process each server)
  5. Select the “Add” operation, as we want to grant our “fictive” “SOURCE_Domain Users” group the same permissions as the real “Domain Users” group without replacing/removing it – doing so will instead result in lost access for users that hasn’t been migrated yet.Note:Verify twice that you really have selected the “Add” operation.
  6. You’re now ready to start the operation.Note:You might end-up with security issues if the account you’re running this operation with isn’t member of the local administrators group on all servers you intend to translate security on (e.g all member servers in the source domain – you can accomplish that with GPOs or scripting)

Let’s see now how it works

Once security translation has been performed the “SOURCE_Domain Users” group should now have been granted the same rights as the real “Domain Users” group in the source domain “source.dom” it should look like:
Let’s now migrate “John” from “source.dom” to “target.dom” and make him a member of “SOURCE_Domain Users”

Summary

I’m working on a domain migration and consolidation project where we discovered that it was very common in the source domains that ACLs (Access Control Lists) contained ACEs (Access Control Entries) with the built-in “Domain Users” group – resulting in lost permissions for migrated uses (We applied this workaround on 80~ domains being all migrated to a single domain/forest) – Running Security Translation in domains with 1000+ member servers could take an entire week, I’ve recommend to perform security translations on lager file servers off business hours cause the ADMT agent can consume notable amount of CPU.

If someone is interested we had to write a script to do basically the same on NetApp Filers.

How the Active Directory – Data Store Really Works (Inside NTDS.dit) – Code [1-3]

So what is the “Code [1-3]” all about and where is Part 4 of the series that you might expect?
Before I go ahead with Part 4 I thought it would be a good idea to sum up Part 1 to Part 3 with code (So that you know how we figured out all this stuff while we was coding on ESEDump) – Note: This most may be targeted for the developer audience more than the general Active Directory administrator.

Disclaimer: The code samples provided here is code snippets that doesn’t represent any code actual code from the DSA and or any other Microsoft products and technologies, nor those they represent the complete source of ESEDump


ESEHelper – A managed ESE wrapper around the ESE APIs

We decided that we wanted to work with ESE in C# (and when we first started this project EseManaged from codeplex wasn’t around) and even if we could have used it later on – I guess we wanted full control and decided to stick with our own wrapper. So when you see references to “EseHelper” in the code snippets below – you know it’s just a wrapper around: Extensible Storage Engine Native APIs – there is no secrets around this J

JET_RETRIVECOLUMN structure custom methods

We extended the JET_RETRIVECOLUMN structure with some additional methods to retrieve data.

Table 0: JET_RETRIVECOLUMN structure

Code Snippet

// The custom methods in JET_RETRIEVECOLUMN allow us to quickly// interpret each column’s data depending on its data type (string, integer, etc.)

internal
struct
JET_RETRIEVECOLUMN

{

public
int columnid;

public IntPtr pvData; // Pointer to the data block in memory

public
int cbData; // Size of the allocated data block

public
int cbActual; // Size of the actual/used data

public
int grbit;

// Offset to the first byte to be retrieved from a column of type

// JET_coltypLongBinary or JET_coltypLongText

public
int ibLongValue;

// Number of values in a multi-valued column

// Can be used to retrive a specific value

public
int itagSequence;

// The columnid of the tagged, multi-valued, or sparse column

// when all tagged columns are retrieved by passing 0

// as the columnid to JetRetrieveColumn.”

public
int columnidNextTagged;

public
int err;

public
void Initialize(ColumnInfo att)

{

this.Initialize(att.ID, 0);

}

public
void Initialize(ColumnInfo att, int cbData)

{

// Initialize with a data block of cbData size

this.Initialize(att.ID, cbData);

}

public
void Initialize(int columnid)

{

// Initialize with an empty data block

this.Initialize(columnid, 0);

}

public
void Initialize(int columnid, int cbData)

{

// Reset the fields

this.cbActual = 0;

this.cbData = 0;

this.err = 0;

// Make sure to free any previously used memory in this instance

if (this.pvData != IntPtr.Zero)

{

Marshal.FreeHGlobal(this.pvData);

this.pvData = IntPtr.Zero;

}

this.columnid = columnid;

this.itagSequence = 1;

// Allocate a new memory block if necessary (if > 0 bytes requested)

this.cbData = cbData;

if (this.cbData > 0)

this.pvData = Marshal.AllocHGlobal(this.cbData);

}

// Copies the current memory block into a byte array

public
byte[] GetData()

{

byte[] output = new
byte[this.cbActual];

Marshal.Copy(this.pvData, output, 0, output.Length);

return output;

}

// Interpret the inner data as a GUID

public Guid GetGuid()

{

IntPtr pGuid = this.pvData;

byte[] bGuid = new
byte[16];

Marshal.Copy(pGuid, bGuid, 0, bGuid.Length);

return
new Guid(bGuid);

}

// Interpret the inner data as a string (automatically checks ASCII or Unicode encoding)

public
string GetString()

{

byte[] data = this.GetData();

if (IsUnicode(data))

return Encoding.Unicode.GetString(data, 0, data.Length);

else

return Encoding.ASCII.GetString(data, 0, data.Length);

}

// Interpret the inner data as a 32-bit integer

public
int GetInteger()

{

return BitConverter.ToInt32(this.GetData(), 0);

}

// Interpret the inner data as a 64-bit integer

public
long GetLong()

{

return BitConverter.ToInt64(this.GetData(), 0);

}

// Interpret the inner data as a boolean (true/false)

public
bool GetBool()

{

return Marshal.ReadByte(this.pvData) == 1 ? true : false;

}

// Determines if a string in a data block is of Unicode or ASCII encoding

// TODO: International 2-byte characters unsupported?

private
bool IsUnicode(byte[] data)

{

bool isUnicode = false;

// Unicode strings’ data always have an even number of bytes

if (data.Length % 2 == 0)

for (int i = 0; i < data.Length; i += 2)

if (data[i + 1] == ‘\0’)

isUnicode |= true;

return isUnicode;

}

}

// ColumnInfo stores column metadata (ID, type, table owner)

// Used when retrieving JET columns

internal
struct
ColumnInfo

{

public
int ID;

public
string Name;

public
int DataType;

public
string AltName; //added for attribute name

public
int AltId; // added for attribute id

public IntPtr TableId; // added for table support in caching

public ColumnInfo(int id, string name, int type, string altname, int altid, IntPtr tableid)

{

this.ID = id;

this.Name = name;

this.DataType = type;

this.AltName = altname; //added for attribute name

this.AltId = altid;

this.TableId = tableid; //added for table support in caching

}

}

 

Perform Initialization and Attach to NTDS.dit

First thing we had to figure out was how we attached to the database (NTDS.dit) using JetInit, JetBeginSession, JetAttachDatabase and finally calling JetOpenDatabase in addition to those callas we had to set several parameters with JetSetSystemParameter for our usage, e.g there is things that need to be turned off as we attach/open the DB as read-only due to the nature of our application.

Table 1: ESE Initialization

Code Snippet

// E.Check makes sure a JET API call is successful, i.e. JET_errSuccess (0)// If the call fails, we throw an exception/write to the Console

// Initialize ESENT. Setting JetInit will inspect the logfiles to see if the last

// shutdown was clean. If it wasn’t (e.g. the application crashed) recovery will be

// run automatically bringing the database to a consistent state.

err = E.Check(EseHelper.JetSetSystemParameter(ref instance, EseHelper.JET_sesidNil, new
IntPtr(EseHelper.JET_paramDatabasePageSize), new
IntPtr(0x2000), null));

err = E.Check(EseHelper.JetCreateInstance(out instance, “instance”));

// Set up the recovery option (off), the maximum number temporary tables (7) and temporary path

err = E.Check(EseHelper.JetSetSystemParameter(ref instance, EseHelper.JET_sesidNil, new
IntPtr(EseHelper.JET_paramRecovery), IntPtr.Zero, “off”));

err = E.Check(EseHelper.JetSetSystemParameter(ref instance, EseHelper.JET_sesidNil, new
IntPtr(EseHelper.JET_paramEnableOnlineDefrag), IntPtr.Zero, null));

err = E.Check(EseHelper.JetSetSystemParameter(ref instance, EseHelper.JET_sesidNil, new
IntPtr(0xa), IntPtr.Zero, null));

err = E.Check(EseHelper.JetSetSystemParameter(ref instance, EseHelper.JET_sesidNil, new
IntPtr(EseHelper.JET_paramMaxTemporaryTables), new
IntPtr(7), null));

err = E.Check(EseHelper.JetSetSystemParameter(ref instance, EseHelper.JET_sesidNil, new
IntPtr(EseHelper.JET_paramTempPath), IntPtr.Zero, System.IO.Path.GetTempPath()));

// Initialize ESE and begin a session

err = E.Check(EseHelper.JetInit(ref instance));

err = E.Check(EseHelper.JetBeginSession(instance, out sesid, null, null));

// Attach a database

err = E.Check(EseHelper.JetAttachDatabase(sesid, “NTDS.dit”, 1));

err = E.Check(EseHelper.JetOpenDatabase(sesid, “NTDS.dit”, null, out dbid, 1));

 

List the tables inside NTDS.dit

We figured out that by statically opening the “MSysObjects” and positioning over the “RootObjects” index we could enumerate the tables inside the database using the following code snippet.

Table 2: ESE Enumerate tables inside the database

Code Snippet

// Simplified:// Method to obtain a list of tables for a given JET database

private
static
void GetTableNames(IntPtr instance, IntPtr sesid, IntPtr dbid, ref EseErrors err)

{

IntPtr tableid = IntPtr.Zero;

List<string> tables = new List<string>();

// E.Check makes sure a JET API call is successful, i.e. JET_errSuccess (0)

// If the call fails, we throw an exception/write to the Console

err = E.Check(EseHelper.JetOpenTable(sesid, dbid, “MSysObjects”, IntPtr.Zero, 0, 0, out tableid));

// Select the first row in the RootObjects record set

err = E.Check(EseHelper.JetSetCurrentIndex(sesid, tableid, “RootObjects”));

err = E.Check(EseHelper.JetMove(sesid, tableid, EseHelper.JET_MoveFirst, 0));

// Allocate a column array of one element — we only need the name column

EseHelper.JET_RETRIEVECOLUMN[] array = new EseHelper.JET_RETRIEVECOLUMN[1];

// Loop until we reach the end of the record set or an error occurs

while (err == 0)

{

// Allocate 0x41 bytes for this column’s value

// 0x80 is the column ID (table name)

array[0].Initialize(0x80, 0x41);

err = E.Check(EseHelper.JetRetrieveColumns(sesid, tableid, Marshal.UnsafeAddrOfPinnedArrayElement(array, 0), array.Length));

foreach (EseHelper.JET_RETRIEVECOLUMN column in array)

{

if (column.cbData != 0x04)

{

string table = column.GetString();

// Print out the table

Console.WriteLine(table);

}

}

// Select the next record (table info row)

err = E.Check(EseHelper.JetMove(sesid, tableid, EseHelper.JET_MoveNext, 0));

}

// Clean up

err = E.Check(EseHelper.JetCloseTable(sesid, tableid));

Console.WriteLine();

}

 

Retrieving the Ancestors_col

In Part 3, we’re discussing the usage of the “Ancestors_col” column and how it’s used to walk subtrees in the database, the DNTs are stored as bytes within the “Acenstors_col” and are read as in the code snippet below.

Table 3: Ancestors_col

Code Snippet

if (column.err != EseErrors.ColumnNull)
{


string ancestory = null;


char[] ancestor_separator = { ‘,’ };

 


// Walk through every ancestry record in the returned column data


// Construct the ancestry string with the returned DNTs


for (int i = 0; i < column.GetData().Length; i += sizeof(int))

{


int dnt = BitConverter.ToInt32(column.GetData(), i);

ancestory = ancestory + dnt + “,”;

}

 


// Remove any trailing “,” characters

ancestory = ancestory.TrimEnd(ancestor_separator);

output = ancestory;

}

 

Reading an object’s full distinguished name

Note: This is our way to read an objects full distinguished name, given an object’s DNT (Distinguished Name Tag) – But this is not considered safe by the DSA as mentioned in Part 3 as the “Ancestors_col” is being processed by a background task and might not be in-sync all the times. (Safer would be to walk the tree up – by each PDNT until PDNT == 2)

Table 4: Get an objects distinguished name by its DNT

Code Snippet

internal
static
string DBGetDN(IntPtr sesid, IntPtr tableid, int tag, ref
EseErrors err)
{

// 0 (zero) means where already positioned at the obejct.

if (tag != 0)

DBFindDNT(sesid, tableid, tag, ref err);

 

List<string> DN = new
List<string>();

 

EseHelper.JET_RETRIEVECOLUMN[] Ancestors = new
EseHelper.JET_RETRIEVECOLUMN[1];

 

Ancestors[0].Initialize(attid.GetByDisplayName(“Ancestors_col”, tableid), 256 * 6);

 

err = E.Check(EseHelper.JetRetrieveColumns(sesid, tableid, Marshal.UnsafeAddrOfPinnedArrayElement(Ancestors, 0), Ancestors.Length));

 

// Ensure the “Ancestors_col” exist and contains data.

if (Ancestors[0].err != EseErrors.ColumnNull)

{


// Loop thru all ancestors.


for (int i = 0; i < Ancestors[0].GetData().Length; i += sizeof(int))

{


int dnt = BitConverter.ToInt32(Ancestors[0].GetData(), i);


if (dnt != 2) // “2” == $ROOT_OBJECT$ == We hit the top most DN Component.

{


// Move the cursor over the ancestor

DBFindDNT(sesid, tableid, dnt, ref err);

 


// Define a list of attributes we want to read off each ancestor (object)


EseHelper.JET_RETRIEVECOLUMN[] attrList = new
EseHelper.JET_RETRIEVECOLUMN[2];

 

attrList[0].Initialize(attid.GetByDisplayName(“RDNtyp_col”, tableid), 256 * 24);

attrList[1].Initialize(attid.GetByDisplayName(“name”, tableid), 256 * 24);

 

err = E.Check(EseHelper.JetRetrieveColumns(sesid, tableid, Marshal.UnsafeAddrOfPinnedArrayElement(attrList, 0), attrList.Length));

 


// Make sure the object has both a RDNType and a Name


if (attrList[0].err != EseErrors.ColumnNull && attrList[1].err != EseErrors.ColumnNull)

{


string RDNType = DBGetRDNType(tableid, attrList[0].GetInteger());


string Name = attrList[1].GetString();


// Construct this ancestors RDN.

DN.Add(RDNType + “=” + Name);

}

}

}

DN.Reverse();


return
string.Join(“,”, DN.ToArray());

}

return
null;

}

 

Table 5: Get an objects distinguished name (object is referenced by sAMAccountName: ADCH)

ESEDump

 

How the Active Directory – Data Store Really Works (Inside NTDS.dit) – Part 3

This is the third post in a series of articles that will describe what’s really inside NTDS.dit and how Active Directory works on the database layer, the past two articles has been about:

Support for onelevel searches

Given the knowledge from the last article in the series on how objects are referred to each other in terms of a parent – child relation (DNT and PDNT) it becomes obvious that it is easy to get all direct-descendent/child objects of a given object by searching for all object’s (rows) that have a specific PDNT (where objects.PDNT == parent.DNT) this can efficiently be archived by using the “PDNT_index”

Table 1: PDNT_Index

PDNT_Index

    PDNT_index

 

        Grbit: IndexUnique, IndexIgnoreNull

        CultureInfo: en-US

        CompareOptions: IgnoreCase, IgnoreNonSpace, IgnoreKanaType, IgnoreWidth

            PDNT_col

                Coltyp: Long

                IsAscending: True

                IsASCII: False

            Name [1.1]

                Coltyp: LongText

                IsAscending: True

                IsASCII: False

[1.1]: “Name” represent the RDN attribute, it’s not stored/named as described in the illustration above, it’s rather stored as “ATTm589825” I choose to represent it as “Name” for simplifying the understanding

Introducing the Ancestors_col and support for subtree searches

Support for subtree searching requires the implementation of another column at the DBLayer, the “Ancestors_col”; the other columns are we already familiar with as of the last post in the series

Table 2: datatable – Simplified for hierarchy representation 2

Name

ESE Data Type

ESE Column Options (grbit)

Description

DNT_col JET_coltypLong JET_bitColumnFixed,
JET_bitColumnAutoincrement
Every object/phantom within the “datatable” contains a unique DNT value.

 

ESE enforces uniqueness by declaring DNT to be an ESE auto-incrementing column (JET_bitColumnAutoincrement.)

DNT is the primary key of the “datatable”, so objects are clustered in storage by DNT, and access to an object by DNT is more efficient than access via any other column/attribute. Since new objects are created in ascending DNT order, the primary key organization does not slow down the creation of new objects.

PDNT_col JET_coltypLong JET_bitColumnFixed The PDNT column holds the DNT of the parent of an object.

 

The tree structure of objects is not represented by pointers from parent to child, as you might expect given how the tree is normally browsed, but by a pointer in each child object/row to its parent

RDNTyp_col JET_coltypLong JET_bitColumnFixed The RDNTyp_col holds the attributeID to the attribute being used as RDN, typically: cn (Common-Name), ou (Organizational-Unit), dc (Domain-Component), o (Organization)
Ancestors_col JET_coltypBinary JET_bitColumnTagged The Ancestors_col holds the DN path [2.1] (every DNT from the root in the hierarchy to the objects DNT) in a binary blob. This always efficient subtree searches by searching the Ancestors_col with a prefix of DNTs to the root:ed object [2.2]

 

 

[2.1]: The first object/row within the “datatable” always has a NULL ancestory_col value.
[2.2] See “Table 4: Ancestors_index”

Let’s apply “Table 2” to a theoretical sample:

[3.1]: “Name” represent the RDN attribute, it’s not stored/named as described in the illustration above, it’s rather stored as “ATTm589825” I choose to represent it as “Name” for simplifying the understanding

If we wanted to do an subtree search with the “Windows Development” organizational unit as the search base – we would like to use an index over the “ancestors_col” [4.1] and set the prefix to be “2,1787,1788,1789,1790,5520,5521*” that would return a list of all objects (rows) that are subordinated the “Windows Development” organizational unit (e.g. all children).

Table 4: Ancestors_index

PDNT_Index

    Ancestors_index

 

        Grbit: IndexIgnoreNull

        CultureInfo: en-US

        CompareOptions: IgnoreCase, IgnoreNonSpace, IgnoreKanaType, IgnoreWidth

            Ancestors_col

                Coltyp: LongBinary

                IsAscending: True

                IsASCII: False

The ancestors_col and the SDProp (Security Descriptor Propagation Demon) – How are they related?

The “ancestors_col” complicates an object move (Within the same NC/Database, We leave cross-NC/database moves outside this article for simplify understanding)

Given the knowledge from the last article in the series on how objects are referred to each other in terms of a parent – child relation (DNT and PDNT) – It seems easy to implement an object move by simply change the PDNT to the DNT of the new designated parent (e.g. give “Christoffer Andersson” a value of “5521” in his “PDNT_col” and the object is now subordinated the organizational unit “Windows Development” instead of “Users” in the above sample)

After the operation above, the “Ancestors_col” wouldn’t be accurate, so the Ancestors_col needs “fixup”, (e.g. it needs to be adjusted to the new path “2,1787,1788,1789,1790,5520,5521,,5524” – “5522” has to be removed as we moved the object “Christoffer Andersson” from the “Users” organizational unit to the “Windows Development” organizational unit) This might not seem to be an issue at the first glance, but imagine moving a large subtree – the operation wouldn’t fit into an single atomic transaction, therefore ancestry fixup is performed in the background by the SDProp (Security Descriptor Propagation Demon) [4.1]

The most experienced Active Directory administrators at least have heard of “SDProp” that is a short for the Security Propagation Demon and some know that it’s responsible for handling propagation of ACE inheritance, very few probably know that it is in addition also responsible to maintain the acenstors_col at the DBLayer.

Each DSA/DC runs the SDProp (Security Descriptor Propagation Demon) as a background task (TQ_TASK). By default, this task is triggered by the following conditions:

  • Any modification (originating or replicated) of the nTSecurityDescriptor attribute of any object (Except for those modifications done by the SDProp demon)
    • This requires that any new/modified inheritable ACEs are propagated to all descendant objects and that any removed inheritable ACEs are removed from all descendent objects, ACE inheritance is not replicated and is being applied by the SDProp using this process on all DSAs/DCs.
  • Any modification of the “PDNT_col” (object is being moved in the directory) of an object that results in the object having a different parent (Except for those cases where the new parent is a Deleted Objects container)
    • This requires adjustments of the values (DNTs) stored in the “ancestors_col” as the object/row has been moved.
    • This requires that all inheritable ACEs present on the new parent object (and all its ancestors from the top) are being propagated down to the object, and that inheritable ACEs originating from the previous parent (and all its ancestors from the top) has to be removed.


      Table 5: Security Descriptor Propagation Demon – Responsibilities Summary

Name

Description

ACE Inheritance Responsible for propagation of inheritable ACEs, if inheritable ACEs are being added or removed from an object’s SD (Security Descriptor) – SDProp is responsible for propagating those ACEs to all descendant / child objects of the object where the ACEs where added. [4.2]
Ancestors_col fixup If any modification to an object’s parent “PDNT_col” occur (Simply an object move) – SDProp is responsible to adjust the “ancestors_col” to match the full path of DNTs from the top down to the object with respect to its new parent.


[4.1] Since this work is performed in the background and SDProp (that runs on single thread doesn’t implement a limit of how many transactions that is used to perform an operation) there may be a period of time when Active Directory returns inconsistent query results with the tree structure, and inheritable ACEs won’t be accurate.

[4.2]: A common misunderstanding is that the SDProp is responsible for maintaining protection for object’s that is protected by the AdminSDHolder, That’s incorrect. The AdminSDHolder runs in its own background task (TQ_TASK) every 60 minutes on the PDC (if a protected object’s security descriptor mismatches from the once at the adminSDHolder object, inheritance is turned off and the security descriptor is over written with the one at the adminSDHolder object) that makes the AdminSDHolder background task to trigger off SDProp .

You can read more about the SDProp – Security Descriptor Propagation Demon here:
http://msdn.microsoft.com/en-us/library/dd350247(v=prot.13).aspx

Trigger the SDProp from manually from the outside.

It’s possible to use an operational attribute to trigger the SDProp demon to run from the “outside” (e.g be performed by an administrator) – How ever on a fully functional DSA/DC there is no need to invoke the SDProp manually, the SDProp can be trigged globally or for a specific object /row identified by its DNT, The requester must have the “Recalculate-Security-Inheritance” control access right on the nTDSDSA object for the DSA/DC

The following shows an LDIF sample that performs this operation on the entire DIT.

dn:
changetype: modify
add: fixupInheritance
fixupInheritance: 1

The following shows an LDIF sample that performs this operation on a specific object /row

dn:
changetype: modify
add: fixupInheritance
fixupInheritance: dnt:5524

You can read more about the “fixupInheritance” operational attribute here: (it’s actually well documented now days)
http://msdn.microsoft.com/en-us/library/cc223299(v=prot.13).aspx

I think that’s all regarding the “ansectors_col” – I might have missed something.

How the Active Directory – Data Store Really Works (Inside NTDS.dit) – Part 2

This is the second post in a series of articles that will describe what’s really inside NTDS.dit and how Active Directory works on the database layer, In an earlier post I explained the tables within NTDS.dit in detail as far as what they are used for, in which release of Active Directory (Windows Server) they were introduced in, as well any major changes being added in later versions: How the Active Directory – Data Store Really Works (Inside NTDS.dit) – Part 1

This post will go into the details of the contents of the “datatable” also known as the object store – that contains all objects and phantoms [1.1] represented as rows (1 object/phantom = 1 row in the table) from any instanced naming context (NC) held as either writable or read-only (until they are physically removed by the garbage-collector) by the Directory System Agent (DSA) hosting the database and where columns represent every [1:3] attribute present in the schema except linked attributes [1:2]

[1.1]: phantoms are references to object’s hosted outside the given database (NTDS.DIT) and the given Directory System Agent (DSA) – (Except structural phantoms)

[1:2] Post-Windows Server 2003 the attribute “ntSecurityDescriptor” is stored in the “sd_table” rather than in the “datatable”

[1:3] Some columns doesn’t reflect attributes and are columns pre-defined in the NTDS.dit template database generated by Microsoft (those are needed for internal states to the DSA)

Maintain the hierarchy of an object tree within a flat object store

The hierarchy in Active Directory is quite obvious to most of us at a simplified layer e.g. daily administrative task such as creating an Organizational Unit and creates several descendent/child objects under neat it, some people may refer to some objects as leaf objects (object that usually don’t contain descendent/child objects) such as object of the class “user” – However the fact is that any object within Active Directory has the possibility (technically) to contain one or more descendent/child objects – this is controlled by schema constrains and more specifically the sum of the following attributes of a given object class and any inherited class (except for auxiliary classes) :

Table 1: Possible Superiors

Attribute

Description

possSuperiors Contains references to object classes that can host the given as a descendent/child object.

 

possSuperios can be modified on both cat1 and cat2 schema class object’s after that they have been instantiated in the schema.

systemPossSuperiors Contains references to object classes that can host the given as a descendent/child object.

 

systemPossSuperiors can’t be modified from the outside, once being instanced after the initial creation of the class within the schema.

Why it’s easy for all objects to host descendants/child objects becomes more obvious when the hierarchy is explained at the DBLayer.

The question remains with the details given above, if one row within the “datatable” represents an object/phantom, how can the hierarchy be maintained? The below table “Table 2” represent columns in the “datatable” that are vital for representing/building the hierarchy in the directory at the DBLayer.

Table 2: datatable – Simplified for hierarchy representation 1

Name

ESE Data Type

ESE Column Options (grbit)

Description

DNT_col JET_coltypLong JET_bitColumnFixed,
JET_bitColumnAutoincrement
Every object/phantom within the “datatable” contains a unique DNT value.

 

DNT is a short for distinguished name tag.

ESE enforces uniqueness by declaring DNT to be an ESE auto-incrementing column (JET_bitColumnAutoincrement.)

DNT is the primary key of the “datatable”, so [2.1] objects are clustered in storage by DNT, and access to an object by DNT is more efficient than access via any other column/attribute. Since new objects are created in ascending DNT order, the primary key organization does not slow down the creation of new objects.

PDNT_col JET_coltypLong JET_bitColumnFixed The PDNT column holds the DNT of the parent of an object [2.2].

 

PDNT is a short for parent distinguished name tag.

The tree structure of objects is not represented by pointers from parent to child, as you might expect given how the tree is normally browsed, but by a pointer in each child object/row to its parent

RDNTyp_col JET_coltypLong JET_bitColumnFixed The RDNTyp_col holds the attributeID to the attribute being used as RDN, typically: cn (Common-Name), ou (Organizational-Unit), dc (Domain-Component), o (Organization) [2:3]

[2.1]: The maximum numbers of objects/phantoms that ever can be created on a given DSA (Domain Controller) for its entire life time is 2 billion objects (147,483,393 (231 minus 255)). Note that this count against objects/phantoms ever introduced to the local DSA as part of any naming context (NC) writable or partial ever hosted by the DSA. * If the DSA is promoted by using IFM (Install from Media it inheritance the count of already allocated DNTs from the former DSA) – When the maximum numbers of auto-increment values has been used (the limit mention above have been hit) the following error are returned at the DBLayer: JET_errOutOfAutoincrementValues -1076 from the outside we will notice: “Error: Add: Operations Error. <1> Server error: 000020EF: SvcErr: DSID-0208044C, problem 5012 (DIR_ERROR), data -1076.” Read more about Active Directory Limits: http://technet.microsoft.com/en-us/library/active-directory-maximum-limits-scalability(v=ws.10).aspx#BKMK_Objects

[2.2]: The first row introduced in the “datatable” isn’t a real object nor is it a phantom and is named “$NOT_AN_OBJECT1$” and have its PDNT_col set to NULL.
The “PDNT_col” is indexed so becomes very easy to drive an object’s direct-descendants/child objects (not all descendants) by simply query who that has the object’s DNT in their PDNT_col.

[2:3] The DSA has an in-memory cache for the most common RDNs (the ones mentioned above)
Active Directory allows us to use a custom attribute as RDN as well by specify the attributeID of that custom attribute as the rDNAttID for the particular class. * A RDN attribute must have the syntax string **customization may not be supported by all LDAPv3 clients. *** rDNAttID should preferably be set before any objects of the given class is instanced in the directory (changes won’t apply to already instanced/existing objects)

Let’s apply “Table 2” to a theoretical sample:

[3.1]: “Name” represent the RDN attribute, it’s not stored/named as described in the illustration above, it’s rather stored as “ATTm589825” I choose to represent it as “Name” for simplifying the understanding of the hierarchy in this case.

[3.2] Structural Phantom – (Different from a phantom used for reference integrity to real object’s hosted outside the given DIT) is used to represent the full distinguished name of the domain e.g “DC=ntdev,DC=corp,DC=chrisse,DC=com”

In the next article – we will continue the deep-dive into the content and the structure of the “datatable” – going thru things like ancestors, the difference between phantoms and real objects, tombstones and the garbage collector on the DBLayer and much more.

How the Active Directory – Data Store Really Works (Inside NTDS.dit) – Part 1

You might as I have asked yourself many times – What is inside NTDS.dit? (Most experienced Active Directory admins knows that NTDS.dit is the database and the physical on disk store that Active Directory uses to store information – most of you have probably got in touch with NTDS.dit during backup and restore scenarios)

Long story in a short version – I wasn’t satisfy not knowing – neither was I after being reading the following article:
(That I actually think isn’t that bad – but is also probably the most detailed public available information on the subject)
[1] http://technet.microsoft.com/en-us/library/cc772829(WS.10).aspx

So I decided with a very good friend of mine Stanimir Stoyanov (Microsoft Visual C# MVP) to go ahead and build a tool that could read NTDS.dit and decode its internals, and then we started a journey that has given us invaluable knowledge at this part of Active Directory, this is the first article in a series of articles that will describe what’s really inside NTDS.dit and how Active Directory works on the database layer.

The illustration below has been presented in various documentations since Active Directory was initially released over 10 years ago; a similar illustration is also available in (However after this research project it’s actually turning out to be inaccurate in some aspects – in the way the DRA/REPL communicates with the DBLayer) [1]

Table 1: DSA Components (Simplified for the DBLayer)

Component

Description

Ntdsa.dll – Directory System Agent The DSA, which runs as Ntdsa.dll on each domain controller, provides the interfaces through which directory clients and other directory servers gain access to the directory database (the DBLayer). In addition, the DSA enforces directory semantics, maintains the schema, guarantees object identity, and enforces data types on attributes.
Esent.dll – Extensible Storage Engine (ESE) APIs The Extensible Storage Engine (ESE) is an advanced indexed and sequential access method (ISAM) storage technology. ESE enables applications to store and retrieve data from tables using indexed or sequential cursor navigation. It supports denormalized schemas including wide tables with numerous sparse columns, multi-valued columns, and sparse and rich indexes. It enables applications to enjoy a consistent data state using transacted data update and retrieval.

 

ESE was formerly known as Joint Engine Technology (JET) Blue, The DBLayer uses the ESE APIs documented here: http://msdn.microsoft.com/en-us/library/windows/desktop/gg269259(v=exchg.10).aspx

NTDS.dit The on physical-disk file that represent the ESE/JetBlue database that holds the information store for the given DSA/Active Directory Domain Controller.

Data Store Physical Structure / Inside NTDS.dit – Tables

Finally we can start looking into the content/internal structure of NTDS.dit – but first let’s take a look on what has been reveled before, the illustration below is from [1] and is accurate as far as outside the white box that represent the tables within the database, the tables do exist (Except for * “sd_table” on Windows 2000 DSAs) – but there is more tables that isn’t mentioned in this example.

So it’s about time to reveal the real table structure of an NTDS.dit database file – It’s time to use the tool we produced to first discover this:

Table 2: NTDS.DIT – Tables

Table

Description

Minimum DSA Version

Datatable Contains all objects and phantoms [2.1] represented as rows (1 object/phantom = 1 row in the table) from any instanced naming context (NC) held as either writable or read-only by the Directory System Agent (DSA) hosting the database and where columns represent every [2:3] attribute present in the schema except linked attributes [2:2]

 

[2.1]: phantoms are references to object’s hosted outside the given database (NTDS.DIT) and the given Directory System Agent (DSA)

[2:2] Post-Windows Server 2003 the attribute “ntSecurityDescriptor” is stored in the “sd_table” rather than in the “datatable”

[2:3] Some columns doesn’t reflect attributes and are columns pre-defined in the NTDS.dit template database generated by Microsoft (those are needed for internal states to the DSA)

Windows 2000 Server

 

Note: Windows Server 2008 R2 added a column to support the “is-Recycled” state

Hiddentable Contains one row but several columns that defines the state of the database as well the [2:2] DNT (reference) of the NTDSA-Settings object that represents this DSA (used for finding config information specific to this domain controller.)

 

[2:4] The concept of DNTs (Distinguished Name Tags)

Windows 2000 Server
Note: Windows Server 2003 Introduced additional state columns such as backupexpiration_col
Link_table Contains link-pair references (DNT, DNT), the link base (link id >> 1) and possibly a binary blob (In case of DN-binary, DN-string syntax) Windows 2000 Server

 

Note: Windows Server 2008 R2 added a column to support deactivated links for recycle-bin

Sd_table Contains single-instance-stored SDs (Security Descriptors) that pre-Windows Server 2003 was stored in the ntSecurityDescriptor attribute in the “datatable” – those are now instead referenced to the SDs in the “sd_table” that is, if more than one object has exactly the same security defined (Security Descriptor) both objects are referenced to the same row in the “sd_table”, hence the single-instance-storage and reducing the size needed to store Security Descriptors. Windows Server 2003.
Sdpropcounttable Used by the Security Descriptor Propagation Demon (SDProp) responsible for Security Descriptor inheritance down the tree, within the local database  
Sdproptable Used by the Security Descriptor Propagation Demon (SDProp) responsible for Security Descriptor inheritance down the tree, within the local database Windows 2000 Server
Quota_rebuild_progress_table Contains temporary information during quota tracking rebuild, for the Active Directory quota feature introduced in Windows Server 2003 – this allows the demon to keep track of processed objects. Windows Server 2003
Quota_table Contains quota tracking information, for the Active Directory quota feature introduced in Windows Server 2003, quota tracking is peer naming context (NC) and for a given security principal identified by its SID. Windows Server 2003
MSysObjects ESE Internals – out of scope for this article N/A
MSysObjectsShadow ESE Internals – out of scope for this article N/A
MSysUnicodeFixupVer2 ESE Internals – out of scope for this article N/A

In the next article – we will take a deep-dive into the content and the structure of the “datatable” also known as the object-store.