Application: LDAPS-клиенты.
После перевода на приём клиент-серверных подключений к LDAP-сервису "389-DS" только в зашифрованных SSL/TLS-соединениях может возникнуть проблема с проверкой валидности предлагаемого сервером x509-сертификата, если таковой "самоподписанный (self-signed)".
Прежде всего стоит проверить возможность подключения к LDAP-серверу посредством SSL/TLS-соединения, запросить сертификата и ознакомится с его параметрами:
$ echo QUIT | openssl s_client -connect ldap0.example.net:636 | openssl x509 -noout -text | less
Если SSL-сертификат "валидный" и вся цепь его электронных подписей проверяется вплоть до доверенных корневых удостоверяющих центров, то проблем с подключением у подавляющего большинства клиентов не предвидится.
Другое дело - если всю цепь электронных подписей проверить невозможно. Для примера рассмотрим далее способы уговоров разных приложений соединиться с LDAP-сервером, работающим за "самоподписанным" SSL-сертификатом.
Самому распространённому программному клиенту "ldapsearch" достаточно указать не проверять (или доверять любому, в примере) корректность электронной подписи сертификата:
$ LDAPTLS_REQCERT=allow ldapsearch -x -v -H ldaps://ldap0.example.net -D "uid=userOne,ou=People,dc=example,dc=net" -W -b "dc=example,dc=net" "(uid=userOne)"
Проверка подключения к LDAP-серверу посредством утилиты "ldapsearch" - почти обязательный шаг в процессе наладки подключения клиента к серверу, позволяющий самым простым способом убедиться в том, что параметры подключения, а также логин и пароль, в принципе верны.
В большинстве случаев клиентскому приложению достаточно предварительно подать "самоподписанный" корневой сертификат, явно указав доверять ему. Это срабатывает для всех компонентов самого "389-AS-DS" и Java-приложений, которые мне приходилось эксплуатировать.
О нюансах далее.
Linux + PHP: общий подход.
В интерпретаторе PHP для подключения к LDAPS-сервисам используется библиотека "OpenSSL", функционал которой определяется переменными окружения. На уровне несущей ОС можно задать параметр, указывающий допускать для клиент-серверных соединений любые SSL/TLS-сертификаты:
# vi /etc/environment
....
LDAPTLS_REQCERT="allow"
....
LDAPTLS_REQCERT="allow"
....
Переменные окружения считываются при входе в системы, как правило - так что нужно проследить, чтобы пользователь, в контексте которого запускается LDAP-клиент, вошёл в систему заново.
Сервис PHP-FPM из соображений безопасности по умолчанию не пропускает в свою среду переменные окружения несущей системы, так что их придётся задать явно для контекста:
# vi /etc/php/7.2/fpm/pool.d/pool0.conf
; Блок описания отдельного инстанса PHP-FPM
[pool0]
....
; Разрешаем чтение системных переменных окружения (default: Yes)
clear_env = No
env[LDAPTLS_REQCERT] = "allow"
....
[pool0]
....
; Разрешаем чтение системных переменных окружения (default: Yes)
clear_env = No
env[LDAPTLS_REQCERT] = "allow"
....
Для применения изменений нужно указать сервису PHP-FPM перечитать конфигурацию:
# /etc/init.d/php7.2-fpm reload
Для запускаемых в docker-контейнерах web-приложений переменную окружения тоже нужно явно объявить:
# vi ./docker-run.sh
....
docker run --rm --name site0.example.net-php \
--env TZ="`cat /etc/timezone`" \
--env LDAPTLS_REQCERT="allow" \
....
docker run --rm --name site0.example.net-php \
--env TZ="`cat /etc/timezone`" \
--env LDAPTLS_REQCERT="allow" \
....
Применение изменения конфигурации docker-контейнера потребует его полного перезапуска.
Важно понимать, что практически всегда обойти обойти проверку валидности сертификата сервера можно непосредственно в программном коде приложения - заданием специально для этого предназначенных параметров соединения или конфигурационных флагов в контексте исполнения. К сожалению, большинство разработчиков web-приложений, с которыми мне приходилось работать, упорно не желали заниматься такого рода доработками, так что в итоге приходилось принимать описываемые здесь глобальные меры на уровне несущего сервера.
MS Windows 10 + Apache2 + modPHP: общий подход.
Обычно PHP-программисты, разрабатывающие в среде "MS Windows", не представляют, что творится на их рабочей станции - чаще всего общее состояние "магическое". Поэтому приходится вначале выяснять, в какой точке воздействовать на систему, отталкиваясь в поисках непосредственно от исполняемого кода.
По умолчанию LDAP-модуль PHP пишет о своих нуждах и проблемах скудно:
....
Unable to bind to server: Can't contact LDAP server
Unable to bind to server: Can't contact LDAP server
Повысить детализацию вывода можно в PHP-коде, указав уровень "отладки" перед подключением к LDAP-серверу (разумеется, сразу после получения подробностей эту опцию отключаем):
<?php
....
// Set debugging LDAP connections
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
....
$ldapconn = ldap_connect($ldapurl);
$ldapbind = @ldap_bind($ldapconn, $ldapuser, $ldappass);
....
....
// Set debugging LDAP connections
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
....
$ldapconn = ldap_connect($ldapurl);
$ldapbind = @ldap_bind($ldapconn, $ldapuser, $ldappass);
....
Сообщения об ошибках LDAP-модуля в журнале событий (скорее всего в глобальном журнале "Apache2", а не виртуального "хоста") станут подробнее - вроде следующих:
....
ldap_init: trying c:\openldap\sysconf\ldap.conf
ldap_init: HOME env is c:\develop\ospanel
ldap_init: trying c:\develop\ospanel\ldaprc
ldap_init: trying c:\develop\ospanel\.ldaprc
ldap_init: trying ldaprc
....
ldap_connect_to_host: TCP ldap0.example.net:636
....
connect success
....
TLS certificate verification: Error, self signed certificate in certificate chain
TLS trace: SSL3 alert write:fatal:unknown CA
TLS trace: SSL_connect:error in error
....
ldap_init: trying c:\openldap\sysconf\ldap.conf
ldap_init: HOME env is c:\develop\ospanel
ldap_init: trying c:\develop\ospanel\ldaprc
ldap_init: trying c:\develop\ospanel\.ldaprc
ldap_init: trying ldaprc
....
ldap_connect_to_host: TCP ldap0.example.net:636
....
connect success
....
TLS certificate verification: Error, self signed certificate in certificate chain
TLS trace: SSL3 alert write:fatal:unknown CA
TLS trace: SSL_connect:error in error
....
Детальный вывод показывает, что сборка "Open Server" ищет конфигурацию LDAP в нескольких файлах, использование одного из которых удобнее всего:
C:\develop\ospanel\.ldaprc
TLS_REQCERT never
Linux + PHP: CMS "Dokuwiki".
Ключевые параметры, влияющие на выбор протокола LDAPS в файле конфигурации:
# vi /var/www/dokuwiki.example.net/www/conf/local.php
....
$conf['authtype'] = 'authldap';
....
$conf['plugin']['authldap']['server'] = 'ldaps://ldap0.example.net:636';
$conf['plugin']['authldap']['version'] = 3;
....
$conf['authtype'] = 'authldap';
....
$conf['plugin']['authldap']['server'] = 'ldaps://ldap0.example.net:636';
$conf['plugin']['authldap']['version'] = 3;
....
Просто работает.
Linux + PHP: LMS "Moodle".
Ключевые параметры, влияющие на выбор протокола LDAPS в web-панели администрирования:
URL: https://moodle0.example.net/admin/auth_config.php?auth=ldap
Администрирование -> Плагины -> Аутентификация -> Сервер LDAP:
Параметры сервера LDAP:
URL сервера: ldaps://ldap0.example.net:636 ; ldaps://ldap1.example.net:636
Использовать TLS: no
Администрирование -> Плагины -> Аутентификация -> Сервер LDAP:
Параметры сервера LDAP:
URL сервера: ldaps://ldap0.example.net:636 ; ldaps://ldap1.example.net:636
Использовать TLS: no
Просто работает.
Linux + PHP: CMS "Bitrix".
Ключевые параметры, влияющие на выбор протокола LDAPS в web-панели администрирования:
URL: https://bitrix0.example.net/bitrix/admin/ldap_server_edit.php
Администрирование -> AD/LDAP -> LDAP-серверы -> "ldap0.example.net":
Сервер:порт: ldap0.example.net 636
Тип подключения: SSL
Администрирование -> AD/LDAP -> LDAP-серверы -> "ldap0.example.net":
Сервер:порт: ldap0.example.net 636
Тип подключения: SSL
"Битрикс" в очередной раз огорчил. Уже в сто какой-то раз. Его внутренние фильтры по атрибутам оказались регистрозависимыми, хотя идеологически схема данных LDAP (классы и атрибуты) регистроНЕзависимая ("case insensitive", RFC4512, RFC4519) - пришлось дорабатывать код CMS, перехватывая событие загрузки данных и приводить содержащие атрибуты элементы массива к нижнему регистру, для унификации.
И, напоследок, в процессе активных экспериментов и работы по чистке созданных "Битрикс"-ом лишних данных, проявились невнятные проблемы с блокировкой доступа к некому "кешу", который CMS формирует для ускорения каких-то там своих операций - так вот операции удаления или модификации одного пользователя (а их у нас несколько тысяч) средствами административной панели наглухо вешает весь связанный с этим "кешем" функционал, минут на пять (!!!).
Linux + Python: общий подход.
Начиная с версии "2.7.9" проверка валидности SSL-сертификата для устанавливаемых соединений стала обязательной по умолчанию. По хорошему отключать эту проверку нужно внутри самих скриптов, но иногда приходится делать это глобально, установкой специальной переменной окружения, считываемой интерпретатором при запуске:
# vi /etc/environment
....
# Disable in Python2 certificate validation
PYTHONHTTPSVERIFY=0
# Disable in Python2 certificate validation
PYTHONHTTPSVERIFY=0
Linux + Java: общий подход.
Реализация "Java" для "Linux" не позволяет запросто отключить проверку валидности SSL-сертификата. Но отлично работает добавление условно корневого сертификата в общий перечень доверенных, размещаемый обычно в файле "$JAVA_HOME/jre/lib/security/cacerts".
Если переменная окружения "$JAVA_HOME" не объявлена, можно запросить сведения о конфигурации интерпретатора в контексте пользователя, от имени которого запускается целевое java-приложение:
user@java-app0-host:~$ java -XshowSettings:properties -version 2>&1 >/dev/null | grep -i "java.home" | awk -F '=' '{print $2}' | sed 's/^\s//;s/\s$//'
Добавление публичного ключа условно корневого "самоподписанного" SSL-сертификата в хранилище элементарно (пароль на доступ к хранилищу по умолчанию: "changeit") - например, для "Oracle Java v8":
# /usr/lib/jvm/jdk1.8.0_211/jre/bin/keytool -import -noprompt -trustcacerts -alias "Self-Signed Root CA certificat" -file ./ss-rootCA.crt -storetype JKS -keystore /usr/lib/jvm/jdk1.8.0_211/jre/lib/security/cacerts -keypass changeit -storepass changeit
Linux + Java: "Atlassian Crowd".
Ключевые параметры, влияющие на выбор протокола LDAPS в web-панели администрирования:
URI: https://crowd0.example.net/crowd/console/secure/directory/browse.action
Crowd -> Directories -> "ldap0.example.net":
Type: OpenLDAP
URL: ldaps://ldap0.example.net:636/
SSL: LDAPS
Crowd -> Directories -> "ldap0.example.net":
Type: OpenLDAP
URL: ldaps://ldap0.example.net:636/
SSL: LDAPS
Просто работает.
Linux + Java: web-сервис "Elsevier:Pure".
Ключевые параметры, влияющие на выбор протокола LDAPS в web-панели администрирования:
Администрирование -> Безопасность -> Механизмы аутентификации -> LDAP и AD -> Изменить:
Имя сервера LDAP: ldaps://ldap0.example.net:636/
Имя сервера LDAP: ldaps://ldap0.example.net:636/
Включение аутентификации через LDAP для конкретного пользователя:
Базовые справочники -> Пользователи -> "User":
Метаданные:
Пользователь, аутентифицированный внешней системой: true
Метаданные:
Пользователь, аутентифицированный внешней системой: true
MS Windows 7/8/10 + Java: "Google Apps Directory Sync".
Аналогично решению для "Linux" добавляем публичный ключ условно корневого "самоподписанного" SSL-сертификата в хранилище интегрированного в приложение java-интерпретатора (пароль по умолчанию: "changeit"):
C:\>"Programm Files\Google Apps Directory Sync\jre\bin\keytool" -importcert -trustcacerts -alias "Self-Signed Root CA certificat" -file .\ss-rootCA.crt -storetype JKS -keystore "C:\Programm Files\Google Apps Directory Sync\jre\lib\security\cacerts" -keypass changeit -storepass changeit
В реализации "Java" для "MS Win" кроме проверки цепочки подписей до известного доверенного производится также OCSP/CRL-запрос, с целью выяснить, не был ли отозван даже корректно подписанный SSL-сертификат. Типичный пример сообщения об ошибке при несостоявшемся OCSP/CRL-запросе:
....
Exception: Network problem: Unable to connect to the specified LDAP server: simple bind failed: ldap0.example.net:636, reason: SSLHandshakeException - sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: Could not determine revocation status
....
Exception: Network problem: Unable to connect to the specified LDAP server: simple bind failed: ldap0.example.net:636, reason: SSLHandshakeException - sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: Could not determine revocation status
....
Разумеется, у "самоподписанных" SSL-сертификатов не будет опций OCSP и CRL, так как нет единого центра выпуска и проверки таковых. Самый простой способ, как быстрое средство решение проблемы - глобально отключить на стороне клиента проверку присутствия сертификата среди отозванных (Java Control Panel: revocation checking).
В приложении "Google Apps Directory Sync" также предусмотрен способ отключения OCSP/CRL-проверки добавлением в файлы "config-manager.vmoptions" и "sync-cmd.vmoptions" в каталоге установки GCDS следующих параметров:
....
-Dcom.sun.net.ssl.checkRevocation=false
-Dcom.sun.security.enableCRLDP=false
-Dcom.sun.net.ssl.checkRevocation=false
-Dcom.sun.security.enableCRLDP=false
Java: "Apache Directory Studio".
Практически безальтернативным клиентским приложением с GUI-интерфейсом для работы с содержимым LDAP является "Apache Directory Studio", на мой взгляд. Иногда предлагают в этой роли "JXplorer", но он убог и некорректно работает со схемой данных (в частности, иногда пытается по своей воле впихнуть в запись атрибут, не предусмотренный объявленными классами, в результате чего получает отказ от LDAP-сервера на самых простейших операциях).
С "Apache Directory Studio" никаких проблем - после добавления "самоподписанного" корневого сертификата в список доверенных отлично это java-приложение работает через SSL-соединение.
Комбайны: web-сервис Git-репозиториев и CI/CD процедур "GitLab".
Ключевые параметры, влияющие на выбор протокола LDAPS в файле конфигурации:
# vi /etc/gitlab/gitlab.rb
....
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
main:
host: 'ldap0.example.net'
port: 636
method: 'ssl'
verify_certificates: false
....
EOS
....
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
main:
host: 'ldap0.example.net'
port: 636
method: 'ssl'
verify_certificates: false
....
EOS
....
Применение изменений в конфигурации встроенной утилитой:
# gitlab-ctl reconfigure
Проверка связи с LDAP-сервисов в CLI-среде:
# gitlab-rake gitlab:ldap:check
....
not verifying SSL hostname of LDAPS server 'ldap0.example.net:636'
LDAP authentication... Success
....
Checking LDAP ... Finished
not verifying SSL hostname of LDAPS server 'ldap0.example.net:636'
LDAP authentication... Success
....
Checking LDAP ... Finished
Возможно для применения изменений всем компонентами придётся перезапустить сервис:
# gitlab-ctl restart
Ради интереса можно почитать журнал событий:
# less /var/log/gitlab/gitlab-rails/production.log
Просто работает.
Комбайны: почтовый сервис "Zimbra".
Обращающийся к LDAP-серверам компонент написан на "Java". Типовым для этой среды исполнения подходом является добавление "корневого сертификата" в локальный список доверенных. Это делается встроенными средствами "Zimbra", в одну команду (в контексте пользователя, от имени которого запускается сервис):
# su - zimbra
$ /opt/zimbra/bin/zmcertmgr addcacert /etc/ssl/ldap/ss-rootCA.20200211.crt
$ /opt/zimbra/bin/zmcertmgr addcacert /etc/ssl/ldap/ss-rootCA.20200211.crt
Ключевые параметры, влияющие на выбор протокола LDAPS в web-панели администрирования:
Zimbra -> Configure -> Domains -> "example.net" -> Authentication:
Authentication mechanism: External LDAP
URL LDAP: ldaps://ldap0.example.net:636
ldaps://ldap1.example.net:636
StartTLS: off
....
Authentication mechanism: External LDAP
URL LDAP: ldaps://ldap0.example.net:636
ldaps://ldap1.example.net:636
StartTLS: off
....
Просто работает.
Сервисы: сервер аутентификации "Freeradius".
Ключевые параметры, влияющие на выбор протокола LDAPS в файле конфигурации соответствующего модуля:
# vi /etc/freeradius/3.0/mods-enabled/ldap
ldap ldap0.example.net {
server = "ldaps://ldap0.example.net"
port = 636
....
# SSL/TLS connection-specific options.
tls {
require_cert = 'allow'
}
}
server = "ldaps://ldap0.example.net"
port = 636
....
# SSL/TLS connection-specific options.
tls {
require_cert = 'allow'
}
}
Для применения изменений конфигурации требуется перезапуск всего сервиса:
# freeradius -C -X && systemctl restart freeradius
Просто работает.
Крайний выход: туннелирование посредством "Stunnel".
Если для какого-то клиентского приложения усилия, прилагаемые к наладке работы через "самоподписанные" сертификаты, оказываются неоправданно велики - бывает проще инкапсулировать их трафик в SSL-туннеле, оставив сами приложения работать через нешифрованный протокол LDAP на коротком участке сети внутри компьютера. Пример того, как это сделать на рабочей станции под управлением "Linux Debian/Ubuntu" посредством утилиты "stunnel".
Устанавливаем приложение и включаем его автозапуск в качестве сервиса:
# apt-get install stunnel4
# vi /etc/default/stunnel4
....
ENABLED=1
....
ENABLED=1
....
Дополняем конфигурацию "по умолчанию" парой параметров:
# vi /etc/stunnel/stunnel.conf
syslog = no
output = /var/log/stunnel4/stunnel.log
include = /etc/stunnel/conf.d
output = /var/log/stunnel4/stunnel.log
include = /etc/stunnel/conf.d
Создаём выделенную директорию для конфигураций SSL-туннелей:
# mkdir -p /etc/stunnel/conf.d
Формируем описания SSL-туннелей:
# vi /etc/stunnel/conf.d/ldap0.example.net.conf
[ldap0.example.net]
client = yes
accept = 127.0.0.1:3890
connect = ldap1.example.net:636
verifyChain = no
client = yes
accept = 127.0.0.1:3890
connect = ldap1.example.net:636
verifyChain = no
Перезапускаем сервис:
# systemctl restart stunnel4
# systemctl status stunnel4
# systemctl status stunnel4
Проверяем, запущено ли прослушивание локальных TCP-портов:
# netstat -apn | grep -i "^tcp" | grep -i "stunnel"
tcp ... 127.0.0.1:3891 0.0.0.0:* LISTEN .../stunnel4
Пробуем запросить удалённый LDAP-сервер через SSL-туннель, обращаясь к нему по незащищённому, казалось бы, соединению:
$ ldapsearch -x -v -H ldap://127.0.0.1:3890 -D "uid=userOne,ou=People,dc=example,dc=net" -W -b "dc=example,dc=net" "(uid=userOne)"
Это тоже просто работает.