Привет, Хабр!
Многим из нас не раз приходилось настраивать интеграцию того и сего c Active Directory по LDAP, и в этот момент начинается судорожный поиск в гугле, как же делалась эта конкретная штука, было же что-то такое. Пусть обобщённая информация останется здесь.
Дисклеймер. В этой статье рассматривается интеграция исключительно с Active Directory на базе Windows Server ADDS с интегрированной в AD DNS-зоной домена. Остальные реализации
от лукавогомогут иметь свои особенности, с которыми необходимо ознакомиться в документации вендора.
Подключение
Адрес сервера
Он, в принципе, может зависеть от вашей топологии AD, но в абсолютном большинстве случаев резонно указывать в его качестве FQDN домена, к которому подключаетесь — даже если у вас разветвлённая сеть, достаточно корректно настроить netmask ordering, чтобы клиент получал в ответе ближайший к нему контролер. Есть частные случаи, когда, например, в целях безопасности, вы указываете в привязке, например конкретный RODC, однако это чревато потенциальными проблемами при изменениях — кто вспомнит через пять лет, что все iLO смотрят именно на него? В качестве компромисса можно рассмотреть вариант установки кластеризированного балансировщика (например на HAProxy в TCP-режиме) перед несколькими RODC c заведением DNS A-записи, например ro.domain.tld на кластерный адрес — таким образом получите и отказоустойчивость, и мониторинг упростите, и гибкую систему балансировки, и текстовый журнал, который легко парсится, а вместе с ними в довесок ещё и одну потенциальную точку отказа. Выбор за вами.
Сетевое взаимодействие и шифрование
Нужно ли оно в вашем конкретном случае — опять же, вам стоит определить самим исходя из путей трафика и применяемых политик безопасности. Если интегрируемая ИС находится в одном контуре с контроллерами домена, возможно, и не стоит. Если всё же используете, у вас, разумеется, должна быть развёрнута Enterprise PKI, а узел — доверять её корневому сертификату. Соответственно, если случится перевыпускать корневой, добавится ещё одна боль. Порты: tcp,udp/389 для простого LDAP и tcp/636 для шифрованного, не забудьте про межсетевые экраны.
Учётная запись для привязки
В большей части систем в качестве идентификатора используется distinguishedName, поэтому планируйте структуру каталогов заранее! Заведите специальный organizational unit для технических учётных записей, подумайте, хотите ли внут��и делать вложенные подразделения по сервисам, разработайте нейм-план — привязок много, сервисов много, УЗ копятся быстро и переделывать потом это всё долго, больно, а иногда и дорого. Практика показала, что одно из самых удобных решений, когда ваш DN выглядит примерно так: CN=T-ISNameLdap,OU=TechAccounts,OU=Company,DC=domain,DC=tld. Заполняйте дескрипшн, указывая, на каком сервере/пуле серверов используется УЗ, укажите менеджера, кто точно знает, как оно работает, иными словами сделайте максимум, чтобы через два года не задаваться вопросами «Что это? Где это? Оно живое вообще?». Используйте стойкие пароли, 16-20 символов звучит оптимальным. В качестве дополнительных мер, можно запретить таким учётным записям локальный вход на всё, кроме нужного.
Корень каталога
Практически всегда нет совершенно никаких причин, чтобы указывать в качестве Root DN весь домен. Если вы грамотно спланировали cтруктуру каталогов, то будет достаточным OU=Persons,OU=Company,DC=domain,DC=tld, содержащее в себе только учётные записи живых пользователей. Техническим, сервисным, и прочим учёткам там обычно делать нечего. Стоит ли синхронизировать с LDAP выделенные УЗ для администрирования — вопрос открытый и дискуссионный. Однако, если вам необходимо забирать и группы, можно расширить до OU=Company,DC=domain,DC=tld и далее оперировать фильтрами. Общее правило — частное лучше общего.
Фильтрация
На этом этапе обычно начинается самое интересное. В общем виде LDAP-фильтр представляет собой строку в обратной польской записи формата (<оператор>(<условие1>)(<условие2>)), описывающую критерии поиска.
Сами условия записываются в виде <атрибут><оператор><значение>, регулярные выражения недоступны, однако поддерживается wildcard (кроме некоторых атрибутов, например distinguishedName). С операторами в условиях несколько любопытнее:
= — всё просто, это равенство. Однако, в случае использования вайлдкарда мы также используем именно его, а не...
~= — приблизительно равно. Пожалуй, самый загадочный оператор, спецификация LDAP его не содержит, а эксперименты с ними не привели к какому-то вменяемого результату. Если в комментариях кто-то подскажет, как всё же его использовать, будет хорошо.
>= — лексикографическое(!) больше или равно. Проще говоря, алфавитная сортировка, выражение
(sAMAccountName>=Y)вернёт все УЗ, которые начинаютcя с Y или Z.<= — Лексикографическое меньше(!) или равно. На первый взгляд, полный аналог предыдущего, но каждый раз не до конца — выражение
(sAMAccountName<=B)вернёт все УЗ, которые начинаютcя с цифр, спецсимволов или A, УЗ у которых атрибут начинается с B возвращены не будут.
Простые условия могут выглядеть следующим образом:
# Пользователи с определённым именем (cn=Oleg Prostoy) # Пользлватели с определённым почтовым доменом (mail=*@domain.tld) # Участники группы (memberOf=CN=MyServiceAdmins,OU=AdminGroups,OU=Groups,OU=Company,DC=domain,DC=tld)
UPD из комментариев (спасибо @FilimoniC): Чтобы не привязываться к месту в иерархии, можно использовать конструкцию (memberOf=<GUID=abc..>) или (memberOf=<SID=S-1...>). Читаемость снизится, зато пересмотр структуры каталогов доставит меньше проблем.
Последний пример в предыдущем блоке хорош всем, кроме одного. Он охватывает только непосредственных участников, тогда как в домене обычно дело обстоит иначе и используется разветвлённая иерархия групп безопасности. Чтобы оперировать этим свойством, Microsoft предлагает использовать идентификаторы правил сопоставления (OID), используя формат записи <атрибут>:<OID правила>:<оператор><значение>. Правил три:
LDAP_MATCHING_RULE_BIT_AND (1.2.840.113556.1.4.803) — побитовое И. Применяется, при фильтрации, когда необходимо, чтобы все биты атрибута соответствовали желаемому значению.
LDAP_MATCHING_RULE_BIT_OR (1.2.840.113556.1.4.804) — побитовое ИЛИ. Применяется, при фильтрации, объектов, у которых хотя бы один бит совпадает.
LDAP_MATCHING_RULE_IN_CHAIN (1.2.840.113556.1.4.1941) — Транзитивный проход по объектам, полезен для обработки атрибутов member и memberOf.
С побитовой обработкой чаще всего сталкиваемся в атрибутах userAccoutControl и groupType, значения флагов перечислены в документации. Например:
# Выберем пользователей, у которых истёк срок действия пароля (8388608) # И они не могут его изменить (64).0x00800000 + 0x00000040 = 8388608 + 64 = 8388672 (userAccountControl:1.2.840.113556.1.4.803:=8388672) # Пользователей, которые или выключены, или заблокированы, # или истёк срок действия пароля # 2 + 16 + 8388608 = 8388626 (userAccountControl:1.2.840.113556.1.4.804:=8388672) # Получим участников группы MyServiceAdmins, в которую включены, например Domain Admins. (memberOf:1.2.840.113556.1.4.1941:=CN=MyServiceAdmins,OU=AdminGroupsåå,OU=Groups,OU=Company,DC=domain,DC=tld)
Помимо этого существует ещё правило LDAP_MATCHING_RULE_DN_WITH_DATA, подробное описание которого можно найти в технической спецификации AD, но оно вряд ли пригодится в таких интеграциях.
Условия компонуются с помощью операторов и скобок. Всегда группируйте условия в скобки и заключайте в скобки всё выражение. Доступно всего три ключевых оператора для компоновки условий:
! — отрицание,
| — логическое ИЛИ,
& — логическое И.
Попробуем собрать всё вместе. Нам необходимо выбрать пользователей, у которых почта имеет определённый почтовый домен, заполнено поле мобильного или внутреннего телефона, имеющих право входа в корпоративные системы и принадлежащих одной группе с учётом вложенности. Для удобства чтения (если это понятие применимо к LDAP-фильтрам), разобьём на строки:
(& (mail=*@domain.tld) (| (mobile=*) (ipPhone=*) ) (! (userAccountControl:1.2.840.113556.1.4.804:=8388672) ) (memberOf:1.2.840.113556.1.4.1941:=CN=MyServiceUsers,OU=Svc,OU=Groups,OU=Company,DC=domain,DC=tld) )
Осталось только объединить результат в одну строку.
Выбор уникального атрибута
Стандартно в рамках домена в качестве уникального атрибута используется objectGuid или SID, что удобно, так как страхует нас от потери привязки в случае переименования УЗ, например, при смене фамилии, смены адреса электронной почты, или других изменений в ходе жизненного цикла. Однако оба они являются байтовыми а не строковыми, и многие системы обрабатывают их некорректно. Если вы столкнулись с такой ситуацией, у вас есть несколько вариантов:
Всё же использовать sAMAccountName или userPrincipalName. Да, неприятно, про это нужно помнить, соответствующие изменения привязки нужно заложить в чекист переименования пользователей, но иногда это и правда самый простой вариант.
Использовать другое поле. Например, в вашей организации некоторый атрибут автоматически заполняется, например, табельным номером. Потенциальные риски очевидны — необходимо точно! быть уверенным в уникальности атрибута и том, что он присутствует у всех целевых объектов AD.
Сделать автоматизацию по заполнению extension- или кастомного (если считаете расширение схемы оправданным) атрибута строковым значением objectGuid (не забудьте включить в LDAP-фильтр приложения обязательность заполнения выбранного атрибута), например, следующего вида:
$CustomAttribute = 'extensionAttribute11' # Your connection parameters $ADSI = [adsi]'' $Searcher = [adsisearcher]$ADSI $Searcher.Filter = [string]::Join( '', @( '(&', '(objectClass=user)', '(objectCategory=person)', '(!(userAccountControl:1.2.840.113556.1.4.803:=2))', '(!(extensionAttribute11=*))', ')' ) ) $SearchResult = $Searcher.FindAll() foreach ($Result in $SearchResult) { $User = $Result.GetDirectoryEntry() $User.Put( $CustomAttribute, ([guid]($User.Properties.objectGUID.Value)).ToString() ) $User.SetInfo() }
Переписать код приложения. В случае самописных ИС этот вариант тоже не стоит сбрасывать со счетов.