Все данные каталога Active Directory хранятся в БД в файле ntds.dit. Подавляющее большинство приложений взаимодействуют с каталогом через прослойку DSA реализованную в ntdsa.dll. В свою очередь функции из ntdsa.dll не работают напрямую с ntds.dit, их функционал ограничен потребностями службы каталогов и они не могут дать нам представление о внутреннем устройстве БД Active Directory. Тем не менее ntds.dit представляет собой не что иное как БД JET Blue. В каждой версии windows (начиная с Windows 2000) есть всё необходимое для работы с этой БД.
В статье ниже я попробую осветить следующие вопросы:
- Какова структура БД?
- Как данных в ntds.dit получается «дерево»?
- Как реализовано членство в группах?
- Каков формат атрибута replPropertyMetaData и с какой точностью в метаданных репликации хранятся временные метки?
Что нужно, чтобы заглянуть в ntds.dit ?
Как минимум: esent.dll
Для «комфортного» использования из различных языков программирования над ESENT API имеются различные «обёртки». Я использовал Meneged Esent в связке с C#. На сайте проекта имеется много примеров — поэтому далее я постараюсь сконцентрироваться именно на содержимом ntds.dit.
Также следует обратить внимание, что у БД JET Blue есть такой параметр как PageSize. По-умолчанию, он равен 4096 (для тех версий esent.dll, с которыми я сталкивался). Так вот в ntds.dit размер страницы равен 8192 и этот параметр следует корректно установить до открытия БД.
У API ESENT есть несколько версий. Эти версии несовместимы! Так ntds.dit из Windows Server 2008 R2 на windows xp не откроется, только на Windoes 7. (Обратную совместимость я не проверял)
Вопрос первый: Какова структура БД?
Функция GetTableNames возвратит список таблиц в базе:
datatable — основная таблица со всеми данными каталога
hiddentable
link_table — согласно статье на technet таблица с информацией о связанных атрибутах (например MemberOf)
quota_rebuild_progress_table
quota_table
sdpropcounttable
sd_table — согласно статье на technet таблица содержит информацию о наследованных правилах доступа для каждого объекта каталога.
Остановимся подробнее на таблице datatable. Список её столбцов имеет вид (на скриншоте первые несколько строк):
Тупик? Нет!
Имена большинства колонок имеют формат ATT<одна латинская буква><цифровой код>. Так вот этот цифровой код — уникальный идентификатор атрибута Active Directory. В таблице есть одна строка, в которой значение цифрового кода столбца совпадает с его значением. Если вывести все значения типа Text из этой строки, то мы увидим, что это определение атрибута «attributeID».
JET_COLUMNID | Column Name | Jet Column Type | AD atribute Name | |
---|---|---|---|---|
JET_COLUMNID(0x6) | ab_cnt_col | Long | Single-value | |
JET_COLUMNID(0x100) | Ancestors_col | LongBinary | Single-value | |
JET_COLUMNID(0x536) | ATTb131079 | Long | subRefs | Multi-value |
JET_COLUMNID(0x121) | ATTb131088 | Long | nCName | Multi-value |
JET_COLUMNID(0x2dd) | ATTb131108 | Long | dMDLocation | Multi-value |
JET_COLUMNID(0x42c) | ATTb1376270 | Long | documentAuthor | Multi-value |
JET_COLUMNID(0x169) | ATTb1376277 | Long | secretary | Multi-value |
JET_COLUMNID(0x2b8) | ATTb1376294 | Long | associatedName | Multi-value |
JET_COLUMNID(0x470) | ATTb33 | Long | roleOccupant | Multi-value |
JET_COLUMNID(0x203) | ATTb34 | Long | seeAlso | Multi-value |
JET_COLUMNID(0x2d2) | ATTb49 | Long | distinguishedName | Multi-value |
JET_COLUMNID(0x4cc) | ATTb50 | Long | uniqueMember | Multi-value |
JET_COLUMNID(0x206) | ATTb589856 | Long | domainPolicyObject | Multi-value |
JET_COLUMNID(0x250) | ATTb589864 | Long | fromServer | Multi-value |
JET_COLUMNID(0x205) | ATTb589881 | Long | defaultLocalPolicyObject | Multi-value |
JET_COLUMNID(0x1cc) | ATTb589921 | Long | preferredOU | Multi-value |
JET_COLUMNID(0x5a6) | ATTb590037 | Long | defaultClassStore | Multi-value |
JET_COLUMNID(0x270) | ATTb590038 | Long | nextLevelStore | Multi-value |
JET_COLUMNID(0x183) | ATTb590127 | Long | notificationList | Multi-value |
JET_COLUMNID(0x238) | ATTb590192 | Long | rIDManagerReference | Multi-value |
JET_COLUMNID(0x57c) | ATTb590193 | Long | fSMORoleOwner | Multi-value |
JET_COLUMNID(0x3c8) | ATTb590246 | Long | domainPolicyReference | Multi-value |
JET_COLUMNID(0x566) | ATTb590281 | Long | localPolicyReference | Multi-value |
JET_COLUMNID(0x3c1) | ATTb590295 | Long | trustParent | Multi-value |
JET_COLUMNID(0x4c1) | ATTb590296 | Long | domainCrossRef | Multi-value |
JET_COLUMNID(0x5bc) | ATTb590304 | Long | defaultGroup | Multi-value |
JET_COLUMNID(0x57f) | ATTb590318 | Long | siteServer | Multi-value |
JET_COLUMNID(0x532) | ATTb590338 | Long | physicalLocationObject | Multi-value |
JET_COLUMNID(0x563) | ATTb590341 | Long | ipsecPolicyReference | Multi-value |
JET_COLUMNID(0x24f) | ATTb590361 | Long | dynamicLDAPServer | Multi-value |
JET_COLUMNID(0x1a4) | ATTb590381 | Long | parentCA | Multi-value |
JET_COLUMNID(0x233) | ATTb590448 | Long | ipsecOwnersReference | Multi-value |
JET_COLUMNID(0x345) | ATTq590722 | Currency | aCSNonReservedTxSize | Multi-value |
JET_COLUMNID(0x398) | ATTq591137 | Currency | aCSMaxTokenBucketPerFlow | Multi-value |
JET_COLUMNID(0x19b) | ATTq591138 | Currency | aCSMaximumSDUSize | Multi-value |
JET_COLUMNID(0x4d3) | ATTq591139 | Currency | aCSMinimumPolicedSize | Multi-value |
JET_COLUMNID(0x244) | ATTq591140 | Currency | aCSMinimumLatency | Multi-value |
JET_COLUMNID(0x12f) | ATTq591141 | Currency | aCSMinimumDelayVariation | Multi-value |
JET_COLUMNID(0x16e) | ATTq591142 | Currency | aCSNonReservedPeakRate | Multi-value |
JET_COLUMNID(0x344) | ATTq591143 | Currency | aCSNonReservedTokenSize | Multi-value |
JET_COLUMNID(0x4d4) | ATTq591144 | Currency | aCSNonReservedMaxSDUSize | Multi-value |
JET_COLUMNID(0x343) | ATTq591145 | Currency | aCSNonReservedMinPolicedSize | Multi-value |
JET_COLUMNID(0x5ac) | ATTq591191 | Currency | mS-SQL-Memory | Multi-value |
JET_COLUMNID(0x213) | ATTq591204 | Currency | mS-SQL-Status | Multi-value |
JET_COLUMNID(0x4d5) | ATTq591220 | Currency | mS-SQL-Size | Multi-value |
JET_COLUMNID(0x2c5) | ATTq591266 | Currency | msDS-Cached-Membership-Time-Stamp | Multi-value |
JET_COLUMNID(0x548) | ATTq591456 | Currency | msWMI-Int8Default | Multi-value |
JET_COLUMNID(0x52a) | ATTq591457 | Currency | msWMI-Int8Max | Multi-value |
JET_COLUMNID(0x53f) | ATTq591458 | Currency | msWMI-Int8Min | Multi-value |
JET_COLUMNID(0x214) | ATTq591459 | Currency | msWMI-Int8ValidValues | Multi-value |
JET_COLUMNID(0x2c1) | ATTq591520 | Currency | lastLogonTimestamp | Multi-value |
JET_COLUMNID(0x2cc) | ATTq591794 | Currency | msDS-LastSuccessfulInteractiveLogonTime | Multi-value |
JET_COLUMNID(0x462) | ATTq591795 | Currency | msDS-LastFailedInteractiveLogonTime | Multi-value |
JET_COLUMNID(0x440) | ATTq591835 | Currency | msDS-MaximumPasswordAge | Multi-value |
JET_COLUMNID(0x2a5) | ATTq591836 | Currency | msDS-MinimumPasswordAge | Multi-value |
JET_COLUMNID(0x200) | ATTq591841 | Currency | msDS-LockoutObservationWindow | Multi-value |
JET_COLUMNID(0x2e7) | ATTq591842 | Currency | msDS-LockoutDuration | Multi-value |
JET_COLUMNID(0x3d0) | ATTq591879 | Currency | msDS-USNLastSyncSuccess | Multi-value |
JET_COLUMNID(0x49a) | ATTq591922 | Currency | msDS-ClaimValueType | Multi-value |
JET_COLUMNID(0x476) | ATTq592002 | Currency | msKds-UseStartTime | Multi-value |
JET_COLUMNID(0x477) | ATTq592003 | Currency | msKds-CreateTime | Multi-value |
JET_COLUMNID(0x3a0) | ATTq592007 | Currency | msDS-GeoCoordinatesAltitude | Multi-value |
JET_COLUMNID(0x3a1) | ATTq592008 | Currency | msDS-GeoCoordinatesLatitude | Multi-value |
JET_COLUMNID(0x3a2) | ATTq592009 | Currency | msDS-GeoCoordinatesLongitude | Multi-value |
JET_COLUMNID(0x43b) | ATTr589945 | LongBinary | securityIdentifier | Multi-value |
JET_COLUMNID(0x1c9) | ATTr589970 | LongBinary | objectSid | Multi-value |
JET_COLUMNID(0x276) | ATTr590433 | LongBinary | sIDHistory | Multi-value |
JET_COLUMNID(0x443) | ATTr590491 | LongBinary | syncWithSID | Multi-value |
JET_COLUMNID(0x5e4) | ATTr591234 | LongBinary | mS-DS-CreatorSID | Multi-value |
JET_COLUMNID(0x50a) | ATTr591668 | LongBinary | msDS-QuotaTrustee | Multi-value |
JET_COLUMNID(0x1c2) | ATTr591978 | LongBinary | msAuthz-CentralAccessPolicyID | Multi-value |
JET_COLUMNID(0x5) | cnt_col | Long | Single-value | |
JET_COLUMNID(0x1) | DNT_col | Long | Single-value | |
JET_COLUMNID(0x5f1) | extendedprocesslinks_col | LongBinary | Single-value | |
JET_COLUMNID(0x9) | IsVisibleInAB | UnsignedByte | Single-value | |
JET_COLUMNID(0x8) | NCDNT_col | Long | Single-value | |
JET_COLUMNID(0x3) | OBJ_col | UnsignedByte | Single-value | |
JET_COLUMNID(0x2) | PDNT_col | Long | Single-value | |
JET_COLUMNID(0x4) | RDNtyp_col | Long | Single-value | |
JET_COLUMNID(0xa) | recycle_time_col | Currency | Single-value | |
JET_COLUMNID(0x7) | time_col | Currency | Single-value |
Остался один «нерасшифрованый» атрибут ATTc0 — это ссылка на «objectClass».
Обратите внимание — все колонки, хранящие атрибуты каталога Active Directory заведены как Multi-value в не зависимости от схемы.
Вопрос второй: Как данных в ntds.dit получается «дерево»?
Название столбца «DNT_col» провоцирует предположить, что он как-то связан с distinguishedName объекта (как позже выяснилось — они равны)
Значения колонки DNT_col начинаются с 1, причём строка с DNT_col=1 соответствует интересному объекту со значенем атрибута name="$NOT_AN_OBJECT1$"
Строка с DNT_col=2 содержит в себе атрибуты объекта с именем "$ROOT_OBJECT$" (Не путать с RootDSE)
С DNT_col=6 начинаются определения objectClass'ов
Опять тупик? Опять нет!
Пойдём другим путём.
Поиск по колонке ATTm589825 (name) значения типа Text «Administrator» вернул запись с DNT_col=3841 и PDNT_col=1951
Поиск по колонке ATTm589825 (name) значения типа Text «Users» (контейнер, в котором расположена учётная запись стандартного администратора) вернул запись с DNT_col=1951 и PDNT_col=1944
Вот она связь! столбец PDNT_col содержит идентификатор DNT_col родительского объекта.
В строке с DNT_col=1944 ( PDNT_col=1943) — оказался объект домена второго уровня (ntds.dit был взять с тестового контроллера домена второго уровня)
В строке с DNT_col=1943 ( PDNT_col=2) — объект домена первого уровня.
Если вы когда-нибудь задавались вопросом: «Почему в при подключении к dc=contoso,dc=com не отображаются объекты из cn=Configuration,dc=contoso,dc=com?» то вам следует обратить внимание на колонку NCDNT_col — она содержит ссылки на объект контекста именования. (причём, объект домена первого уровня не имеет контекста именования — моэет по этому он и не виден?). Для Объектов в контекстах именования «dc=contoso,dc=com» и «cn=Configuration,dc=contoso,dc=com» значение этой колонки отличается.
Не менее интересно как из этих чисел получается полное distinguishedName объекта? Какой аттрибут обекта является относительным уникальным именем (RDN) и используется для построения полного distinguishedName.
Колонка RDNtyp_col содержит идентификатор атрибута (attributeID), содержащего RDN.
В колонке CNT_col содержится количество объектов, связанных с текущим. Эта колонка используется механизмом Link Cleaner
Если бит в колонке OBJ_col установлен в 1, значит данная строка описывает объект каталога. Иначе — это объект-фантом.
Как реализован SubTree поиск?
Понятно, что при такой структуре для поиска OneLavel достаточно поискать среди записей с PDNT_col равной DN базы поиска, а вот как поискать по SubTree? Обходить все ветви? Нет это слишком сложно.
Присмотримся к значениям в колонке с говорящим именем Ancestors_col. В глаза бросается, что чем глубже в иерархии расположен объект, тем длиннее значение в этой колонке. Каждый уровень вложенности добавляет к длине 4 байта. Это не что иное как список родительских объектов в порядке очерёдности.
Причём список начинается с dn=2, т.е. с "$ROOT_OBJECT$"
Кто занимается поддержанием Ancestors_col в актуальном состоянии? Согласно документу при перемещении объекта в новое место изменяется только его PDNT_col, а значение Ancestors_col обновляется механизмом SDProp заодно с пересчётом новых правил доступа к объекту.
Вопрос третий: Как реализовано членство в группах?
Почему c 2003-го сервера появилась возможность влючать в группу брактически неограниченное число прользователей, и при этом в строковый или числовой multi-value атрибут не удастся записать боле ~1200 значений?
Да потому, что «member» и «memberOf» определены как ссылки. В таблице datatable нет столбцов, соответствующих этим атрибутам. Значения этих атрибутов реализованы как соответствия в отдельной таблице link_table.
Быть может именно поэтому при удалении группы (без использования RecucleBin) мы теряем сведения о её членах — объект удалённой группы получает новый идентификатор в DNT_col при перемещении в контейнер удалённых объектов (а связь группы и её члена построена именно по этому идентификатору)
JET_COLUMNID(0x2) | backlink_DNT | Long | Single-value |
JET_COLUMNID(0x3) | link_base | Long | Single-value |
JET_COLUMNID(0x100) | link_data | LongBinary | Single-value |
JET_COLUMNID(0x4) | link_deactivetime | Currency | Single-value |
JET_COLUMNID(0x5) | link_deltime | Currency | Single-value |
JET_COLUMNID(0x1) | link_DNT | Long | Single-value |
JET_COLUMNID(0x80) | link_metadata | Binary | Single-value |
JET_COLUMNID(0x7) | link_ncdnt | Long | Single-value |
JET_COLUMNID(0x101) | link_ndesc | Long | Single-value |
JET_COLUMNID(0x6) | link_usnchanged | Currency | Single-value |
Названия и тип столбцов намекают, а анализ содержащихся в них данных подтверждает, что:
link_DNT — Содержит идентификатор dn (соответствует DNT_col из datatable) объекта, связь которого описывается (например, объекта группы)
backlink_DNT — Содержит идентификатор dn связанного с ним объекта (например, объекта пользователя)
link_ncdnt — Содержит идентификатор dn контекста именования, в котором расположены участники связи.
link_usnchanged — Содержит USN последнего изменения всязи
link_data — Я видел это поле заполненным только для связей объектов NTDS Settings. Да! Объекты NTDS Settings имеют связи с объектами разделов каталога повидимому посредством этих связей задаются параметры репликации различных разделов каталога.
link_deltime — Содержит время удаления связи.
link_metadata — содержит метаданные, необходимые для репликации внесённых изменений. Заполеннными значения этой колонки я видел только на Windows 2012. Christoffer Andersson пишет, что колонка содержит структуру DS_REPL_VALUE_META_DATA и здесь я с ним категорически не согласен. Структура DS_REPL_VALUE_META_DATA используется на более высоком уровне — её возвращают вызовы ntdsa.dll. Данные, содержащаеся в этой колонке имеют размер меньше, чем необходимо под структуру DS_REPL_VALUE_META_DATA. Пока данная колонка остаётся для меня загадкой. Впринципе метаданные репликации для аттрибутов-связей можно получить из построенного атрибута msDS-ReplValueMetaData, но это не раскрывает внутреннего механизма обработки и хранения этих данных.
Вопрос четвёртый: Каков формат атрибута replPropertyMetaData и с какой точностью в метаданных репликации хранятся временные метки?
В этом атрибуте хранятся метаданные репликации в двоичном виде:
Что хранится в первых 8-ми байтах данной структуры я пока не разобрался.
Следует обратить внимание на тот факт, что в структуре replPropertyMetaData перечисляются только реплицируемые атрибуты со значениями. Так, например, вы не увидите в replPropertyMetaData идентификатора атрибута logonTimestamp.
Тут есть интересный момент: в структуре replPropertyMetaData для объектов безопасности Active Directory (пользователей, групп и компьютеров) присутствует атрибут objectSid.
Кстати, чтобы расшифровать значение этого атрибута — не обязательно так напрягаться — существует построенный атрибут msDS-ReplAttributeMetaData — он собой предстваляет не что иное как разобранное значение replPropertyMetaData.
Временные метки в Active Directory это 64-битные беззнаковые целые числа, указывающие количество секунд, прошедших с 00:00 1 января 1601 года. Отсюда вытекает интересный нюанс: при правке одного атрибута одного и того же объекта на двух контроллерах домена в течение 1 секунды (например при пакетной обработке большого числа пользователей) можно получить неожиданный результат.
По данным статьи на technet выигрывает версия атрибута от контроллера с меньшим GUID.
Использованная информация:
technet.microsoft.com/en-us/library/cc772829%28v=ws.10%29.aspx
blogs.chrisse.se/2012/02/20/how-the-active-directory-data-store-really-works-inside-ntds-dit-part-1
blogs.chrisse.se/2012/02/20/how-the-active-directory-data-store-really-works-inside-ntds-dit-part-2
blogs.chrisse.se/2012/02/20/how-the-active-directory-data-store-really-works-inside-ntds-dit-part-3
blogs.chrisse.se/2012/02/20/how-the-active-directory-data-store-really-works-inside-ntds-dit-part-4
gexeg.blogspot.ru/2009/12/active-directory.html
www.ntdsxtract.com/
Все эксперименты ставились над ntds.dit из Windows Server 2012 Eng
Автор: Slipeer