Введение
В данной статье описаны принцип работы и настройка basic auth в Nginx через Samba Active Directory.
Применение и принцип работы
Необходимость такого решения возникла в результате закрытия всех внутренних ресурсов от посторонних глаз. Но на каждый проект заводить отдельные .htpasswd файлы или делать один общий, а потом где-то хранить логин и пароль не очень хотелось – со временем проектов станет много и администрировать учётки станет не очень удобно. Тут-то на помощь и пришёл вариант использования Nginx с имеющейся Samba в качестве контроллера домена.
Для Nginx существует модуль ngx_http_auth_request_module.
Модуль
ngx_http_auth_request_module
(1.5.4+) предоставляет возможность авторизации клиента, основанной на результате подзапроса. Если подзапрос возвращает код ответа 2xx, доступ разрешается. Если 401 или 403 — доступ запрещается с соответствующим кодом ошибки. Любой другой код ответа, возвращаемый подзапросом, считается ошибкой.
То есть работа модуля основана на подзапросах к какому-либо стороннему сервису, который обращается к Active Directory, и возвращается к Nginx с ответом. Таковым сервисом является демон, написанный на Python, и называется ldap‑auth daemon. Поддерживает работу Python 2 и 3 версии.
На сайте Nginx изображена схема работы при использовании модуля авторизации и коннектора на Python:
Необходимые зависимости
- Модуль ngx_http_auth_request_module, согласно документации, не включен в сборку по умолчанию, но в пакетах из репозитория Nginx данный модуль присутствует. Для проверки необходимо вывести список всех модулей Nginx:
nginx -V 2>&1 | tr ' ' '\n' | grep with-http_auth_request_module
- Для работы демона ldap‑auth daemon необходима библиотека python-ldap – установить её можно через пакетный менеджер python – pip. Но для этого потребуется установить на сервер следующие зависимости:
- компилятор языка C
- непосредственно python3
- openldap-devel
- Помимо этого пригодится также пакет openldap-clients для тестирования подключения к LDAP-серверу в дальнейшем. Итого на сервер, где расположен Nginx, необходимо установить:
yum install python3 gcc python3-devel openldap-devel
Создание пользователя Active Directory
Для возможности обращения в AD с помощью ldap‑auth daemon необходима сервисная учётная запись с правами на чтение дерева.
- Для создания в Microsoft AD или Samba можно воспользоваться оснасткой dsa.msc. Или же через консоль сервера в случае с Samba:
samba-tool user add nginx
- Также необходимо отключить срок действия учетной записи:
samba-tool user setexpiry nginx --noexpiry
Проверка связанности с AD
- Рекомендуется проверить напрямую через утилиту ldapsearch, есть ли возможность подключиться к AD с помощью созданной УЗ. Команда ниже выведет объекты дерева:
ldapsearch -v -D "nginx@domain.local" -w "PASSWORD" -b "DC=domain,DC=local" -H "ldap://10.10.4.30" | head -n 40
Если ошибок не возникло, можно переходить к установке демона.
Установка nginx-ldap-auth bare metal
- Демон потребляет мало ресурсов, поэтому его допустимо установить на тот же сервер, где запущен Nginx:
curl -o /usr/bin/nginx-ldap-auth-daemon.py https://raw.githubusercontent.com/nginxinc/nginx-ldap-auth/master/nginx-ldap-auth-daemon.py
chmod +x /usr/bin/nginx-ldap-auth-daemon.py
- Выполнить пробный запуск, задав адрес и порт:
/usr/bin/python3 /usr/bin/nginx-ldap-auth-daemon.py --host 127.0.0.1 --port 8080
- Обратиться к демону через curl. В логах должно появиться что-то подобное:
curl localhost:8080
Start listening on 127.0.0.1:8080...
127.0.0.1 - - [02/Apr/2021 17:14:08] using username/password from authorization header
127.0.0.1 - - [02/Apr/2021 17:14:08] "GET / HTTP/1.1" 401 -
- Убедившись, что запуск выполняется корректно, для удобного последующего запуска логично будет создать systemd-юнит:
cat > /etc/systemd/system/nginx-ldap-auth.service << EOF
[Unit]
Description=LDAP authentication helper for Nginx
After=network.target network-online.target
[Service]
Type=simple
User=nginx-ldap-auth
Group=nginx-ldap-auth
WorkingDirectory=/var/run
ExecStart=/usr/bin/python3 /usr/bin/nginx-ldap-auth-daemon.py --host 127.0.0.1 --port 8080
KillMode=process
KillSignal=SIGINT
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
Стоит обратить внимание – в конфиге юнита указан отдельный пользователь nginx-ldap-auth, от имени которого будет запускаться демон. Необходимо создать данного пользователя:
adduser nginx-ldap-auth --no-create-home --shell=/bin/false
- Выполнить запуск демона через systemd:
systemctl daemon-reload && systemctl start nginx-ldap-auth && systemctl enable nginx-ldap-auth
- Проверить логи:
journalctl -u nginx-ldap-auth.service -f
Запуск в Docker
На гитхабе представлен Dockerfile для сборки Docker-образа nginx-ldap-auth. Я немного его изменил, добавив:
- non-root пользователя для запуска приложения в контейнере
- временную зону Europe/Moscow
- 3 версию питона по умолчанию в entrypoint
- аргументы CMD
ARG PYTHON_VERSION=3.9
FROM python:${PYTHON_VERSION}-alpine
RUN \
addgroup -S nginx-ldap-auth && \
adduser -S nginx-ldap-auth -G nginx-ldap-auth && \
cp -r /usr/share/zoneinfo/Europe/Moscow /etc/localtime && echo Europe/Moscow > /etc/timezone && \
apk --no-cache add openldap-dev && \
apk --no-cache add --virtual build-dependencies build-base && \
pip3 install python-ldap && \
apk del build-dependencies
COPY nginx-ldap-auth-daemon.py /usr/src/app/
WORKDIR /usr/src/app/
EXPOSE 8888
USER nginx-ldap-auth
ENTRYPOINT ["python3", "/usr/src/app/nginx-ldap-auth-daemon.py"]
CMD ["--host", "0.0.0.0", "--port", "8888"]
Таким образом, можно выполнять запуск командой ниже:
docker build -t nginx-ldap-auth .
docker run --name nginx-ldap-auth -p 8888:8888 -d nginx-ldap-auth
А при необходимости запуска на порту, отличном от дефолтного 8888, можно изменить аргументы CMD при старте:
docker run --name nginx-ldap-auth -p 8888:8887 -d nginx-ldap-auth --host 0.0.0.0 --port 8887
Используемые заголовки
Демон nginx-ldap-auth успешно запущен, но перед дальнейшей настройкой непосредственно самого Nginx, необходимо убедиться, что запросы корректно приходят в Active Directory от nginx-ldap-auth. Для этого понадобится curl и специальные HTTP-заголовки, в которых будет передаваться вся необходимая информация для подключения к LDAP.
Параметры LDAP | HTTP Header | Описание |
---|---|---|
basedn | X-Ldap-BaseDN | База поиска. В большинстве случаев соответствует суффиксу каталога. Если необходимо просто авторизовать пользователя, то не нужно указывать дополнительные группы, в которые этот пользователь входит (для этого используется template). Например, dc=domain,dc=local или cn=Users,dc=domain,dc=local |
binddn | X-Ldap-BindDN | Для выполнения операции поиска в каталоге используется BIND DN, в данном параметре указывается непосредственно уникальное имя пользователя каталога. Например, cn=root,dc=domain,dc=local |
bindpasswd | X-Ldap-BindPass | Пароль пользователя |
cookiename | X-CookieName | Авторизация на основе файлов кук, необязательный параметр. При использовании basic auth игнорируется. |
realm | X-Ldap-Realm | Имя realm, необязательный параметр, используется по умолчанию значение Restricted. |
template | X-Ldap-Template | Шаблон, по которому будет происходить выборка. Можно настраивать различные конфигурации. Например, для OpenLDAP подойдет такой шаблон: (cn=%(username)s), а для AD – (SAMAccountName=%(username)s) – это базовый шаблон, который просто выполняет поиск пользователя по каталогу. |
url | X-Ldap-URL | Адрес подключения к LDAP-серверу. Например, ldap://10.10.4.30:389 или ldaps://10.10.4.30:636 |
Проверка работы
Определившись с заголовками, можно выполнить проверку через curl, передав минимальные параметры: адрес AD, наименование домена, логин\пароль и простой шаблон:
curl --location --request GET 'http://localhost:8080' \
--header 'X-Ldap-URL: ldap://10.10.4.30' \
--header 'X-Ldap-BaseDN: DC=domain,DC=local' \
--header 'X-Ldap-BindDN: nginx@domain.local' \
--header 'X-Ldap-BindPass: STRONG_PASS' \
--header 'X-Ldap-Template: (SAMAccountName=%(username)s)' -vv -u nginx:STRONG_PASS
- В ответ должен поступить 200 код, т.е. подключение успешно устанавливается от имени созданного пользователя:
* About to connect() to localhost port 8080 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'nginx'
> GET / HTTP/1.1
> Authorization: Basic efVnsdfuZ2DpQaEFhDFsdxM0tMZVRmdWfg0
> User-Agent: curl/7.29.0
> Host: localhost:8080
> Accept: */*
> X-Ldap-URL: ldap://10.10.4.30
> X-Ldap-BaseDN: DC=domain,DC=local
> X-Ldap-BindDN: nginx@domain.local
> X-Ldap-BindPass: STRONG_PASS
> X-Ldap-Template: (SAMAccountName=%(username)s)
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: BaseHTTP/0.6 Python/3.6.8
< Date: Mon, 05 Apr 2021 10:01:42 GMT
<
* Closing connection 0
В случае ошибки вида Error while binding as search user: {‘msgtype’: 97, ‘msgid’: 1, ‘result’: 8, ‘desc’: ‘Strong(er) authentication required‘, ‘ctrls’: [], ‘info’: ‘BindSimple: Transport encryption required.’} необходимо добавить в конфиг smb.conf строку “ldap server require strong auth = no”, но делать так для production не рекомендуется.
Конфигурация Nginx
Наконец, можно выполнять настройку веб-сервера. Для удобства будет создана директория, в которой необходимо расположить конфиг для авторизации через AD – так его будет удобно подключать в необходимый контекст. Ну, или разместить по своему усмотрению:
mkdir -p /etc/nginx/conf.d/nginx-ldap-auth
cat > /etc/nginx/conf.d/nginx-ldap-auth/nginx-ldap-auth.conf << EOF
location = /auth {
internal;
#proxy_cache auth_cache;
proxy_cache_valid 200 10m;
proxy_cache_key "$http_authorization$cookie_nginxauth";
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Ldap-URL "ldap://10.10.4.30:389";
proxy_set_header X-Ldap-BaseDN "DC=domain,DC=local";
proxy_set_header X-Ldap-BindDN "nginx@domain.local";
proxy_set_header X-Ldap-BindPass "STRONG_PASS";
proxy_set_header X-Ldap-Template "(SAMAccountName=%(username)s)";
}
EOF
В файле nginx-ldap-auth.conf описан location, в котором настроено:
- наименование location – в данном случае /auth
- кеширование 200 кода (через пробел можно добавить и другие коды) ответа в течение 10 минут
- проксирование запросов на локальный адрес, где слушает nginx-ldap-auth
- передача HTTP-заголовков для LDAP
Если раскомментировать директиву “proxy_cache auth_cache”, то необходимо в nginx.conf в контексте http указать директиву proxy_cache_path cache/ keys_zone=auth_cache:10m; – она задаёт путь и другие параметры кэша. Данные кэша хранятся в файлах.
Поскольку тело запроса отбрасывается для подзапросов аутентификации, необходимо отключить директиву “proxy_pass_request_body”, а также установить для заголовка Content-Length пустую строку
- И в завершение осталось подключить в нужном контексте “server” location для авторизации через Active Directory – выделено жирным:
server {
server_name example.com
location / {
auth_request /auth;
proxy_pass http://10.16.0.14:81;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
autoindex on;
}
include /etc/nginx/conf.d/nginx-ldap-auth/nginx-ldap-auth.conf;
}
}
В вышеописанном конфигурационном файле к обычному “location /” добавляется “auth_request /auth;” и после подключается ранее сформированный файл nginx-ldap-auth.conf. Далее остаётся проверить синтаксис и выполнить релоад Nginx:
nginx -t && nginx -s reload
Исключение для basic auth
Удобным может тот случай, когда для доверенных адресов какую-либо авторизацию на веб-сервере можно вообще убрать – на помощь приходит директива “satisfy any”:
location / {
satisfy any;
allow 10.10.1.0/24;
auth_request /auth;
proxy_pass http://10.16.0.14:81;
Если пользователь пришёл с адреса из подсети 10.10.1.0/24, то satisfy разрешает доступ, если все (all
) или хотя бы один (any
) из модулей ngx_http_access_module, ngx_http_auth_basic_module, ngx_http_auth_request_module или ngx_http_auth_jwt_module разрешают доступ и в таком случае пароль вводить не понадобится.
Удобно сделать white-лист и подключать его через include:
location / {
satisfy any;
include /etc/nginx/conf.d/whitelist_ip.conf;
auth_request /auth;
proxy_pass http://10.16.0.14:81;
}
Заключение
По сути вся настройка сводится к тому, что команда Nginx предоставила готовое решение и описала общие принципы настройки, что очень удобно. К тому же, для большего удобства предоставлены Dockerfile для сборки образов и запуска nginx-ldap-auth в контейнере.
Из минусов мне видится два и причём весьма существенных:
- если пропадёт сетевая связанность до сервера с AD или сам сервер падёт смертью храбрых, то клиент на Nginx не сможет авторизоваться и в ответ получит 500 код. Для серьезных решений это может быть критичным моментом и стоит это учитывать. Но в теории Nginx сейчас крайне функционален и наверняка можно обыграть этот нюанс с использованием модуля njs или хитрых конструкций из директив.
- аутентификация в AD происходит по протоколу LDAP, а это по сути большая дыра. В идеале использовать хотя бы NTLM или Kerberos.
Также есть форк, в котором есть возможность использования нескольких LDAP-серверов, что в теории должно нивелировать описанный выше минус. Но на практике не проверял.
Используемые источники
- https://www.nginx.com/blog/nginx-plus-authenticate-users/
- http://nginx.org/en/docs/http/ngx_http_auth_request_module.html
- https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/
- https://winitpro.ru/index.php/2021/03/22/nginx-active-directory-ldap-autentifikaciya/
- https://github.com/kvspb/nginx-auth-ldap
- https://github.com/nginxinc/nginx-ldap-auth
Добрый день,
Правильно понимаю что тут описаны два варианта с Docker и без?
да