Плох тот системный администратор, которому не приходится задумываться о централизованной авторизации.
Многие сервисы её поддерживают, и это хорошо.
Но почти все сервисы, которые её поддерживают, знают только про один протокол — 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
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.*
[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$
", с другими вариантами совместимость не гарантирована.
На этом заявленную в заголовке тему можно считать исчерпанной.