# LDAP - directory services основы _Протоколы · LinuxLab Knowledge Base_ **TL;DR:** LDAP - запрос к иерархическому справочнику. DN = координата объекта (cn=user,ou=People,dc=example,dc=com), bind = аутентификация, schema задаёт классы объектов и атрибуты. OpenLDAP/389-DS на Linux. ## Зачем LDAP Lightweight Directory Access Protocol (RFC 4511), стандарт доступа к **иерархическому справочнику**: пользователи, группы, hosts, сертификаты, конфиги приложений. Появился в 90-х как упрощение X.500. Сегодня используется для: - **Centralized auth**: Active Directory (Microsoft), FreeIPA (Red Hat), OpenLDAP / 389-DS (linux-only) - Адресная книга email-серверов (`ou=People` для autocomplete в Outlook/Thunderbird) - Конфиги приложений и DNS-данных в одном дереве - Хранение SSH-ключей пользователей (с patches/openssh-ldap) Не БД общего назначения. Чтение быстрое, запись редкая оптимизирован под "много read, мало write". ## Иерархическая структура Дерево объектов (DIT, Directory Information Tree): ``` dc=example,dc=com ← корень = domain ├─ ou=People │ ├─ uid=alice (objectClass: posixAccount, inetOrgPerson) │ ├─ uid=bob │ └─ uid=carol ├─ ou=Groups │ ├─ cn=admins │ └─ cn=developers └─ ou=Services └─ cn=ldap-replica ``` - **DN** (Distinguished Name), полная "координата": `uid=alice,ou=People,dc=example,dc=com` - **RDN** (Relative DN), последний компонент: `uid=alice` - **dc** (domainComponent), **ou** (organizationalUnit), **uid**, **cn** (commonName), атрибуты, формирующие DN Чтение справа-налево: `dc=com → dc=example → ou=People → uid=alice`. ## Bind, аутентификация Перед запросами клиент **bind**'ится: - **Anonymous bind**, без credentials, доступ только к public-данным - **Simple bind**, DN + пароль в plaintext (требует TLS!) - **SASL bind**, через Kerberos/EXTERNAL/DIGEST-MD5/PLAIN ```bash # Anonymous (для тестов) ldapsearch -x -H ldap://ldap.example.com -b "dc=example,dc=com" "(uid=alice)" # Simple bind ldapsearch -x -D "uid=alice,ou=People,dc=example,dc=com" -W \ -H ldaps://ldap.example.com -b "dc=example,dc=com" "(objectClass=*)" # SASL Kerberos (когда есть TGT) ldapsearch -Y GSSAPI -H ldap://ldap.example.com -b "dc=example,dc=com" "(uid=alice)" ``` - `-x`, simple bind - `-D`, bind DN - `-W`, спросить пароль интерактивно - `-H`, URL (`ldap://`, `ldaps://`) - `-Y`, SASL mechanism ## Объекты и атрибуты Каждый объект имеет один или несколько `objectClass` (определяет schema), которые задают **обязательные** и **опциональные** атрибуты: ```ldif dn: uid=alice,ou=People,dc=example,dc=com objectClass: top objectClass: posixAccount # uidNumber, gidNumber, homeDirectory objectClass: inetOrgPerson # mail, sn, givenName, telephoneNumber objectClass: shadowAccount # shadowLastChange и подобное uid: alice cn: Alice Wonderland sn: Wonderland givenName: Alice uidNumber: 1001 gidNumber: 1001 homeDirectory: /home/alice loginShell: /bin/bash mail: alice@example.com userPassword: {SSHA}xxxx # hashed ``` Главные классы для пользователя в Linux: - `posixAccount`, uid/gid, чтобы NSS+PAM могли использовать - `inetOrgPerson`, почта, имя - `shadowAccount`, password expiry ## LDAP-фильтры Поиск через **LDAP Filter** (RFC 4515): ``` (uid=alice) # точное совпадение (cn=Alice*) # wildcard (mail=*@example.com) # суффикс (&(uid=alice)(memberOf=cn=admins,ou=Groups,dc=example,dc=com)) # AND (|(uid=alice)(uid=bob)) # OR (!(loginShell=/bin/false)) # NOT (uidNumber>=1000) # >= (только для int) (objectClass=posixAccount) ``` Все спецсимволы в значении надо экранировать (`\28` для `(`). ## ldapsearch, основная утилита ```bash # Все posixAccount'ы с uidNumber >= 1000 ldapsearch -x -H ldaps://ldap -b "dc=example,dc=com" \ "(&(objectClass=posixAccount)(uidNumber>=1000))" uid uidNumber cn # Только конкретные атрибуты ldapsearch ... "(uid=alice)" mail telephoneNumber # ВСЁ что есть на объекте ldapsearch ... "(uid=alice)" "+" # operational attributes # Размер дерева ldapsearch -x -H ldap://srv -b "" -s base "(objectclass=*)" # rootDSE ``` ## LDIF, формат экспорта/импорта LDIF (LDAP Data Interchange Format), текстовый формат: ```ldif dn: ou=Test,dc=example,dc=com changetype: add objectClass: organizationalUnit ou: Test dn: uid=alice,ou=Test,dc=example,dc=com changetype: add objectClass: posixAccount objectClass: inetOrgPerson uid: alice cn: Alice sn: Wonderland uidNumber: 1001 gidNumber: 1001 homeDirectory: /home/alice ``` ```bash ldapadd -D "cn=admin,dc=example,dc=com" -W -f users.ldif ldapmodify -f changes.ldif ldapdelete "uid=alice,ou=People,dc=example,dc=com" ``` ## OpenLDAP vs 389-DS vs FreeIPA vs AD | Признак | OpenLDAP (slapd) | 389-DS | FreeIPA | Active Directory | |---------|-------------------|--------|---------|-------------------| | Default где | Debian/Ubuntu | RHEL | RHEL/Fedora | Windows Server | | Конфиг | cn=config (LDIF) | dse.ldif + console | веб-UI + CLI | Windows GUI | | Replication | mirror/N-way multimaster | multi-supplier | suppliers + RO replicas | DC + RODC | | Kerberos | отдельно (MIT) | можно | **встроен** + DNS + Kerberos + CA | **встроен** | | Сложность setup | средняя | средняя | низкая (всё-в-одном) | низкая (на Windows) | | Linux-клиенты | sssd / nss-ldap | sssd | sssd (родная интеграция) | sssd через realmd | Для **нового self-hosted** на Linux, FreeIPA (если RHEL/Fedora) или 389-DS standalone. OpenLDAP, legacy, но всё ещё распространён. ## TLS, обязательно Без TLS пароли при simple-bind идут в plaintext, `tcpdump` их достанет. Варианты: - **ldaps://** на 636, implicit TLS - **STARTTLS** на 389, opportunistic, но клиент может не запросить Современный сервер должен принимать только `ldaps://` или **запрещать** simple-bind без STARTTLS: ```ldif # OpenLDAP olcSecurity: ssf=128 ``` ## Когда что-то пошло не так - **`Invalid credentials (49)`**, неверный DN или пароль. Бывает также если запись не имеет userPassword вообще. - **`Insufficient access rights (50)`**, bind ок, но ACL-rule запрещает чтение/запись. Смотри `olcAccess`. - **`Server is unwilling to perform (53)`**, operation запрещена конфигом (например write на read-only replica). - **`Constraint violation (19)`**, пароль не прошёл pwd-policy (длина, история). - **`Object class violation (65)`**, добавляешь атрибут которого нет в objectClass'ах или удаляешь обязательный. - **Чудовищно медленный поиск**, нет индекса по атрибуту. `slapindex` после `olcDbIndex: cn,uid eq`. - **TLS handshake падает**, старые шифры на одной стороне или самоподписанный CA не в trust store клиента. ## Альтернативы и связанные - **sssd**, Linux-клиент, кэширует LDAP/AD/Kerberos для NSS+PAM - **nslcd / nss-ldap**, старый клиент, deprecated в пользу sssd - **Apache Directory Studio**, GUI для просмотра/правки дерева - **OpenID Connect / OAuth2**, современный пользовательский SSO (приложения уходят с LDAP-аутентификации в OIDC, но LDAP остаётся как backing user-store) ## Команды ```bash ldapsearch -x -H ldaps://ldap -b 'dc=example,dc=com' '(uid=alice)' mail uidNumber ``` Поиск конкретного юзера, ограниченный набор атрибутов ```bash ldapsearch -x -H ldap://srv -b '' -s base '+' ``` rootDSE - метаинформация о сервере (поддержка SASL, TLS, naming contexts) ```bash ldapadd -x -D 'cn=admin,dc=example,dc=com' -W -f users.ldif ``` Импортировать LDIF - типичный bulk-add ```bash ldapmodify -x -D 'cn=admin,dc=example,dc=com' -W < backup.ldif ``` Бэкап OpenLDAP базы данных N1 в LDIF - оффлайн-метод ```bash getent passwd alice ``` NSS-проверка - LDAP-юзер виден операционке (если sssd/nss-ldap настроены) ```bash openssl s_client -connect ldap.example.com:636 -showcerts ``` Проверить TLS-сертификат LDAPS-сервера - дебаг handshake-проблем ## См. также - [PAM - Pluggable Authentication Modules](/kb/pam.md) - [Kerberos - сетевой single sign-on](/kb/kerberos.md) - [SMTP и MTA - доставка email](/kb/smtp-mta.md) - [TLS handshake](/kb/tls-handshake.md) - [SSH - secure shell](/kb/ssh.md) - [RADIUS - аутентификация для сети, VPN, Wi-Fi](/kb/radius.md)