LDAP-сервер для бедных

17 января 2024

Плох тот системный администратор, которому не приходится задумываться о централизованной авторизации.

Многие сервисы её поддерживают, и это хорошо.

Но почти все сервисы, которые её поддерживают, знают только про один протокол — LDAP.

Проблема LDAP заключается в том, что почти все существующие серверы разработаны с расчётом на интенсивную нагрузку и сложные конфигурации.

Например, в презентации для ReOpenLDAP заявлены следующие цифры:

  • 4 мастер-узла в кластере с полносвязной репликацией (full-mesh multi-master),
  • 10К операций записи и 50K операций чтения в секунду,
  • 100M записей, размер базы 100Gb.

Сложные задачи требуют сложного ПО. Сложное ПО повышает:

  • время- и мыслезатраты для администратора,
  • требования к ресурсам для сервера.

Подавляющему большинству инсталляций от LDAP-сервера не требуется подобных рекордов. Чтобы обслуживать Redmine, Gitlab, Zabbix, Grafana, Jira, Jenkins, Dovecot, pure-ftpd, OTRS, OpenVPN или SSSD, достаточно гораздо более скромного функционала.

На наш взгляд, идеальный сферический LDAP-сервер в вакууме, необходимый и достаточный для 99% ситуаций, примерно таков:

  • быстро настраивается с нуля в несколько типовых шагов, без миллиона команд и ключей,
  • не требует для работы JVM, RVM, NJS, .Net, NoSQL, K8S и прочих шедевров комбайностроения,
  • обслуживает только запросы на чтение — запись не нужна, конфигурация фиксирована,
  • содержит все настройки в текстовом файле, который легко редактировать вручную, делать резервные копии (в т.ч. в Git), генерировать через Ansible/Chef/Rexify и т.д.
  • кроме пользователей и групп, в настройках вряд ли потребуется хранить что-то ещё,
  • для пользователей, в дополнение к логину-паролю-группе — полезно иметь возможность сохранять UID/GID, SSH-ключи, Email, Fullname и т.д.
  • разумеется, пароли обязаны быть надёжно зашифрованы,
  • репликация не нужна — если требуется отказоустойчивость, создаются дополнительные серверы с идентичными настройками,
  • изменение настроек на лету необязательно — достаточно быстрой перезагрузки.

И такой сервер появился 5 лет назад. Он называется GLAuth.

Первоначально GLAuth был написан для упрощения отладки приложений, получающих данные из LDAP, чтобы разработчикам не приходилось настраивать в качестве сервера избыточно тяжеловесный 389DS, OpenLDAP, ApacheDS, OpenDJ или Active Directory LDS от Microsoft.

Однако общественный запрос постепенно привёл в появлению продвинутых функций:

  • 2FA
  • App passwords
  • backend plugins для SQLite, MySQL и Postgresql

С каждой из них GLAuth становится полезнее не только разработчикам, но и администраторам — поддержка хранения данных в SQL-базе полезна в первую очередь для них.

Установка:

  • Скачиваем файл приложения, делаем исполняемым:
  • wget  -O /usr/local/sbin/glauth https://github.com/glauth/glauth/releases/download/v2.3.0/glauth-linux-amd64
    chmod +x /usr/local/sbin/glauth
  • Создаём файл настроек /etc/glauth.ini (см.ниже)
  • Создаём SSL-ключ и сертификат:
  • openssl req -x509 -newkey rsa:4096 -keyout /etc/glauth.key -out /etc/glauth.crt -days 9999 -nodes -subj "/CN=$(hostname)"
  • Создаём псевдопользователя для запуска сервиса и ограничиваем доступ к настройкам:
  • useradd -c glauth_service -d /dev/null -s /bin/true -r glauth
    chgrp glauth /etc/glauth.*
    chmod 440    /etc/glauth.*
  • Создаём файл управления сервисом /etc/systemd/system/glauth.service:
  • [Unit]
    Description=Lightweight LDAP daemon
    Documentation=https://cdnnow.ru/blog/glauth
    After=network-online.target
    
    [Service]
    User=glauth
    WorkingDirectory=/etc
    ExecStart=/usr/local/sbin/glauth -c glauth.ini
    Restart=on-failure
    RestartSec=5s
    AmbientCapabilities=CAP_NET_BIND_SERVICE
    
    [Install]
    WantedBy=multi-user.target
  • Запускаем и проверяем успешность запуска:
  • systemctl daemon-reload
    systemctl enable --now glauth
    systemctl status glauth
    journalctl -xeu glauth
    ss -ntlp | grep glauth
  • В случае успеха проверяем подключение:
  • SERV="127.0.0.1"
    NAME="bill"
    BASE="ou=people,ou=users,dc=cdnnow,dc=ru"
    FULL="uid=$NAME,$BASE"
    PASS="xxx"
    
    ldapsearch -h "$SERV" -D "$FULL" -w "$PASS" -b "$BASE" -LLL "(uid=$NAME)" dn

