Перейти к основному содержимому
Версия: 2.5

LDAP

LDAP (Lightweight Directory Access Protocol) — открытый протокол прикладного уровня с множеством реализаций для доступа к службе каталогов, использующий TCP/IP и позволяющий производить операции аутентификации (bind), поиска (search) и сравнения (compare), а также операции добавления, изменения или удаления записей об организации, ее активах и пользователях.

Основные понятия LDAP

DN (Distinguished Name) – Уникальное имя; отличительное имя. Всякая запись в каталоге LDAP состоит из одного или нескольких атрибутов и обладает уникальным именем. Примеры уникальных имен:

  • CN=Администратор,CN=Users;
  • CN=Иван Петров,OU=Сотрудники,DC=example,DC=com.

Уникальное имя состоит из одного или нескольких относительных уникальных имён (RDN). RDN (Relative Distinguished Name) – Относительное уникальное имя. Уникальное имя (DN) состоит из одного или нескольких относительных уникальных имён (RDN), разделённых запятыми. Относительное уникальное имя имеет вид ИмяАтрибута=значение. На одном уровне каталога не может существовать двух записей с одинаковыми относительными уникальными именами. Примеры RDN:

  • CN=Иван Петров;
  • OU=Сотрудники;
  • DC=example;
  • DC=com.

Атрибуты записи. Каждая запись в LDAP-системе обладает уникальным именем (DN) и хранится как совокупность элементов, называемых атрибутами. Атрибуты являются парами «ключ-значение», например: «mail: admin@example.com», или «CN: Иван Петров». Каждый атрибут определён в наборе схемы данных.

  • DIT (Data Information Tree) – Информационное дерево Каталога (Информационное дерево справочника). Все записи в системе LDAP иерархически упорядочены в виде веток на деревьях, называемых DIT. DIT представляет собой организационную структуру (Каталог), где каждая запись (кроме корневой записи верхнего уровня) имеет ровно одну родительскую запись и под ней может находиться любое количество дочерних записей. Запись по ее DN (distinguished name) используется для идентификации в DIT в целом и функционирует как полный путь до "корня" DIT. «Корнем» является само доменное имя (например: foiv.local; citadel.com; example.com и т.д.).

  • Base DN:

    • Корневая точка входа в LDAP, с которой начинается поиск всех объектов (пользователей, групп, компьютеров и т. д.).
  • Group DN:

    • Путь к контейнеру/подразделению, где хранятся группы.
  • User DN:

    • Путь к контейнеру/подразделению, где хранятся учетные записи пользователей.

Base DN, Group DN, User DN определяют, где искать пользователей, группы и другие объекты в структуре LDAP-каталога

Также частями спецификации Каталога LDAP, которые определяют его узлы, являются:

  • CN = Common Name (Общее Имя);
  • OU = Organizational Unit (Подразделение).
  • DC = Domain Component (Компонент Домена).

Настройка подключения к LDAP​

Настройка LDAP выполняется на странице Главная / Настройки / Пользователь / LDAP.

Памир поддерживает одновременную работу с несколькими доменами. Ниже приведены параметры настройки подключения к LDAP с пояснениями и примерами заполнения.

Основные параметры подключения​

ПараметрОписаниеПример заполнения
НазваниеПроизвольное имя подключения (для идентификации в системе).LDAP-подключение к Active Directory
ПротоколТип шифрования соединения:SSL, TLS, Без шифрования
ХостIP-адрес или FQDN LDAP-сервера.ldap.example.com, 192.168.1.100
ПортПорт LDAP-сервера (по умолчанию: 389 для незашифрованного, 636 для SSL).389, 636
Анонимное подключениеЕсли включено, логин и пароль не требуются.☑ Включено / ☐ Выключено

Учетные данные для поиска в LDAP​

ПараметрОписаниеПример заполнения
ЛогинУчетная запись для поиска в LDAP (в формате DN).CN=Администратор,CN=Users,DC=foiv,DC=local
ПарольПароль для указанного пользователя.********

Пути поиска объектов в LDAP​

ПараметрОписаниеПример заполнения
Base DNКорневая точка входа в LDAP (определяет начало поиска).DC=foiv,DC=local
User DNПодпуть для поиска пользователей (добавляется к Base DN).CN=Employees,OU=Users
Group DNПодпуть для поиска групп (добавляется к Base DN).CN=Security,OU=Groups

