Раньше или позже инженер упирается в задачу: один источник учётных записей, групп, хостов и сертификатов на всю инфраструктуру вместо россыпи локальных /etc/passwd. Этим источником почти всегда оказывается LDAP-каталог (OpenLDAP, FreeIPA, Active Directory). В этом уроке мы не поднимаем сервер - его разбирает соседняя тема - а учимся быть грамотным клиентом: понимать, как устроен каталог, и уверенно читать и менять записи утилитами ldapsearch, ldapadd, ldapmodify и ldapdelete. Это ровно объект 210.3 и навык, который пригодится при настройке SSSD, почты, Samba и любого сервиса с централизованной аутентификацией.

Как это работает
LDAP - это не база в привычном смысле, а протокол доступа к древовидному каталогу. Данные лежат в дереве, которое называют DIT (Directory Information Tree). Каждый лист и каждая ветка - это запись (entry), и у каждой записи есть уникальный путь от корня - DN (Distinguished Name). DN читается справа налево, от корня к листу, например cn=anna,ou=people,dc=cyberlake,dc=ru. Самый левый кусок, cn=anna, это RDN (Relative DN) - имя записи относительно её родителя. RDN обязан быть уникальным среди соседей, иначе путь перестанет быть однозначным.
Внутри записи лежат атрибуты в форме имя: значение. Атрибут может быть многозначным: у одного пользователя несколько mail или несколько memberOf. Какие атрибуты разрешены и какие обязательны, диктует objectClass - это как тип записи. Структурный objectClass (например inetOrgPerson) задаёт каркас, вспомогательные (auxiliary, например posixAccount) досыпают атрибуты для конкретной задачи, скажем UID и домашний каталог для входа в Linux. Схема каталога - это правила игры: попытка положить атрибут, не предусмотренный ни одним objectClass записи, отлетит с ошибкой objectClass violation.
Чтобы каталог тебя послушал, нужно представиться - это операция bind (привязка). Анонимный bind часто разрешён только на чтение публичных полей. Для записи нужен аутентифицированный bind: simple - это DN плюс пароль открытым текстом (поэтому строго поверх TLS), либо SASL - механизмы вроде EXTERNAL (по сертификату или по unix-сокету от root) или GSSAPI (Kerberos). После bind сервер проверяет ACL и решает, что тебе видно и что можно менять.
Обмен записями между человеком и каталогом идёт в текстовом формате LDIF (LDAP Data Interchange Format). Это простой текст: блок начинается со строки dn:, дальше атрибуты, блоки разделяются пустой строкой. Тот же LDIF используется и для описания изменений - тогда добавляется строка changetype со значением add, modify или delete. Понимать LDIF обязательно: и ldapadd, и ldapmodify читают именно его.
Команды и примеры
Сначала ставим клиентские утилиты. Пакеты называются по-разному:
Код: Выделить всё
# Debian 13 / Ubuntu 24.04
sudo apt install ldap-utils
# RHEL 10 / Fedora 41+
sudo dnf install openldap-clients
Код: Выделить всё
ldapsearch -x -H ldap://dir.cyberlake.ru \
-D "cn=admin,dc=cyberlake,dc=ru" -W \
-b "ou=people,dc=cyberlake,dc=ru" \
"(uid=anna)" cn mail uidNumber
Код: Выделить всё
(uid=anna) один атрибут
(objectClass=posixAccount) все учётки POSIX
(&(objectClass=person)(mail=*)) И: персона и почта задана
(|(uid=anna)(uid=ivan)) ИЛИ
(!(memberOf=cn=blocked,...)) НЕ
(cn=an*) подстановка
Чтобы каждый раз не писать -H и -b, заводим клиентский конфиг. В Debian это /etc/ldap/ldap.conf, в RHEL/Fedora - /etc/openldap/ldap.conf (или ~/.ldaprc для своего):
Код: Выделить всё
BASE dc=cyberlake,dc=ru
URI ldaps://dir.cyberlake.ru
TLS_CACERT /etc/ssl/certs/cyberlake-ca.crt
Добавление записи. Готовим LDIF-файл и скармливаем ldapadd (это тот же ldapmodify с подразумеваемым changetype: add):
Код: Выделить всё
# new.ldif
dn: uid=petr,ou=people,dc=cyberlake,dc=ru
objectClass: inetOrgPerson
objectClass: posixAccount
uid: petr
cn: Petr Sokolov
sn: Sokolov
uidNumber: 10005
gidNumber: 10000
homeDirectory: /home/petr
mail: petr@cyberlake.ru
Код: Выделить всё
ldapadd -x -D "cn=admin,dc=cyberlake,dc=ru" -W -f new.ldif
Код: Выделить всё
# mod.ldif
dn: uid=petr,ou=people,dc=cyberlake,dc=ru
changetype: modify
replace: mail
mail: petr.sokolov@cyberlake.ru
-
add: mail
mail: p.sokolov@cyberlake.ru
Код: Выделить всё
ldapmodify -x -D "cn=admin,dc=cyberlake,dc=ru" -W -f mod.ldif
Удаление записи целиком - по DN:
Код: Выделить всё
ldapdelete -x -D "cn=admin,dc=cyberlake,dc=ru" -W \
"uid=petr,ou=people,dc=cyberlake,dc=ru"
Код: Выделить всё
ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config
- Забыли -x: утилита по умолчанию пробует SASL, и поверх неготового сервера вы получаете невнятную ошибку вместо ожидаемого запроса пароля.
- Передали пароль через -w на скучной командной строке - он осядет в history и в ps. Используйте -W (спросит) или -y файл.
- objectClass violation при ldapadd: не хватает обязательного атрибута (например sn для inetOrgPerson) или указан атрибут вне всех objectClass. Сверяйтесь со схемой.
- Путаница LDIF на add и на modify: для ldapadd объявляются objectClass и атрибуты, для ldapmodify нужны строки changetype и действие (add/replace/delete) с разделителем-дефисом.
- simple bind по ldap:// без TLS гонит пароль открытым текстом по сети. Для записи всегда ldaps:// или -ZZ (StartTLS).
- Перепутан search base: -b указывает не на ту ветку, и поиск молча возвращает ноль записей. Проверьте DN базы.
- Кавычки и пробелы в DN: cn=Petr Sokolov должен быть в кавычках для шелла, а запятая внутри значения экранируется обратным слешем.
- Установите клиентские утилиты (ldap-utils или openldap-clients) на свой стенд.
- Пропишите BASE и URI вашего тестового каталога в ldap.conf и убедитесь, что ldapsearch -x "(objectClass=*)" -s base работает без явных -H и -b.
- Выполните поиск всех POSIX-учёток фильтром (objectClass=posixAccount), выведите только uid и uidNumber, сравните с -LLL и без.
- Создайте LDIF на одну запись inetOrgPerson + posixAccount и добавьте её через ldapadd.
- Напишите LDIF с changetype: modify, который replace для loginShell и add для второго mail, примените ldapmodify.
- Проверьте результат повторным ldapsearch по uid этой записи.
- Удалите запись через ldapdelete и убедитесь, что поиск её больше не находит.
- Чем DN отличается от RDN и в каком порядке читается DN относительно корня дерева?
- Какую роль играет objectClass и что произойдёт при попытке добавить атрибут, не предусмотренный ни одним objectClass записи?
- Запишите фильтр, который найдёт всех person с заданным mail, но не входящих в группу cn=blocked.
- Чем отличается LDIF для ldapadd от LDIF для ldapmodify и зачем нужна строка-дефис?
- Что делает ключ -x, чем simple bind отличается от SASL EXTERNAL и когда применяют второй?
- В каких файлах живёт клиентский ldap.conf в Debian и в RHEL и какие директивы избавляют от ручного указания -H и -b?