В данной статье мы рассмотрим, как настроить basic аутентификацию в Nginx через LDAP каталог. В качестве сервера LDAP будем использовать Windows Active Directory. Так как у Nginx нет официального встроенного модуля авторизации через LDAP (в отличии от Apache или Tomcat), разработчики предлагают использовать сторонний сервис nginx-ldap-auth, который будет обращаться к LDAP и выступать своего рода прокси между Nginx и сервером каталога LDAP ldap сервером. Для начала давайте запустим сервис и посмотрим, как он работает.
Настройка и запуск сервиса аутентификации nginx-ldap-auth
Для запуска сервиса nginx-ldap-auth будем использовать docker. Для начала нужно клонировать репозиторий с сервисом с github:
# sudo git clone https://github.com/nginxinc/nginx-ldap-auth
# cd nginx-ldap-auth
Если у вас не установлен утилиты git, можно просто скачать архив с исходниками и распаковать его в директорию nginx-ldap-auth.
В директории уже имеется готовый Dockerfile. Для сборки контейнера нужно выполнить:
# sudo docker build -t nginx-ldap-auth-daemon .
Вывод при сборке будет таким:
Sending build context to Docker daemon 208.9kB Step 1/7 : ARG PYTHON_VERSION=2 Step 2/7 : FROM python:${PYTHON_VERSION}-alpine ---> 8579e446340f Step 3/7 : COPY nginx-ldap-auth-daemon.py /usr/src/app/ ---> Using cache ---> a0b2c58fd4af Step 4/7 : WORKDIR /usr/src/app/ ---> Using cache ---> 2d93d045af89 Step 5/7 : RUN apk --no-cache add openldap-dev && apk --no-cache add --virtual build-dependencies build-base && pip install python-ldap && apk del build -dependencies ---> Using cache ---> fd942ca28c6e Step 6/7 : EXPOSE 8888 ---> Using cache ---> 0d5b7bab2edf Step 7/7 : CMD ["python", "/usr/src/app/nginx-ldap-auth-daemon.py", "--host", "0.0.0.0", "--port", "8888"] ---> Using cache ---> 3eb60dda0847 Successfully built 3eb60dda0847 Successfully tagged nginx-ldap-auth-daemon:latest
Docker image готов, можно запустить контейнер. Для этого выполните команду:
# sudo docker run -p 8888:8888 --name ldap-auth nginx-ldap-auth-daemon
Вывод данной команды будет таким:
Start listening on 0.0.0.0:8888…
Эта команда запустит контейнер из образа nginx-ldap-auth-daemon, c названием контейнера ldap-auth и пробросит порт 8888 из контейнера на host машину. Проброс портов нужен, чтобы проверить работу и выполнить отладку аутентификации в Active Directory. В дальнейшем, можно исключить этот параметр. Также для отладки мы не добавили ключ -d, который запустил бы контейнер и отвязал бы его от консоли, но тогда не было видно лога работы, который полезен при отладке.
Давайте теперь разберем, как работает сервис nginx-ldap-auth. Данный демон является небольшим web сервисом, к которому можно отправить HTTP запрос методом get с определенными заголовками, он в свою очередь обратится в Active Directory и проверит авторизационные данные. Если они верны, он ответит HTTP кодом 200 ОК, если нет, то вернет 401 код (не авторизован).
Для проверки нужен настроенный сервер Active Directory либо OpenLDAP. В данной статье будет рассмотрен контроллер домена на Windows Server 2008 R2, но в README репозитория демона заявлена поддержка и других LDAP серверов.
- Имя домена: corp.to.high
- IP контроллера домена: 192.168.0.16
Создадим пользователя с правами гость. Назовем его ldap_reader. Данный пользователь нужен, чтобы от его имени демон обращался в AD и проверял переданную при авторизации связку логин и пароль.
Данные пользователя следующие
- Логин: [email protected]
- Пароль: r05-2020
- Группа AD: Гости домена.
Запретите пользователю менять пароль и установите неограниченный срок действия пароля (Never Expires), так как это сервисная УЗ.
Для тестирования можно скачать утилиту для поиска в дереве LDAP, например ldapsearch.
Устанавливается командой:
# sudo apt install ldap-utils
Для проверки доступности контроллера домена и правильной настройки пользователя можно выполнить команду:
# ldapsearch -v -D "[email protected]" -w "r05-2020" -b "DC=corp,DC=to,DC=high" -H "ldap://192.168.0.16" "(sAMAccountName=ldap_reader)"
- -D binddn учетная запись, для доступа к домену,
- -w bindpassword пароль учетной записи,
- -b searchbase – контейнер (OU) AD, в котором нужно искать пользователя (можно сузить область поиска для получения ответа более быстро),
- -H адрес ldap сервера;
- Затем указывается LDAP фильтр, по которому нужно выполнить поиск.
Вывод для нашего домена будет такой:
ldap_initialize( ldap://192.168.0.16:389/??base ) filter: (sAMAccountName=ldap_reader) requesting: All userApplication attributes # extended LDIF # # LDAPv3 # base <DC=corp,DC=to,DC=high> with scope subtree # filter: (sAMAccountName=ldap_reader) # requesting: ALL # # ldap_reader, Users, corp.to.high dn: CN=ldap_reader,CN=Users,DC=corp,DC=to,DC=high objectClass: top objectClass: person objectClass: organizationalPerson objectClass: user cn: ldap_reader givenName: ldap_reader distinguishedName: CN=ldap_reader,CN=Users,DC=corp,DC=to,DC=high instanceType: 4 whenCreated: 20210308212600.0Z whenChanged: 20210308212700.0Z displayName: ldap_reader ………... # search reference ref: ldap://ForestDnsZones.corp.to.high/DC=ForestDnsZones,DC=corp,DC=to,DC=high # search reference ref: ldap://DomainDnsZones.corp.to.high/DC=DomainDnsZones,DC=corp,DC=to,DC=high # search reference ref: ldap://corp.to.high/CN=Configuration,DC=corp,DC=to,DC=high # search result search: 2 result: 0 Success # numResponses: 5 # numEntries: 1 # numReferences: 3
Данный лог показывает, что пользователь найден и у пользователя ldap_reader есть доступ на чтение дерева домена. Проверим теперь работу демона авторизации. Для этого, выполним в консоли команду:
# curl --location --request GET 'http://localhost:8888' \
--header 'X-Ldap-URL: ldap://192.168.0.16' \
--header 'X-Ldap-BaseDN: CN=Users,DC=corp,DC=to,DC=high' \
--header 'X-Ldap-BindDN: [email protected]' \
--header 'X-Ldap-BindPass: r05-2020' \
--header 'X-Ldap-Template: (sAMAccountName=%(username)s)' -vv -u ldap_reader:r05-2020
Вывод будет таким:
Note: Unnecessary use of -X or --request, GET is already inferred. * Rebuilt URL to: http://localhost:8888/ * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 8888 (#0) * Server auth using Basic with user 'ldap_reader' > GET / HTTP/1.1 > Host: localhost:8888 > Authorization: Basic bGRhcF9yZWFkZXI6cjA1LTIwMjA= > User-Agent: curl/7.58.0 > Accept: */* > X-Ldap-URL: ldap://192.168.0.16 > X-Ldap-BaseDN: CN=Users,DC=corp,DC=to,DC=high > X-Ldap-BindDN: [email protected] > X-Ldap-BindPass: r05-2020 > X-Ldap-Template: (sAMAccountName=%(username)s) > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Server: BaseHTTP/0.3 Python/2.7.18 < Date: Sun, 14 Mar 2021 19:07:48 GMT < * Closing connection 0
Заголовки, используемые в запросе:
- X-Ldap-URL — адрес ldap сервера, такой же, как при тестировании с помощью утилиты ldapsearch;
- X-Ldap-BaseDN — начальный DN для поиска;
- X-Ldap-BindDN — учетная запись с правами на чтение дерева домена.
- X-Ldap-BindPass – пароль пользователя для доступа к AD;
- X-Ldap-Template — в данном заголовке передается LDAP фильтр, по которому будет происходить поиск. В заголовке можно комбинировать разные фильтры. Подробнее можно посмотреть в документации к демону nginx-ldap-auth.
- Опцией -u <login>:<password> нужно передать креденшелы пользователя, которые нужно проверить в домене
Если передать неверные учетные данные, будет возвращена ошибка 401 ( не авторизован). Попробуем передать неверный пароль, вывод будет такой:
* Rebuilt URL to: http://localhost:8888/ * Trying 127.0.0.1... * TCP_NODELAY set * Connected to localhost (127.0.0.1) port 8888 (#0) * Server auth using Basic with user 'ldap_reader' > GET / HTTP/1.1 > Host: localhost:8888 > Authorization: Basic bGRhcF9yZWFkZXI6cjA1LTIwMjAx > User-Agent: curl/7.58.0 > Accept: */* > X-Ldap-URL: ldap://192.168.0.16 > X-Ldap-BaseDN: CN=Users,DC=corp,DC=to,DC=high > X-Ldap-BindDN: [email protected] > X-Ldap-BindPass: r05-2020 > X-Ldap-Template: (sAMAccountName=%(username)s) > * HTTP 1.0, assume close after body < HTTP/1.0 401 Unauthorized < Server: BaseHTTP/0.3 Python/2.7.18 < Date: Sun, 14 Mar 2021 19:16:40 GMT * Authentication problem. Ignoring this. < WWW-Authenticate: Basic realm="Restricted" < Cache-Control: no-cache < * Closing connection 0
Также в логах демона можно увидеть вывод:
172.17.0.1 - - [14/Mar/2021 19:17:45] using username/password from authorization header 172.17.0.1 - ldap_reader [14/Mar/2021 19:17:45] searching on server "ldap://192.168.0.16" with base dn "CN=Users,DC=corp,DC=to,DC=high" with filter "(sAMAccountName=ldap_reader)" 172.17.0.1 - ldap_reader [14/Mar/2021 19:17:45] attempting to bind using dn "CN=ldap_reader,CN=Users,DC=corp,DC=to,DC=high" 172.17.0.1 - ldap_reader [14/Mar/2021 19:17:45] Error while binding as an existing user "CN=ldap_reader,CN=Users,DC=corp,DC=to,DC=high": {'info': u'80090308: LdapErr: DSID-0C0903AA, comment: AcceptSecurityContext error, data 52e, v1772', 'msgid': 3, 'msgtype': 97, 'result': 49, 'desc': u'Invalid credentials', 'ctrls': []}, server="ldap://192.168.0.16", login="ldap_reader" 172.17.0.1 - ldap_reader [14/Mar/2021 19:17:46] "GET / HTTP/1.1" 401 -
Настройка контейнера Nginx для авторизации в Active Directory (LDAP)
Мы проверили работу демона аутентфикации nginx-ldap-auth, теперь можно перейти к настройке Nginx. В данной статье, покажу, как настроить авторизацию для docker registry с учетной записью Active Directory. Nginx будем запускать в docker контейнере. Создадйте Dockerfile:
FROM nginx COPY nginx-ldap-auth.conf /etc/nginx/conf.d/nginx-ldap-auth.conf
В директории с Dockerfile создадйте файл конфигурации nginx-ldap-auth.conf.
Содержание файла:
proxy_cache_path cache/ keys_zone=auth_cache:10m; upstream backend { server registry:5000; } server { listen 8081; # Protected application location / { auth_request /auth-proxy; proxy_pass http://backend/; } location = /auth-proxy { internal; proxy_cache auth_cache; proxy_cache_valid 200 10m; # The following directive adds the cookie to the cache key proxy_cache_key "$http_authorization$cookie_nginxauth"; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_pass http://ldap-auth:8888; # URL and port for connecting to the LDAP server proxy_set_header X-Ldap-URL "ldap://192.168.0.16"; # Base DN proxy_set_header X-Ldap-BaseDN "cn=Users,dc=corp,dc=to, dc=high"; # Bind DN proxy_set_header X-Ldap-BindDN "[email protected]"; # Bind password proxy_set_header X-Ldap-BindPass "r05-2020"; proxy_set_header X-Ldap-Template "(sAMAccountName=%(username)s)"; } }
В данном конфиге указывается location, с опцией auth_request, которая позволяет отправить HTTP запрос для проверки авторизации. Мы указали внутренний адрес /auth-proxy, на который будет переадресован запрос авторизации. В данном location указаны опций, которые настраивают параметры кэширования и заголовки, которые будут отправлены в nginx-ldap-auth.
- proxy_pass_request_body — запрещает передачу полей заголовка исходного запроса на проксируемый сервер;
- proxy_set_header — устанавливает заголовок Content-Length;
- proxy_cache_valid 200 10m — кешировать ответ с кодом 200 на 10 минут (чтобы не отправлять запрос к серверу авторизации при каждом обращении);
- proxy_cache_path — путь и другие параметры кэша;
- proxy_cache_key — ключ кэширования;
- proxy_pass – указывает на контейнера демона авторизации nginx-ldap-auth. Остальные опции аналогичны тем, что использовались при проверке демона. Файл конфига и dockerfile можно взять в github репозитории.
После создания конфига, нужно собрать контейнер:
# sudo docker build . -t nginx-ldap
Запустите контейнер с nginx с выводом в консоль:
# sudo docker run -p:8081:8081 --link ldap-auth --link registry --name nginx-ldap nginx-ldap
- Запускаем nginx-ldap образ с именем —name nginx-ldap;
- —link — опция позволяет связать в одну сеть контейнеры, так как мы к ним обращаемся по именам в конфиге nginx. Без этих опций из контейнера nginx не будет резолвиться имя registry и ldap-auth;
- -p:8081:8081 — прокидываем порт наружу.
Вывод команды будет такой:
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Configuration complete; ready for start up
Тестируем аутентификацию пользователя доменного пользователя AD в Nginx
Теперь откройте браузер и перейдите по адресу: http://localhost:8081/v2/_catalog. После успешной авторизации nginx должен переадрессовать на приватный репозиторий docker, описанный в статье про Docker Registry Нам будет предложено окно базовой авторизации следующего вида.
В логах веб сервера должна появится строку обращения к /v2/_catalog. Т.к. текущий пользователь не авторизирован, сервер вернул код ответа 401 код и предлагает авторизоваться. После успешной авторизации должен вернуться код ответа 200.
172.17.0.1 - - [21/Mar/2021:19:19:09 +0000] "GET /v2/_catalog HTTP/1.1" 401 179 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0" "-" 172.17.0.1 - ldap_reader [21/Mar/2021:19:19:20 +0000] "GET /v2/_catalog HTTP/1.1" 200 20 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0" "-"
Введите логин и пароль пользователя домена. Для теста можно использовать сервисного пользователя, которого создали для поиска по дереву домена.
После успешной авторизации должна открыться страница со списком образов в Docker репозитории.
У меня в репозитории в данный момент один образ, созданного в статье про создание простого микросервиса в docker.
В логах сервиса, который обращается к серверу ldap можно увидеть следующее:
172.17.0.4 - - [21/Mar/2021 19:18:18] using username/password from authorization header 172.17.0.4 - ldap_reader [21/Mar/2021 19:18:18] searching on server "ldap://192.168.0.16" with base dn "cn=Users,dc=corp,dc=to, dc=high" with filter "(sAMAccountName=ldap_reader)" 172.17.0.4 - ldap_reader [21/Mar/2021 19:18:18] attempting to bind using dn "CN=ldap_reader,CN=Users,DC=corp,DC=to,DC=high" 172.17.0.4 - ldap_reader [21/Mar/2021 19:18:18] Auth OK for user "ldap_reader" 172.17.0.4 - ldap_reader [21/Mar/2021 19:18:18] "GET /auth-proxy HTTP/1.0" 200
По логу видно, что выполнено подключение к LDAP серверу, выполнена авторизация и проверены учетные данные пользователя.
После проверки, можно остановить контейнеры, и запустить заново в фоновом режиме:
Удаляем контейнеры:
# sudo docker rm ldap-auth
# sudo docker rm nginx-ldap
Теперь можно запуститьконтейнеру nginx и nginx-ldap-auth в фоновом режиме. Также мы убрали в контейнере авторизации проброс портов наружу, это больше не нужно.
# sudo docker run -d --name ldap-auth nginx-ldap-auth-daemon
# sudo docker run -p:8081:8081 -d --link ldap-auth --link registry --name nginx-ldap nginx-ldap
В данной статье мы рассмотрели общий принцип настройки авторизации с учетными данными LDAP в nginx. В качестве конечного backend может выступать не обязательно docker registry, таким образом можно настроить аутентификацию в Active Directory в в любом вашем приложении, опубликованном через nginx.