Примечание:

  • Полный путь для поиска пользователей: User DN + Base DN → CN=Employees,OU=Users,DC=foiv,DC=local
  • Полный путь для поиска групп: Group DN + Base DN → CN=Security,OU=Groups,DC=foiv,DC=local

Шаблоны поиска​

ПараметрОписаниеПример шаблона
Шаблон поиска пользователяLDAP-запрос для поиска пользователя (должен содержать {login}).(&(|(objectClass=person)(objectClass=inetOrgPerson))(|(uid={login})(cn={login})))
Шаблон поиска группыLDAP-запрос для поиска групп пользователя (должен содержать {dn}).(&(|(objectClass=group)(objectClass=groupOfNames))(member={dn}))

Пояснение:

  • В {login} подставляется логин пользователя при аутентификации.
  • В {dn} подставляется Distinguished Name пользователя.

Приведённый в таблице шаблон поиска пользователя рассчитан на каталоги OpenLDAP. Для Microsoft Active Directory он не подходит: там учётные записи имеют классы person и user, а логин хранится в атрибуте sAMAccountName. Проверенный на Active Directory шаблон:

(&(|(objectClass=person)(objectClass=user))(sAMAccountName={login}))

Сопоставление атрибутов пользователя​

При входе через LDAP система сначала находит запись пользователя в каталоге, а затем вычисляет из неё логин и значения атрибутов. И логин, и каждый атрибут задаются отдельным выражением на языке jq, которое применяется к одному и тому же JSON-объекту с результатами поиска.

примечание

Регулярные выражения и выбор типа выражения («Пользователь» или «Группа») больше не используются — всё сопоставление выполняется выражениями jq.

Данные, к которым применяется выражение​

Перед вычислением выражений система:

  1. Подключается к серверу под учётной записью из настроек подключения.
  2. Ищет пользователя по шаблону поиска пользователя, подставив в {login} логин, введённый на странице входа. Поиск идёт в Base DN, а если заданы User DN — последовательно по каждому из них, до первого пути, в котором что-то нашлось.
  3. Ищет группы пользователя по шаблону поиска группы, подставив в {dn} уникальное имя найденной записи.

Результат собирается в один JSON-объект — именно он подаётся на вход jq:

КлючЧто содержит
loginЛогин, который пользователь ввёл на странице входа (то же значение подставлялось в {login})
user_dnУникальное имя (DN) найденной записи пользователя
dataСписок найденных записей пользователя. Каждая запись — объект с ключами dn и attributes
groupsСписок найденных групп пользователя. Устроен так же: dn и attributes

Три правила, без которых выражение не заработает:

  • Значения атрибутов — всегда массивы строк, даже если атрибут хранит единственное значение. Поэтому к значению обращаются по индексу: [0] — первое значение атрибута.
  • Имена атрибутов чувствительны к регистру и пишутся ровно так, как их вернул сервер: sAMAccountName, а не samaccountname.
  • Двоичные атрибуты в выражениях непригодны. objectGUID и objectSid в Active Directory приходят нечитаемой строкой. Идентификатор записи система берёт из них сама, настраивать это не требуется.

JQ-выражение для логина​

Поле «JQ‑выражение для логина» в блоке «Сопоставление атрибутов» отвечает за то, как из данных каталога получить логин пользователя в системе.

  • Выражение применяется к описанному выше JSON-объекту целиком.
  • Результат должен быть непустой строкой. Если выражение вернуло null, число, массив или объект, вход не состоится: система сообщит, что логин не найден.
  • Полученное значение записывается в поле login пользователя и по нему учётная запись сопоставляется с уже существующими — подробнее в разделе Единая учетная запись пользователя.

Раз в пять минут система обновляет атрибуты активных сессий, повторяя поиск в каталоге по уже вычисленному логину. Поэтому логин должен оставаться значением, по которому шаблон поиска находит того же пользователя. Если выражение меняет значение — например, отрезает часть почтового адреса, — убедитесь, что результат по-прежнему находится шаблоном; иначе после входа атрибуты обновляться не будут, а сессия завершится.

Пример для Microsoft Active Directory​

Шаблон поиска пользователя:

(&(|(objectClass=person)(objectClass=user))(sAMAccountName={login}))

Выражение для логина:

.data[0].attributes.sAMAccountName[0]

Данные, которые получает выражение (пример сокращён — реальная запись Active Directory содержит несколько десятков атрибутов, включая служебные):

{
"login": "test.user",
"user_dn": "CN=Тестовый Пользователь,OU=Users,OU=PAMIR,DC=example,DC=local",
"data": [
{
"attributes": {
"cn": ["Тестовый Пользователь"],
"displayName": ["Тестовый Пользователь"],
"givenName": ["Тестовый"],
"sn": ["Пользователь"],
"mail": ["test.user@example.local"],
"memberOf": ["CN=Admins,OU=Groups,OU=PAMIR,DC=example,DC=local"],
"objectClass": ["top", "person", "organizationalPerson", "user"],
"sAMAccountName": ["test.user"],
"userPrincipalName": ["test.user@example.local"]
},
"dn": "CN=Тестовый Пользователь,OU=Users,OU=PAMIR,DC=example,DC=local"
}
],
"groups": [
{
"attributes": { "memberOf": [] },
"dn": "CN=Admins,OU=Groups,OU=PAMIR,DC=example,DC=local"
}
]
}

Выражение читается слева направо:

ШагЧто происходит
.dataБерётся список найденных записей пользователя
[0]Из списка берётся первая запись
.attributesБерётся набор атрибутов этой записи
.sAMAccountNameБерётся атрибут с таким именем — это массив ["test.user"]
[0]Из массива берётся первое значение — строка "test.user"

Результат для приведённого JSON — "test.user", он и становится логином пользователя в системе.

Пример для OpenLDAP​

Шаблон поиска пользователя и выражение для логина отличаются: в каталогах на основе OpenLDAP учётные записи обычно имеют класс inetOrgPerson, а логин хранится в атрибуте uid.

(&(|(objectClass=person)(objectClass=inetOrgPerson))(|(uid={login})(cn={login})))
.data[0].attributes.uid[0]

Состав атрибутов в OpenLDAP заметно скромнее, чем в Active Directory, а идентификатор записи приходит в entryUUID:

{
"login": "test.user",
"user_dn": "cn=Тестовый Пользователь,ou=Users,ou=PAMIR,dc=example,dc=int",
"data": [
{
"attributes": {
"cn": ["Тестовый Пользователь"],
"entryUUID": ["3f895c10-8721-1040-9c3b-33d3d656972d"],
"objectClass": ["inetOrgPerson", "top", "person"],
"sn": ["Пользователь"]
},
"dn": "cn=Тестовый Пользователь,ou=Users,ou=PAMIR,dc=example,dc=int"
}
],
"groups": []
}

Обратите внимание: в этом примере атрибута uid у записи нет, и выражение .data[0].attributes.uid[0] вернёт null. Состав атрибутов зависит от того, как заполнен конкретный каталог, поэтому выражение всегда нацеливают на атрибут, который реально виден в данных — как их посмотреть, описано ниже в разделе Проверка выражения. Если логин хранится, например, в sn, выражение будет .data[0].attributes.sn[0].

Сопоставление дополнительных атрибутов​

Ниже поля с выражением для логина находится список атрибутов пользователя, которые заполняются из каталога. Каждая строка списка состоит из трёх частей.

Название
Атрибут пользователя в системе, в который записывается результат.

Выражение
Выражение jq, применяемое к тому же JSON-объекту. Результат должен быть строкой; значение другого типа система считает отсутствующим. Примеры:

  • .data[0].attributes.mail[0] — почта;
  • .data[0].attributes.displayName[0] — отображаемое имя;
  • .data[0].attributes.givenName[0] + " " + .data[0].attributes.sn[0] — имя и фамилия одной строкой;
  • .groups[0].dn — уникальное имя первой из найденных групп.

Тип синхронизации
Определяет, когда обновлять атрибут:

ЗначениеПоведение
DISABLEСинхронизация выключена, значение атрибута из каталога не берётся
ON_CREATEАтрибут заполняется один раз, при первом входе пользователя через это подключение
PERIODICАтрибут пересчитывается при каждом входе и при обновлении данных активной сессии
IF_EXISTТо же, что PERIODIC, но пустой результат не стирает уже сохранённое в системе значение

Данные активных сессий обновляются раз в пять минут: система повторяет поиск пользователя в каталоге и пересчитывает выражения. Разница между PERIODIC и IF_EXIST проявляется, когда выражение не дало значения: в режиме PERIODIC атрибут в системе очищается, в режиме IF_EXIST сохраняется прежнее значение.

Результаты поиска групп​

Группы ищет отдельный запрос к каталогу, но обрабатываются они не в стороне: результат попадает в тот же JSON-объект, в ключ groups. Он доступен выражениям наравне с data, обращение такое же — .groups[0].dn, .groups[0].attributes.memberOf[0].

Пустой список групп — штатная ситуация: она означает, что по шаблону поиска группы ничего не нашлось. На вычисление логина это не влияет, вход состоится.

Членство пользователя в группах Active Directory дополнительно видно в его собственном атрибуте memberOf внутри data — это независимый от поиска групп источник, к которому тоже можно обращаться выражением.

Группы и OU пользователя​

Памир содержит два системных атрибута пользователя: ous — список OU пользователя, и groups — список групп, в которых он состоит. Атрибуты остаются в системе, но автоматически при входе через LDAP они не заполняются: значения из каталога попадают в атрибуты только через сопоставление, описанное выше.

Выражение всегда возвращает одну строку, поэтому список собирают через join. Группы — по результатам поиска групп:

[.groups[].dn] | join(", ")

Для приведённого выше примера с Active Directory результат — CN=Admins,OU=Groups,OU=PAMIR,DC=example,DC=local.

Если поиск групп ничего не вернул, те же данные обычно есть в атрибуте memberOf самой записи пользователя:

[.data[0].attributes.memberOf[]?] | join(", ")

Список OU собирается из уникального имени пользователя. Регистр ou= в разных каталогах отличается, поэтому сравнение идёт без учёта регистра:

[.user_dn | split(",")[] | select(test("^ou=";"i")) | sub("^[oO][uU]=";"")] | join(", ")

Для CN=Тестовый Пользователь,OU=Users,OU=PAMIR,DC=example,DC=local результат — Users, PAMIR.

warning

Эти выражения заполняют атрибуты строкового типа. У системных ous и groups тип «Список строк», и результат выражения в них не запишется: строку система не принимает по схеме атрибута и отклоняет вход с ошибкой Incorrect attributes: groups: is not valid by schema, а выражение, возвращающее массив ([.groups[].dn] без join), считает пустым результатом. Пока это не изменено на стороне сервера, списки групп и OU складывайте в атрибуты строкового типа.

Особые случаи​

СитуацияЧто происходит
Пользователь не найденВыражения не вычисляются вовсе, вход отклоняется с сообщением, что пользователь не найден на сервере
Атрибута нет в записиВыражение возвращает null. Для логина это ошибка входа, для атрибута — отсутствие значения
Атрибут есть, но пустЗначение приходит пустым, обращение по [0] снова даёт null — результат тот же, что и при отсутствии атрибута
Найдено несколько записейВсе они попадают в data, [0] берёт первую в том порядке, в каком их вернул сервер. Уточните шаблон поиска пользователя
Нет objectGUID или entryUUIDВход отклоняется: системе нечем однозначно связать учётную запись с записью каталога
Выражение написано с ошибкойВход отклоняется с сообщением о неверном формате данных и текстом ошибки jq

Проверка выражения​

Проверить выражение можно, не заходя в систему под учётной записью пользователя и не изучая исходный код.

  1. Сохраните подключение.
  2. Нажмите «Протестировать соединение» и введите логин и пароль любого пользователя каталога.
  3. На странице результата разверните блок «Исходные данные» — это в точности тот JSON, к которому применяются выражения, со всеми атрибутами, которые вернул сервер.
  4. Блок «Сопоставленный пользователь» показывает, какой пользователь системы получился после применения выражений.

Проверка не создаёт и не изменяет учётные записи: результат нужен только для настройки.

warning

«Исходные данные» содержат все атрибуты записи, которые вернул сервер, в том числе служебные. Не публикуйте этот вывод целиком.