Пример настроек в /etc/glauth.ini:

[ldap]
  enabled       = true
  listen        = "127.0.0.1:389"

[ldaps]
  enabled       = true
  listen        = "127.0.0.1:636"
  cert          = "glauth.crt"
  key           = "glauth.key"

[backend]
  datastore     = "config"
  baseDN        = "dc=cdnnow,dc=ru"
  nameformat    = "uid"
  groupformat   = "ou"

[api]
  enabled       = true
  internals     = true
  tls           = true
  listen        = "127.0.0.1:5555"
  cert          = "glauth.crt"
  key           = "glauth.key"

[[groups]]
  name          = "admins"
  gidnumber     = 9000
[[groups]]
  name          = "people"
  gidnumber     = 9001
[[groups]]
  name          = "services"
  gidnumber     = 9002

[[users]]
  name          = "vasya"
  uidnumber     = 8001
  primarygroup  = 9000
  passbcrypt    = "24326224313224484c72684f316d59663666775932304e77582f6e786579566b4c62563948674e4b42463438447771546b2e7944784f6d4f464f7161"
  [[users.capabilities]]
    action      = "search"
    object      = "*"

[[users]]
  name          = "gitlab"
  uidnumber     = 8002
  primarygroup  = 9002
  passbcrypt    = "2432622431322451697937485845592e5431712e4963486c343941307531324c505252394a7836417378535473326f5941583045774a6a7933696d75"
  [[users.capabilities]]
    action      = "search"
    object      = "*"

Пояснения к настройкам:

  • Раздел "api" является необязательным. Любопытно посмотреть на подробности работы в тестовом режиме, но в постоянном его лучше отключить (enabled = false), т.к. доступ не защищён логином-паролем.
  • Раздел "ldaps" является необязательным. Его можно отключить, если всем сервисам хватает обычного LDAP.
  • Для разделов api и ldaps используются самоподписанные сертификаты, но лучше использовать полноценные — например, привязанные к имени сервера и запрашиваемые через LetsEncrypt.
  • BaseDN, имена и номера групп и пользователей придуманы почти случайным образом.
  • Пароли хэшированы алгоритмом bcrypt с солью (salt), хэш хранится в кодировке Base16.

Утилита генерация паролей для GLAuth:

#!/usr/bin/python3

import string, os
import random
import bcrypt

passlen  = 30
letters  = string.ascii_letters
plain    = os.getenv("PLAIN_PASSWORD", "".join(random.choice(letters) for i in range(passlen)))
crypted  = bcrypt.hashpw(plain.encode("utf-8"), bcrypt.gensalt())
hexcrypt = "".join("{:02x}".format(c) for c in crypted)

print(plain)
print(crypted.decode("utf-8"))
print(hexcrypt)

Пояснения:

  • перед запуском установите модуль bcrypt — в Debian/Ubuntu/Mint это делается командой apt install python3-bcrypt, в остальных с помощью pip3 install -U bcrypt
  • исходный пароль читается из переменной окружения PLAIN_PASSWORD, при её отсутствии генерируется случайным образом,
  • выводимый на консоль результат содержит пароль в трёх формах — незашифрованной, хешированной в Salted Bcrypt, хэшированной с последующим преобразованием из ASCII в Base16,
  • пароль в форме Base16 следует сохранить в glauth.ini,
  • незашифрованный пароль следует записать на бумажку и приклеить на монитор запомнить и указывать при входе в использующие GLAuth сервисы,
  • пользователь vasya и сервис gitlab в приведённом выше примере glauth.ini имеют одинаковый пароль "hello", который превратился в разные хэши благодаря добавлению разной случайно сгенерированной "соли" (salt),
  • если вы администрируете GLAuth, требуйте от новых пользователей, чтобы они самостоятельно создавали себе пароли и присылали вам только полученный хэш, дабы знание чужого пароля, пусть даже мимолётное, не вводило вас в обременение и\или искушение,
  • алгоритм bcrypt имеет разные версии — glauth не понимает слишком старые, поэтому если наш суперкостыль genbcrypt.py недостаточно хорош для вас, замену ему следует выбирать с осторожностью,
  • быстрая проверка пригодности хэша для GLAuth — ASCII-форма должна начинаться с символов "$2b$12$", с другими вариантами совместимость не гарантирована.

На этом заявленную в заголовке тему можно считать исчерпанной.



← Назад в Блог

Подпишитесь на новые статьи: