Беспарольная авторизация на сервере OpenLDAP с использованием SASL

LDAP
Возникла как-то у вашего покорного слуги насущная потребность ходить на LDAP-сервер секьюрно (то есть по шифрованному соединению, а не со 100% гарантией безопасности, как можно было бы подумать), но без ввода пароля. Идея хорошая, но почему-то традиционно слова «секьюрно» и «без пароля» ассоциируются с такими громоздкими понятиями, как Kerberos, GSSAPI, SSO, Настройка всего этого — занятие не только трудоёмкое, но и не всегда оправданное: один человек это настроит, другой же, который рано или поздно придёт работать на его место, довольно быстро сойдёт с ума, а это нехорошо, потому что с людьми нужно быть… гуманнее как минимум :)
Так уж получилось, что из-за происков Microsoft с её вездесущим AD в контексте авторизации на LDAP-сервере «Kerberos» звучит к месту и не к месту. При этом мало кто вспоминает о механизме асимметричного шифрования ключевой парой, которым nix-админы частенько пользуются как простым и безопасным методом доступа к серверам по протоколу SSH — ведь если оставить пустой парольную фразу криптования приватного ключа на этапе генерации оного, то можно будет беспрепятственно заходить на все серверы, где имеется слепок этого ключа в authorized_keys, не вводя при этом никаких паролей!
Так вот, метод EXTERNAL прослойки аутентификации SASL как раз и позволяет аналогично SSH ходить к LDAP-серверу, используя одну из разновидностей асимметричного шифрования ключевой парой, а именно — инфраструктуру X.509 (она же SSL). Собственно, ведь что такое сертификат, используемый в SSL — это слепок приватного ключа — публичный ключ, «заверенный» сургучной печатью… то есть подписью третьей стороны — удостоверяющего центра (его ещё называют центром сертификации), подтверждающего таким образом корректность информации о владельце сертификата, прилагаемой к публичному ключу. Таким образом, в основе X.509 лежит всё тот же до боли знакомый механизм, дополненный всего лишь одной дополнительной сущностью — удостоверяющим центром.
Имея собственный «корневой» центр сертификации (создаётся несколькими командами openssl, либо с помощью OpenCA, EJBCA, TinyCA и прочих, им подобных), вы можете авторизоваться на LDAP-сервере как Subject сертификата, если сервер доверяет центру сертификации, поставившему соотв подпись.
Итак, что требуется сделать:
  • 1) На стороне сервера указать:
    • Пусть к сертификату самого сервера;
    • Пусть к приватному ключу, использованному при генерации публичного ключа в сертификате сервера (это должен быть приватный ключ без криптования парольной фразой);
    • Сертификат удостоверяющего центра (CA), выдавшего сертификаты сервера и клиента.
  • 2) И симметрично на стороне клиента:
    • Сертификат клиента;
    • Приватный ключ, использованный при генерации сертификата клиента — обязательно без пароля;
    • Сертификат удостоверяющего центра (CA), выдавшего сертификаты сервера и клиента.
На стороне сервера нода cn=config должна содержать следующие директивы:
olcTLSCertificateFile: /путь/к/сертификату/сервера
olcTLSCertificateKeyFile: /путь/к/приватному/ключу/сервера
olcTLSCACertificateFile: /путь/к/сертификату/центра/сертификации
olcTLSCRLCheck: none
olcTLSVerifyClient: allow

На стороне клиента в личном файле конфигурации LDAP-клиентов ~/.ldaprc, либо в глобальном /etc/openldap/ldap.conf (где-то это будет файл /etc/ldap/ldap.conf или какой-то иной — см. документацию), должно быть something like this:
BASE	начальный_dn
URI	ldap://имя_сервера[:порт]
TLS_REQCERT     allow
TLS_CERT	/путь/к/сертификату/клиента
TLS_KEY		/путь/к/приватному/ключу/клиента
TLS_CACERT      /путь/к/сертификату/центра/сертификации
SASL_MECH	EXTERNAL


Теперь проверьте как это работает:
ldapwhoami -vvv -ZZZ

Опция ZZZ требует установления доверительных отношений в ходе «рукопожатия» TLS только в случае, если все участвующие в нём стороны полностью доверяют друг другу на основе доверия третьей стороне, подписавшей их сертификаты.

И…
ныне возрадуйтесь, коли узреете вывод подобного рода:
ldap_initialize( <DEFAULT> )
SASL/EXTERNAL authentication started
SASL username: cn=Andrey Konovalov,ou=Users,o=Express TeleCom Security,c=RU
SASL SSF: 0
dn:cn=Andrey Konovalov,ou=Users,o=Express TeleCom Security,c=RU
Result: Success (0)

— ведь свидетельствует он о том, что сервер успешно авторизовал вас как DN, указанный в поле Subject клиентского сертификата.
Но… и в этой бочке мёда обнаруживается вполне отчётливо своя ложка дёгтя, а именно:
поле Subject сертификата в общем случае по закону подлости и близко не соответствует структуре реального DIT (структуре LDAP-каталога) — и не только потому, что вы такой нерадивый админ, но ещё и потому, что шибко умные программы типа Zimbra Collaboration Suite уж очень любят сами диктовать структуру каталога, полагая его не иначе как своей собственностью.
Преобразовать DN, указанный в Subject, в реальное отличительное имя пользователя нам поможет атрибут olcAuthzRegexp ноды cn=config:
olcAuthzRegexp: "^cn=([^,]+),.+,c=RU$" "ldap:///ou=people,dc=company,dc=ru??one?(&(objectClass=posixAccount)(gecos=$1))"

В данном случае из всего Subject используется только значение CN, которое подставляется в фильтр поиска пользователя (и пусть вас не смущает тот факт, что в моём случае этот фильтр выглядит немного экзотично ;) ).

В итоге мы имеем (относительно) защищённый канал до LDAP-сервера плюс неоспоримое удобство отсутствия необходимости ввода пароля, чего собственно хотелось мне и чего соответственно и вам горячо желаю.

12 комментариев

avatar
А подскажите пожалуйста на каком дистрибутиве вы это реализовывали?
Попытался провернуть подобное на ubuntu, оказалось там ldap обрезанный из механизмов аутентификации sasl только:
supportedSASLMechanisms: DIGEST-MD5
supportedSASLMechanisms: NTLM
supportedSASLMechanisms: CRAM-MD5
Возможно придется все же из исходников ставить. Но хотелось бы знать где это можно будет сходу реализовать.
avatar
В Debian пакет OpenLDAP собран с GnuTLS, а для того, чтобы работало описанное в статье — нужно собрать с OpenSSL. Т.е. пересобрать да, нужно. Где работает по дефолту сказать не могу, но обнаружить проблему довольно просто: натравить ldd на ббинарник slapd и посмотреть, с чем он слинкован.
avatar
Да, я уже очень давно не пользуюсь дистрибутивным OpenLDAP, мне легче самому собрать.
avatar
Спасибо за ответ. Я уже собрал LDAP из исходников. Только не получается все равно подключиться по SSL. Может знаете из личного опыта, какие могут быть проблемы? Гуглеж пока особо не помог… :(

LDAP собирал так:
BerkeleyDB.5.3, OpenSSL, Cyrus-SASL
./configure --sysconfdir=/etc --libdir=/usr/lib/ldap --sbindir=/usr/sbin --localstatedir=/var/lib --prefix=/usr --libexecdir=/usr/bin --mandir=/usr/share/man --with-tls=openssl --enable-slapd --with-cyrus-sasl --enable-monitor --enable-overlays --enable-syncprov --enable-crypt --enable-relay --enable-rewrite --enable-rwm --enable-mdb --enable-bdb --enable-hdb --enable-syslog --enable-accesslog --enable-auditlog

На сервере сn=config:
olcTLSCACertificateFile: /etc/ssl/certs/ca.cert
olcTLSCertificateFile: /etc/ssl/ldap/ldap.pem
olcTLSCertificateKeyFile: /etc/ssl/ldap/ldap.pem
olcTLSCRLCheck: none
olcTLSVerifyClient: allow

У клиента .ldaprc:
BASE    dc=host,dc=net
URI     ldaps://ldap.host.net
TLS_REQCERT     never
TLS_CERT        ~/.ssl/addtest2.cert
TLS_KEY         ~/.ssl/addtest2.key
TLS_CACERT      /etc/ssl/certs/ca.cert
SASL_MECH       EXTERNAL

Подключения не происходит:
user@client1:~$ ldapwhoami -d1 -vvv -ZZZ -Y EXTERNAL -H ldaps://ldap.host.net:636
ldap_url_parse_ext(ldaps://ldap.host.net:636)
ldap_initialize( ldaps://ldap.host.netu:636/??base )
ldap_create
ldap_url_parse_ext(ldaps://ldap.host.net:636/??base)
ldap_extended_operation_s
ldap_extended_operation
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP ldap.host.net:636
ldap_new_socket: 3
ldap_prepare_socket: 3
ldap_connect_to_host: Trying 192.168.18.100:636
ldap_pvt_connect: fd: 3 tm: -1 async: 0
ldap_err2string
ldap_start_tls: Can't contact LDAP server (-1)
avatar
У Вас просто на 636-м порту и нет ничего, либо slapd прибиндился на localhost :)
Вообще StartTLS работает на стандартном порту 389, совершенно ни к чему использовать специальный SSL-порт 636.
avatar
Собственно, чтобы slapd слушал на 636-м порту, нужно, чтобы серверу slapd в параметрах ключа -h передавался URL вида ldaps://host:636/ или просто ldaps:///, если нужно, чтобы слушал на всех сетевых интерфейсах на стандартном ldaps-порту 636.
avatar
Спасибо. С этой частью я уже разобрался. Я еще бился с клиентскими настройками. Оказалось в файле ldap.conf нужно значения отбивать табом обязательно, а то ерунда получается.
Сейчас я пришел к выводу, что я неправильно ldap компилил, поддержки SASL у меня вообще не было. Довольно запутанный процесс получается — сначала нужно скомпилить ldap затем только cyrus-sasl, а потом еще раз ldap c параметрами --with-tls=openssl --with-cyrus-sasl. Сейчас переделываю. Если получится что-то — отпишусь, может кому полезно будет.
avatar
Фух блин! Поборол я эту хрень! Пожалуй напишу небольшой мануал (если время будет) — как на Убунте из исходников LDAP правильно ставить.
Основная идея такая:
— Скомпилить BDB (5.3) (по-умолчанию)
— Скомпилить Cyrus-SASL (2.1.26) у меня было так: ./configure --libdir=/usr/lib/sasl2 --with-openssl --with-dblib=berkeley --with-ldap
— Скомпилить OpenLDAP (2.4.33) с поддержкой первых двух и openSSL. У меня примерно так (без префиксов):
./configure --enable-spasswd --with-tls=openssl --enable-slapd --with-cyrus-sasl --enable-crypt --enable-hdb=yes --enable-ppolicy --enable-valsort --enable-monitor=yes --enable-overlays --enable-syncprov --enable-rewrite=yes --enable-rwm --enable-syslog=yes --enable-accesslog --enable-auditlog --enable-modules=yes
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.