You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Код PgDoorman: src/config/{user,pool,general}.rs, src/auth/hba.rs, src/pool/mod.rs, src/server/server_backend.rs.
2. Ограничения
ID
Требование
C-1
Синтаксис LDAP-строк pg_hba совместим с PostgreSQL 16–18; референс поведения — PostgreSQL 18. Исключения перечислены в CFG-HBA-3 и разделе 11. Собственных опций PgDoorman в строке нет.
C-2
Пароль клиента используется только для bind в каталог. В PostgreSQL он не передаётся ни в каком виде.
C-3
Запись users — единственное описание пула к PostgreSQL: имя, реквизиты backend, размеры, режим, хуки. Конфигурация LDAP не содержит копий этих полей.
C-4
Всё специфичное для PgDoorman в LDAP находится в pools.<db>.ldap. Глобального блока LDAP нет.
C-5
Пулы известны при загрузке конфигурации. Динамических пулов для LDAP-клиентов нет.
C-6
При отказе LDAP-аутентификации клиент получает 28000 или 28P01, подробная причина остаётся в логе. Ошибки backend и setup передаются по F-BE-3 и F-HOOK-4.
C-7
Некорректная или неоднозначная конфигурация отклоняется при загрузке, если ошибка определяется статически. При ошибке аутентификации или подготовки backend клиент получает отказ. Backend с незавершённым setup, ошибкой cleanup или нарушенным состоянием протокола закрывается и повторно не выдаётся.
3. Конфигурация
3.1 Строка pg_hba
ID
Требование
CFG-HBA-1
Метод ldap с опциями ldapserver, ldapport, ldapscheme, ldaptls, ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, ldapprefix, ldapsuffix, ldapurl.
CFG-HBA-2
Проверки при загрузке — как в hba.c PostgreSQL 18, с теми же текстами ошибок: смешение режимов; отсутствие ldapbasedn/ldapprefix/ldapsuffix; ldapsearchattribute вместе с ldapsearchfilter; ldapport=0; неизвестная опция; опция не вида name=value; map= при ldap.
CFG-HBA-3
Строже PostgreSQL, ошибки загрузки: ldapscheme не из ldap/ldaps; пустой ldapserver; ldapscheme=ldaps вместе с ldaptls=1.
CFG-HBA-4
ldapsearchfilter действует как в PostgreSQL: все вхождения $username заменяются логином; без подстановки фильтр используется как задан, без дополнительного предупреждения. Если фильтр не задан, поиск идёт по ldapsearchattribute, по умолчанию uid.
CFG-HBA-5
ldapurl: схема ldap/ldaps, один хост, base, первый атрибут, scope (по умолчанию base, как в документации PostgreSQL), фильтр; поля участвуют в CFG-HBA-2 как отдельные опции.
CFG-HBA-6
ldapbindpasswd маскируется в SHOW CONFIG и логах.
3.2 Записи users
ID
Требование
CFG-USR-1
password необязателен; для MD5/SCRAM это хеш для проверки пароля клиента. Без него локальная проверка MD5/SCRAM недоступна; запись может принимать LDAP-клиентов или подключения по trust. Существующее поведение PAM и других методов аутентификации сохраняется.
CFG-USR-2
server_password — пароль PostgreSQL в открытом виде; допускается без server_username. server_username по умолчанию равен username. Прежняя ошибка «server_password requires server_username» снимается для всех записей.
CFG-USR-3
Запись с password может быть целевой записью правила.
CFG-USR-4
Запись получает поля setup_server_query и cleanup_server_query (раздел 3.5).
3.3 Блок pools.<db>.ldap
ID
Требование
CFG-LDAP-1
Пул принимает LDAP-клиентов, только если у него есть блок ldap; пустой блок допустим.
timeout — общий бюджет соединения и операций для каждого сервера из ldapserver, по умолчанию 60s. Ожидание свободного соединения имеет отдельный бюджет той же длительности. При N серверах суммарный бюджет — до (N+1)×timeout.
CFG-LDAP-4
connections — максимальное число постоянных соединений с каталогом для пула; целое > 0, по умолчанию 2. Одно соединение обслуживает один вход одновременно. Ограничение при RELOAD описано в F-LOAD-5.
CFG-LDAP-5
cache_ttl, cache_failure_ttl, min_interval — имена и типы как у auth_query. Значения по умолчанию: 0s, 30s, 1s. Кэш успешных входов по умолчанию выключен.
CFG-LDAP-6
group_attribute — атрибут записи пользователя со списком групп. По умолчанию memberOf.
3.4 Список mapping
ID
Требование
CFG-MAP-1
mapping — упорядоченный список правил. Если задан, не пуст.
CFG-MAP-2
Правило содержит ровно один селектор: user — логин, точное сравнение с учётом регистра; group — DN группы, содержит =.
CFG-MAP-3
Правило содержит цель as_user — имя записи users того же пула. Для селектора user по умолчанию равна логину; для group обязательна.
CFG-MAP-4
Цель существует среди записей users пула; иначе ошибка загрузки.
CFG-MAP-5
Два правила с одинаковым user или одинаковым group — ошибка загрузки с указанием недостижимого правила.
CFG-MAP-6
Подстановочных селекторов («любой») нет.
3.5 SQL при выдаче и возврате backend
ID
Требование
CFG-HOOK-1
setup_server_query — новый ключ уровней general, pools.<db>, users[]. Отсутствие поля или null означает наследование; setup_server_query: "" отключает унаследованный setup. Приоритет: users[] → пул → general.
CFG-HOOK-2
cleanup_server_query дополнительно принимается на уровне users[], с приоритетом над пулом и general. Отсутствие поля или null означает наследование; пустая строка остаётся ошибкой, как в действующей конфигурации.
CFG-HOOK-3
В обоих ключах допустимы шаблоны {{username}} (логин) и {{group}} (DN из сработавшего group-правила, иначе пустая строка). Неизвестный плейсхолдер — ошибка загрузки.
CFG-HOOK-4
Подстановка текстовая, без экранирования; SQL и контекст подстановки задаёт администратор. Подставляемое значение не содержит ', ", \, ; и управляющих символов: для {{group}} проверяется при загрузке, для {{username}} — при входе. Эти проверки не гарантируют безопасность подстановки в произвольном SQL-контексте.
4. Функциональные требования
4.1 Выбор метода
ID
Требование
F-HBA-1
Первая строка pg_hba, подходящая по типу соединения, базе, логину и адресу, определяет метод.
F-HBA-2
Метод ldap исключает остальные методы, users с паролем и auth_query для этого клиента.
F-HBA-3
Если у пула нет блока ldap, LDAP-клиент получает 28000; в лог pool "<db>" has no ldap block.
4.2 Проверка в каталоге
ID
Требование
F-DIR-1
Клиенту отправляется AuthenticationCleartextPassword. Пустой пароль — 28P01 empty password returned by client.
F-DIR-2
В режиме search+bind логин с символами * ( ) \ / отклоняется без обращения к каталогу, как в PostgreSQL.
F-DIR-3
Search+bind: bind под ldapbinddn/ldapbindpasswd или анонимный; поиск в ldapbasedn по (<ldapsearchattribute>=<логин>) (по умолчанию uid) либо по ldapsearchfilter с заменой всех $username; ровно одна запись, иначе отказ; bind найденным DN и паролем клиента на том же сеансе.
При наличии group-правил group_attribute запрашивается тем же поиском (search+bind) либо чтением собственной записи после bind (simple bind).
F-DIR-6
Новый LDAP-сеанс начинает перебор хостов ldapserver с первого. Здоровое соединение переиспользуется, в том числе со вторым сервером после failover; первый перед каждым входом не проверяется. Сетевая ошибка или истечение timeout переводит текущий вход к следующему хосту с полным бюджетом timeout. Ошибка каталога (неверный пароль, нет записи, нет прав) завершает вход отказом.
F-DIR-7
TLS по строке: ldapscheme=ldaps — TLS с соединения, ldaptls=1 — StartTLS. Доверие — системное хранилище CA. Имя хоста сверяется с ldapserver.
F-DIR-8
Referrals в ответах каталога не отслеживаются.
F-DIR-9
Входы сверх connections ждут свободное соединение в пределах timeout. Перед каждым поиском в search+bind выполняется служебный либо анонимный bind; в simple bind — bind текущего пользователя. Оборванное соединение не переиспользуется; дальнейший перебор хостов — по F-DIR-6.
F-DIR-10
По истечении timeout последнего хоста — 28000; в лог хост и стадия, на которой истёк бюджет.
F-DIR-11
Обработка LDAP не блокирует обслуживание других клиентов.
4.3 Выбор записи
ID
Требование
F-MAP-1
mapping отсутствует: целевая запись — запись с именем, равным логину; её нет — отказ.
F-MAP-2
mapping задан: правила проверяются сверху вниз; первое совпавшее определяет целевую запись. Для LDAP-клиента список исчерпывающий: запись без правила недостижима.
F-MAP-3
Клиент, состоящий в нескольких группах, получает первое по списку group-правило со своей группой. Порядок значений group_attribute в ответе каталога не влияет.
F-MAP-4
Совпадений нет — 28000; в лог no match in ldap.mapping for user "<логин>"; groups=[<значения group_attribute>].
F-MAP-5
Сравнение DN групп: без учёта регистра, пробелы вокруг , и = игнорируются, других преобразований нет.
F-MAP-6
Ошибка чтения group_attribute — отказ на стадии groups; переход к следующим правилам не выполняется.
F-MAP-7
Вложенные группы не разворачиваются.
4.4 Кэш
ID
Требование
F-CACHE-1
Ключ: пул, идентификатор LDAP-конфигурации выбранной строки pg_hba, логин, хеш пароля с солью процесса. Результат одной LDAP-строки не используется для другой. Значение: результат bind, DN, сработавшее правило. Пароль в открытом виде не сохраняется и обнуляется после bind.
F-CACHE-2
Успешный результат действует cache_ttl; при 0 успешные входы не кэшируются. Отказ bind запоминается на cache_failure_ttl в пределах ключа F-CACHE-1; повтор для того же логина раньше min_interval после отказа отклоняется без обращения к каталогу.
F-CACHE-3
Reload очищает кэш.
F-CACHE-4
Наличие свободного backend-соединения не отменяет проверку (кэш или каталог).
4.5 Backend
ID
Требование
F-BE-1
Для LDAP-клиента реквизиты backend — только из целевой записи: server_username (по умолчанию username) и server_password в открытом виде. Хеш password проверяет frontend-клиента и не заменяет server_password в LDAP-пути.
F-BE-2
Без server_password LDAP-путь подключается к PostgreSQL без пароля; запрос пароля сервером — ошибка backend. Для backend с парольной аутентификацией администратор задаёт server_password.
F-BE-3
Ошибка backend передаётся клиенту как ошибка PostgreSQL.
4.6 Хуки
ID
Требование
F-HOOK-1
pool_mode: session: setup_server_query — один раз при закреплении backend за клиентом; при отключении клиента действует политика очистки F-HOOK-3.
F-HOOK-2
pool_mode: transaction: setup_server_query — при каждой выдаче backend; при каждом возврате действует политика очистки F-HOOK-3.
F-HOOK-3
Семантика cleanup_server_connections сохраняется: off отключает очистку, adaptive выполняет её по необходимости, always выполняет заданный cleanup_server_query после использования backend. Пользовательский SQL заменяет встроенную очистку. Без него adaptive использует встроенный путь, а always отклоняется при загрузке конфигурации. ROLLBACK незавершённой транзакции не зависит от режима. Изменения от setup учитываются адаптивной очисткой, в том числе set_config.
F-HOOK-4
До успешного завершения setup backend не обслуживает запросы клиента. Ошибка setup возвращается клиенту и записывается в лог; backend закрывается. При ошибке cleanup backend также закрывается, уже полученный ответ на запрос клиента сохраняется. NOTICE сам по себе не считается ошибкой.
F-HOOK-5
Результаты SQL-хуков клиенту не передаются; ошибка setup обрабатывается по F-HOOK-4.
F-HOOK-6
Хук выполняется для любого клиента целевой записи независимо от метода аутентификации.
4.7 Ошибки и лог
Стадия
Причина
Клиенту
В лог
pool
нет блока ldap
28000
pool "<db>" has no ldap block
password
пустой пароль
28P01
—
name
запрещённый символ в логине
28000
invalid character in user name
throttle
min_interval или отказ в кэше
28000
cached failure
connect
хосты недоступны, ошибка TLS
28000
хост, ошибка
service_bind
отказ служебного bind
28000
could not perform initial LDAP bind + диагностика
search
0 или >1 записей
28000
does not exist / is not unique + фильтр
user_bind
отказ bind пользователя
28000
LDAP login failed + диагностика
groups
ошибка чтения групп
28000
ошибка каталога
map
нет правила / нет записи
28000
no match in ldap.mapping …; groups=[…]
timeout
истёк timeout
28000
стадия
template
недопустимый символ для шаблона
28000
логин, запись
backend
ошибка PostgreSQL или setup_server_query
ошибка PostgreSQL
backend + текст
Успех: connection authenticated: identity="<DN>" method=ldap, сработавшее правило, целевая запись.
5. Загрузка и reload
ID
Требование
F-LOAD-1
Ошибка LDAP-конфигурации, ссылки mapping или шаблона хука отклоняет конфигурацию целиком. При RELOAD продолжает действовать прежняя конфигурация.
F-LOAD-3
По SIGHUP/RELOAD перечитываются строки pg_hba, записи users, блоки ldap. Начатый вход завершается со старым снимком.
F-LOAD-4
Изменение записи users пересоздаёт её пул. Изменение блока ldap применяется к следующим входам. Открытые сессии не пересматриваются.
F-LOAD-5
Пока завершаются входы со старой конфигурацией, старые и новые LDAP-соединения могут существовать одновременно; суммарное число входов может временно превышать connections. В первой версии общий лимит между поколениями конфигурации не обеспечивается.
6. Наблюдаемость
ID
Требование
F-OBS-1
SHOW POOLS: пул целевой записи (<db>/<username>); SHOW CLIENTS: логин клиента и целевая запись.
F-OBS-2
Метрики: входы по результату и стадии; входы в каталоге сейчас; ожидание сеанса; попадания в кэш; выполнения и ошибки хуков.
7. Безопасность
ID
Требование
SEC-1
Персональная роль без server_password требует, чтобы PostgreSQL принимал PgDoorman без пароля (trust по адресу, cert по клиентскому сертификату, peer). Граница доверия — сеть и PgDoorman.
SEC-2
Общая роль скрывает личность клиента от PostgreSQL; личность доступна через setup_server_query и лог PgDoorman. SET ROLE в общем пуле не является границей: RESET ROLE возвращает общую роль.
SEC-3
Администратор контролирует SQL хуков и отвечает за контекст текстовой подстановки. PgDoorman не экранирует значения; запрет символов CFG-HOOK-4 не является универсальной защитой SQL-шаблонов.
SEC-4
Пароль клиента и ldapbindpasswd не попадают в лог.
8. Требования к документации
ID
Требование
DOC-1
Документация на русском и английском: authentication/ldap.md, порядок методов в overview.md, метод ldap в hba.md, актуализация PAM, comparison.md, changelog.md, пример конфигурации.
DOC-2
Правило первого совпадения показано на примере пользователя, состоящего в двух группах с разными правилами: bob в manager и analysts при порядке правил manager, analysts получает роль manager; при обратном порядке — analytics.
DOC-3
Объяснить выбор по имени без mapping и первое совпадение при заданном списке. Персональное правило получает приоритет только за счёт положения в списке; без совпадения — отказ.
DOC-4
Описать бюджет timeout на каждый сервер, переиспользование здорового соединения после failover, ldapsearchfilter по правилам PostgreSQL, ограничения вложенных групп и referrals.
DOC-5
Явно: строки ldap писать как hostssl; SEC-1 и SEC-2 с примерами pg_hba.conf/pg_ident.conf PostgreSQL для trust и cert. Различать frontend-хеш password и открытый пароль backend server_password: для LDAP-доступа к PostgreSQL с парольной аутентификацией нужен server_password в конфигурации.
DOC-6
Личность в общем пуле: пример set_config('app.ldap_user', '{{username}}', false) и current_setting. Описать текстовую подстановку под контролем администратора, отключение унаследованного setup через setup_server_query: "" и закрытие backend при ошибке setup.
DOC-7
Миграция с PostgreSQL/PgBouncer: совместимые LDAP-параметры и ограничения CFG-HBA-3, раздела 11. Пароль клиента используется только в LDAP; остальные клиенты пула продолжают использовать свои методы аутентификации, включая auth_query.
DOC-8
При cache_ttl: 0 успешный вход требует проверки в каталоге. При ненулевом TTL отзыв доступа, смена группы и прекращение приёма старого пароля могут задержаться до истечения cache_ttl. Новый пароль проверяется отдельно с учётом кэша отказов и min_interval.
DOC-9
Описать ограничения RELOAD: начатый вход использует старый снимок, открытые сессии не пересматриваются, старый и новый LDAP-пулы могут временно превысить connections.
9. План BDD
Проверяем вход через PgDoorman с настоящими LDAP и PostgreSQL в существующем Rust Cucumber и Docker/Nix. Текущая ldap-infrastructure.feature проверяет только тестовый каталог через CLI; она не подтверждает LDAP-аутентификацию PgDoorman. Используем существующие шаги PostgreSQL-протокола и клиентские Python-скрипты внутри того же запуска BDD.
Таблицы ниже задают группы сценариев; независимые ошибки и варианты оформляются отдельными сценариями или строками Examples. Полное произведение транспортов, режимов пула, драйверов и политик очистки не требуется.
9.1 Стенд и повторяемость
Переиспользовать ldap_helper.rs: настоящий slapd, LDIF, временные сертификаты, смена пароля, изоляция и очистка процессов. Добавить варианты ACL для анонимного и служебного поиска, группы memberOf и изменение членства.
Для двух LDAP-серверов нужны разные loopback-адреса с одним портом: у ldapserver общий ldapport. Рестарт сохраняет endpoint. Нынешний helper выдаёт разные порты на одном адресе и при рестарте меняет их.
Экспортировать события slapd: соединение, bind, search, результат и DN, без паролей. Счётчики снимать после проверки готовности; служебные запросы самого теста не учитывать. Для ожидания и обрыва добавить управляемый посредник: операция получена → ответ удерживается → ответ разрешён либо соединение закрыто.
Для native LDAPS/StartTLS доверенный CA передавать только процессу PgDoorman через окружение теста. Проверять также посторонний CA и неверное имя в сертификате. Доверие на машине разработчика не менять.
Startup-ошибки читать как ErrorResponse с SQLSTATE; panic клиента или любое закрытие сокета не считать доказательством ожидаемого отказа.
Backend проверять SQL-запросом: session_user, current_user, current_setting, PID. Для доказательства повторного использования — pool_size: 1; для LDAP — connections: 1 и идентификатор соединения slapd. Ошибка входа дополнительно проверяется отсутствием выполнения клиентского SQL.
Синхронизация — по ответам протокола, событиям LDAP, состоянию очереди и наблюдаемому состоянию PostgreSQL с предельным временем ожидания. Барьер незавершённого setup можно построить на advisory lock; достигнутую стадию подтверждать через отдельное соединение наблюдателя. Счётчик попыток до SQL-ошибки — sequence, чтобы rollback не стёр свидетельство выполнения.
Точные границы TTL, min_interval и бюджетов времени проверять компонентными тестами с управляемыми часами. В BDD попадание в кэш проверять с большим TTL; истечение — ожиданием изменения наблюдаемого результата с deadline, без sleep(TTL + запас). В тестах кэша отключать остальные ограничения, если проверяется не их взаимодействие.
9.2 Вход и выбор метода
ID
Сценарий
Наблюдаемая проверка
T-1
Simple bind и search+bind; служебный и анонимный поиск
Клиент получает AuthenticationCleartextPassword, входит и выполняет SQL под ожидаемой ролью. LDAP подтверждает bind нужного DN. Поддерживаемые HBA-строки проверяются также прямым входом в эталонный PostgreSQL.
T-6
Пустой пароль
28P01; пользовательский bind и клиентский SQL не выполняются.
T-7
Неверный пароль, отсутствующая или неуникальная запись, неверный пароль служебной учётки
28000 и соответствующая стадия в логе; исправные альтернативные HBA-строка и второй каталог не превращают отказ в успешный вход.
T-23
Поиск по умолчанию uid, явный атрибут, фильтр с несколькими $username, фильтр без подстановки, эквивалентный ldapurl
На специально различающихся записях найден ожидаемый DN. Поведение совпадает с PostgreSQL; 0 или несколько результатов не допускают вход. Для URL проверить base, one, sub на разных уровнях LDIF.
T-26
Первая HBA-строка — LDAP; ниже разрешающие SCRAM/trust, настроен auth_query; обратный порядок строк
Отказ LDAP не вызывает другой метод. При первой подходящей строке SCRAM/trust LDAP не вызывается. Счётчики LDAP и auth_query подтверждают выбранный путь.
T-27
Нет блока ldap; пустой блок; mapping отсутствует
Без блока — 28000. С пустым блоком существующая одноимённая запись users работает; отсутствующая запись даёт отказ без создания динамического пула.
T-28
LDAP, LDAPS, StartTLS; для TLS — неверный CA, имя сертификата, отказ StartTLS
На каждом поддерживаемом транспорте вход успешен. При ошибке TLS — отказ, без пользовательского bind по незащищённому соединению. Проверяется PgDoorman, а не только LDAP CLI.
T-13
Запрещённые символы в search-логине; отдельно логин с кавычкой при {{username}} в setup
28000, стадия name либо template; небезопасный фильтр или SQL не отправляется.
T-16
В записи нет password, выбран SCRAM, auth_query отсутствует
Отказ; отсутствие локального пароля не превращается в trust или LDAP.
9.3 Mapping и реквизиты PostgreSQL
ID
Сценарий
Наблюдаемая проверка
T-2
Пользователь состоит в двух группах; меняем порядок правил и отдельно порядок значений атрибута
Роль PostgreSQL определяется первым совпавшим правилом; порядок значений от LDAP результата не меняет.
T-3
Совпадают персональное и групповое правила, в обоих порядках
Выбрано первое правило, без скрытого приоритета персональной записи.
T-4
При заданном mapping совпадения нет, но одноимённая запись users существует
28000; возврата к собственному имени нет.
T-29
Группы в search+bind и simple bind; memberOf и явно выбранный атрибут; только вложенное членство
Прямое членство выбирает ожидаемую роль. PgDoorman сам не разворачивает вложенные группы.
T-30
Ошибка LDAP-операции чтения групп при наличии следующего разрешающего правила
Отказ на стадии groups; следующее правило не разрешает вход. Fixture должна давать ошибку операции, а не просто скрывать атрибут через ACL.
T-34
Пароли LDAP, frontend SCRAM и PostgreSQL различаются; server_username задан или отсутствует
Вход LDAP и SQL успешны только с правильным server_password; session_user соответствует явной backend-роли либо имени целевой записи.
T-35
server_password отсутствует; PostgreSQL использует trust либо требует пароль, совпадающий с LDAP-паролем
Trust работает. Парольная аутентификация PostgreSQL отклоняется: LDAP-пароль и frontend-хеш не используются как запасные реквизиты.
T-14
SCRAM- и LDAP-клиенты используют одну запись users; отдельный клиент входит через auth_query
Все разрешённые методы работают с ожидаемыми ролями и setup; LDAP не подменяет остальные пути.
9.4 Кэш
ID
Сценарий
Наблюдаемая проверка
T-10
Повторный успешный вход при cache_ttl: 0 и большом TTL
В первом случае есть новый пользовательский bind, во втором — попадание в кэш без bind. Роль и setup остаются правильными.
T-11
После успешного входа меняем пароль; отдельно членство в группе
При TTL=0 следующий вход отражает изменение. С положительным TTL старый пароль/выбранная роль действуют до истечения; затем результат обновляется. Новый пароль имеет отдельную запись кэша.
T-22
Одинаковые логин и пароль в разных пулах; отдельно две HBA-строки одного пула, выбирающие разные каталоги по адресу клиента
Каталоги содержат разные группы; в отдельном варианте пароль верен только в одном. Успех, отказ и роль не переносятся между ключами. Для обеих HBA-строк действительно используются разные адреса клиента, без RELOAD между входами.
T-31
Повтор неверного пароля при включённом кэше отказов и min_interval: 0; затем другой пароль и истечение TTL отказа
Повтор не вызывает bind; другой пароль проверяется независимо; после истечения прежний пароль снова проверяется в каталоге.
T-32
Кэш отказов выключен; после отказа повтор того же логина с другим паролем раньше min_interval; другой логин
Первый повтор отклоняется без bind, другой логин проходит проверку. Точная граница интервала проверяется компонентным тестом.
T-33
После успешного клиента остаётся свободный backend; следующий вход с неверным паролем, кэш успехов выключен
LDAP отклоняет вход независимо от наличия backend; клиентский SQL не выполняется.
9.5 Соединения с каталогом и отказы
ID
Сценарий
Наблюдаемая проверка
T-8
Первый endpoint отказывает в соединении либо удерживает ответ LDAP-операции
Вход обслуживает второй сервер. Для закрытого endpoint подтверждение — ошибка connect в логе PgDoorman и bind на втором сервере; для удержанного ответа — события обоих серверов. Точные бюджеты проверяются компонентно, без требования уложиться ровно в 2×timeout.
T-9
connections: 1, первый вход удерживается барьером, второй ждёт; затем барьер снимается
После роста gauge ожидающих LDAP-входов до 1 (F-OBS-2) второй ещё не выполняет LDAP-операцию. После освобождения оба завершаются; одновременно занят не более одного LDAP-сеанса. Уже открытый обычный клиент продолжает выполнять SQL.
T-21
Первый LDAP-сервер восстановлен на прежнем endpoint, но соединение со вторым здорово; connections: 1
Следующий вход использует прежний connection ID второго сервера; первый не получает новых попыток.
T-36
Два последовательных пользователя, затем обрыв переиспользуемого соединения; кэш успехов выключен
До обрыва один connection ID; перед новым search — служебный/анонимный bind, при simple bind — bind нового пользователя. После обрыва старое соединение не используется; новый вход либо проходит полный bind, либо получает отказ.
T-37
Каталог возвращает referral на другой контролируемый endpoint
PgDoorman не подключается к адресату и не передаёт ему пароль; ссылка не позволяет обойти поиск и bind выбранного каталога.
Ожидание очереди и операция LDAP имеют одинаковый timeout. Поэтому один зависший bind не доказывает timeout очереди: он сам освободит слот. Точную границу очереди проверять с управляемым временем; BDD T-9 доказывает ограничение параллелизма и освобождение слота.
9.6 Setup и изоляция backend
ID
Сценарий
Наблюдаемая проверка
T-12
Alice и bob по очереди используют одну общую запись с pool_size: 1; режимы session и transaction
При том же PID app.ldap_user всегда соответствует текущему клиенту. В session setup выполняется один раз на закрепление, в transaction — на каждую выдачу; счётчик не растёт от каждого SQL внутри одной транзакции.
T-19
Setup изменил состояние, затем получил SQL-ошибку; отдельно клиент отключился, пока setup удерживался барьером
Клиент получает ошибку либо уже отключён; его SQL не выполнен. Наблюдатель подтверждает закрытие backend; следующий клиент не получает его PID или частичное состояние.
T-20
Разные setup на уровнях general, pool, user; у пользователя отсутствие, null, ""
SQL показывает приоритет user → pool → general; отсутствие/null наследуют, пустая строка отключает.
T-25
Setup выдаёт NOTICE, включая 0A000 и 0AM01, затем завершается успешно
SQL клиента выполняется; NOTICE не становится ошибкой. При повторной выдаче backend сохраняет PID.
T-38
Setup удерживается барьером, клиентский запрос уже отправлен
После подтверждения ожидания setup маркер клиентского запроса не изменён. После снятия барьера запрос видит результат полного setup.
T-39
Пользовательские setup/cleanup с {{username}} и {{group}}; после группового LDAP-клиента — персональный либо SCRAM-клиент
Подстановки относятся к нужному клиенту и правилу. Cleanup использует личность уходящего клиента; у клиента без группового правила {{group}} пуст.
T-40
Setup вызывает set_config; adaptive без custom SQL, custom cleanup на уровне user, режим off
При том же PID встроенная очистка сбрасывает параметр, который следующий setup не перезаписывает. Custom SQL имеет приоритет user → pool → general; его выполнение подтверждает счётчик, а отсутствие встроенного RESET ALL — сохранённый GUC-маркер, заранее установленный и не изменяемый custom SQL. При off счётчик cleanup не меняется.
T-41
Setup возвращает строки и CommandComplete; cleanup после успешного COMMIT завершается ошибкой
Клиент получает только ответы своего SQL, включая успешный COMMIT. Данные видны наблюдателю; backend с ошибкой cleanup закрыт. Переиспользовать существующий cleanup-сценарий, добавив setup и LDAP-клиента.
9.7 Валидация и RELOAD
ID
Сценарий
Наблюдаемая проверка
T-5
Дубликат селектора, неизвестная цель mapping, оба селектора в правиле; неизвестный шаблон хука
Ошибка конфигурации указывает проблемное поле; конфигурация не применяется. Полную таблицу значений проверять unit-тестами.
Запуск отклонён. Тексты ошибок PostgreSQL проверяются для перечисленных в CFG-HBA-2 случаев; дополнительные ограничения — отдельно.
T-24
Ошибочная конфигурация при запуске и RELOAD
При запуске процесс завершается с ошибкой; при RELOAD работающий клиент и новый вход продолжают использовать прежнюю конфигурацию.
T-15
RELOAD меняет порядок mapping, HBA, служебный пароль LDAP или server_password
Новые входы используют новые настройки; уже открытая сессия сохраняет прежнюю роль и работоспособность. Барьер применения — ответ RELOAD и наблюдаемая активная конфигурация, без sleep.
T-42
Перед RELOAD заполнены положительный и отрицательный кэши, затем изменены пароль/доступ
После RELOAD первый вход снова проверяется в LDAP и отражает актуальное состояние.
T-43
Положительный TTL включён, кэш отказов и min_interval выключены; удерживаем успешный BindResponse, меняем пароль, выполняем RELOAD и освобождаем ответ
Старый вход завершается по старому снимку. Его результат не попадает в новый кэш: новый вход со старым паролем делает bind и получает отказ; с новым паролем — успешен.
T-44
RELOAD уменьшает connections, пока старый вход удерживается барьером
Новый лимит действует на новые входы; старый вход завершается по своему снимку. Временное превышение общего числа соединений допускается по F-LOAD-5.
9.8 Драйверы и диагностика
ID
Сценарий
Наблюдаемая проверка
T-18
libpq/psql, pgx, JDBC, psycopg: вход, неверный пароль, повторное использование backend
Каждый драйвер выполняет SQL после LDAP-входа и получает ожидаемый отказ. Один подробный сценарий Simple/Extended protocol проверяет порядок сообщений, скрытые ответы хуков и повторный Bind подготовленного запроса после cleanup.
T-45
Разные секреты-маркеры в LDAP-пароле, ldapbindpasswd, server_password; успешные и неуспешные входы
В логах PgDoorman и SHOW CONFIG секретов нет. Проверять отсутствие конкретных маркеров без печати их в диагностике теста.
T-46
Клиент перенаправлен в общую запись; успешный вход, отказ, попадание в кэш и ошибка setup
SHOW CLIENTS показывает логин и цель, SHOW POOLS — целевой пул. Дельты метрик соответствуют событиям; причина отказа не теряется и не дублируется обёртками.
9.9 Выполнение
Все новые native LDAP-сценарии входят в существующую группу @ldap; дополнительный тег @ldap-native позволяет запускать только их. Базовый стенд и образ переиспользуются, PgDoorman собирается один раз на набор. Проверки драйверов вызываются из этого же набора без дублирования сценариев в нескольких CI-группах.
Текущий Nix-стенд содержит PostgreSQL 16. Для прямой сверки с референсом PostgreSQL 18 в T-1/T-23 нужен отдельный тестовый PostgreSQL 18 с LDAP-поддержкой; это зависимость плана, сейчас такого прогона нет. Входы по LDAP, LDAPS и StartTLS проверяются на настоящем LDAP. Вспомогательный управляемый сервер/посредник нужен только для воспроизводимых задержек, обрывов и ошибок.
Существующие наборы SCRAM/MD5, PAM, auth_query, cleanup и Greengage остаются регрессионными проверками. Для Greengage добавить узкий сценарий нового setup: установка личности, успешный NOTICE и повторное использование backend; всю LDAP-матрицу на Greengage не повторять.
10. Пример конфигурации
general:
pg_hba:
content: | hostssl all all 10.0.0.0/8 ldap ldapserver="dc1.example.org dc2.example.org" ldapscheme=ldaps ldapbasedn="ou=people,dc=example,dc=org" ldapbinddn="cn=pg-doorman,ou=services,dc=example,dc=org" ldapbindpasswd="<secret>" ldapsearchattribute=sAMAccountNamepools:
reports:
server_host: pg-bi.example.orgserver_database: analyticsusers:
- username: alice # без password: пароль проверяет каталогpool_size: 5# соединений с PostgreSQL для alice
- username: manager # без password; server_username по умолчанию = usernameserver_password: "<pg password of manager>"pool_size: 20setup_server_query: "SELECT set_config('app.ldap_user', '{{username}}', false)"
- username: analyticsserver_password: "<pg password of analytics>"pool_size: 20ldap:
# timeout: 60s # бюджет на каждый LDAP-серверconnections: 2# постоянных соединений с каталогом# cache_ttl: 10m # по умолчанию кэш успешных входов выключенmapping: # сверху вниз, первое совпадение
- user: alice
- group: "cn=manager,ou=groups,dc=example,dc=org"as_user: manager
- group: "cn=analysts,ou=groups,dc=example,dc=org"as_user: analytics
Клиент
Группы
Правило
Роль в PostgreSQL
alice
manager
1
alice
bob
manager, analysts
2 — первое совпадение
manager, app.ldap_user = 'bob'
dave
analysts
3
analytics
erin
—
нет
отказ 28000
Роли PostgreSQL:
CREATE ROLE manager LOGIN PASSWORD '...';
CREATE ROLE analytics LOGIN PASSWORD '...';
CREATE ROLE alice LOGIN;
pg_hba.conf PostgreSQL для адреса PgDoorman:
host analytics manager,analytics 10.0.0.5/32 scram-sha-256
host analytics alice 10.0.0.5/32 trust
11. Вне области первой версии
Регулярные выражения в user с \1 в as_user; селектор по DN и поддереву; несколько групп в одном правиле; флаг case_insensitive; отдельный поиск групп для каталогов без memberOf; вложенные группы силами PgDoorman; динамические пулы для не перечисленных логинов; DNS SRV; пароль служебной учётки из файла; отдельный CA-файл каталога; ленивый режим хуков; setup_server_query у auth_query; SHOW LDAP.