Намедни встала задача — обеспечить прозрачную авторизацию пользователей домена в CRM, собственно Microsoft давным давно разработал для этих целей метод аутентификации HTTP Negotiate, это все замечательно работает на IIS и Windows Server, а у нас за плечами Samba4 в роли Primary Domain Controller и проксирующий веб сервер nginx. Как быть?

    В сети куча информации по организации подобной схемы для Apache2 & AD на базе Windows, а вот пользователям nginx приходится собирать все по крупицам, информации кот наплакал. В базовой поставке Nginx нет подобного функционала. Благо люди не пали духом и история началась в мейл рассылках nginx в 2009 году, где один американский товарищ из Огайо нанял разработчика на RentACoder для запиливания модуля с подобным функционалом. Ребята форкнули подобный модуль для апача, прикрутили его к nginx и результаты работы выложили на github, где модуль время от времени допиливался разными людьми и в итоге принял роботоспособный вид. Последнюю версию можно получить на гитхабе.


В данном руководстве я расскажу как заставить работать nginx с SPNEGO модулем и samba4.


Первое необходимое — собрать nginx с нужным нам модулем. Для примера будет использоваться Ubuntu 16.04.

Для начала ставим nginx
apt-get install nginx -V

Далее куда-нибудь качаем последнюю версию исходников с официального сайта.
wget http://nginx.org/download/nginx-1.11.2.tar.gz

Отлично, распакуем папку с исходниками и положим в него наш spnego-http-auth-nginx-module
tar xvzf nginx-1.11.2.tar.gz
cd nginx-1.11.2
git clone https://github.com/stnoonan/spnego-http-auth-nginx-module

Смотрим с какими опциями у нас сейчас собран nginx из базовой поставки и получаем портянку
root@dc1:~# nginx -V
nginx version: nginx/1.11.1
built by gcc 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2.1) 
built with OpenSSL 1.0.2g-fips  1 Mar 2016
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_perl_module=dynamic --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-debug  --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,--as-needed'

Копируем в блокнот портянку и добавляем куда-нибудь
--add-module=spnego-http-auth-nginx-module

Приступаем к сборке с нужными нам параметрами
./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_perl_module=dynamic --with-threads --with-stream --with-stream_ssl_module --add-module=spnego-http-auth-nginx-module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-debug  --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,--as-needed'

Естественно утилиты для сборки должны быть установлены в системе, а nginx должен быть потушен.
make
make install

После процедуры у нас в итоге теперь рабочий самосборный nginx с нужным нам модулем. Проверить это можно также — nginx -V и посмотреть в параметрах наличие модуля.


Для примера используем url для авторизации test.intranet.com, домен intranet.com


Пора немного поработать с самбой, добавим пользователя, на которого повесим service principal name (spn) для авторизации через Kerberos
samba-tool user add HTTP
samba-tool user setexpiry HTTP --noexpiry
samba-tool spn add HTTP/test.intranet.com HTTP
samba-tool spn add host/test.intranet.com HTTP

Создадим Kerberos keytab файл для nginx
samba-tool domain exportkeytab /etc/http.keytab --principal=HTTP/test.intranet.com
samba-tool domain exportkeytab /etc/http.keytab --principal=host/test.intranet.com

Проверим созданный keytab
klist -ke /etc/http.keytab
Keytab name: FILE:/etc/http.keytab
KVNO Principal
---- --------------------------------------------------------------------------
   1 host/test.intranet.com@INTRANET.COM (des-cbc-crc) 
   1 host/test.intranet.com@INTRANET.COM (des-cbc-md5) 
   1 host/test.intranet.com@INTRANET.COM (arcfour-hmac) 
   1 HTTP/test.intranet.com@INTRANET.COM (des-cbc-crc) 
   1 HTTP/test.intranet.com@INTRANET.COM (des-cbc-md5) 
   1 HTTP/test.intranet.com@INTRANET.COM (arcfour-hmac)

Авторизуемся на контроллере домена через kerberos
kinit administrator
Password for administrator@INTRANET.COM:
Warning: Your password will expire in 39 days on Пт 30 июл 2016 11:23:11

Проверяем авторизацию в домене по имени spn с помощью keytab-файла:
kinit -V -k -t /etc/http.keytab HTTP/test.intranet.com@INTRANET.COM
Using default cache: /tmp/krb5cc_0
Using principal: HTTP/test.intranet.com@INTRANET.COM
Using keytab: /etc/http.keytab
Authenticated to Kerberos v5

У меня на данном этапе была проблема, аутентификация никак не хотела проходить. Ошибка:
kinit: Client not found in Kerberos database while getting initial credentials
Перерыл пол интернета, в итоге нашлось решение, оказалось samba-tool отрабатывает не так, как задумывалось Microsoft, неожиданно, правда?
Для решения проблемы идем на виндовую машину и с помощью адмистративной консоли «Active Directory — пользователи и компьютеры» правим нашего пользователя HTTP, а именно правим поле имя входа пользователя на — HTTP/test.intranet.com
После этой процедуры все работает.

Пора перейти к конфигурации nginx, смотрим мой конфиг виртуального хоста
    server {
	listen *:443 ssl;
	server_name test.intranet.com;

#	error_log /var/log/nginx/debug.log debug;

        ssl_certificate /etc/nginx/ssl/nginx.crt;
        ssl_certificate_key /etc/nginx/ssl/nginx.key;

	location / {

	proxy_pass http://********/$request_uri;
	proxy_set_header   X-Real-IP $remote_addr;
	proxy_set_header   Host $http_host;
	proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

	auth_gss on;
	auth_gss_realm INTRANET.COM;
	auth_gss_keytab /etc/http.keytab;
	auth_gss_service_name HTTP/test.intranet.com;
#	auth_gss_allow_basic_fallback off;
	}

    }


Параметр auth_gss_allow_basic_fallback off; позволяет выключить запрос basic авторизации если пошло что-то не так, допустим юзер не в домене.


Для работы прозрачной авторизации необходимо выполнение некоторых условий:
  • Клиентская машина состоит в домене уровня Windows 2003 или выше
  • Пользователь залогинен в домен
  • Обращение к хосту идет посредством HTTPS (по моим тестам работает и с http)
  • Хост внесен в зону Intranet security zone в IE
  • Имя хоста есть в DNS под A записью
  • Имя хоста лежит в зоне домена (пример — xxxx.intranet.com)
  • Вы корректно создали Service Principal Names (SPNs) в Active Directory, а также замапили на service account (в нашем случае HTTP)


Остается на машине внести наш URL — test.intranet.com в зону Intranet security zone в IE и пробовать зайти по нашей ссылке. Если все настроено верно, то nginx должен прозрачно пропустить юзера на сайт. На стороне сервера, куда мы собственно проксируем соеденение, в заголовках пакетов можно словить следующую информацию.
[PHP_AUTH_USER] => Administrator [PHP_AUTH_PW] => bogus_auth_gss_passwd
Остается немного допилить web приложение для прозрачной аутентификации, но то дело уже web программиста…

В других браузерах этот механизм так же работает с небольшими доработками.
  • В Firefox входим в «about:config» ищем параметры с «negotiate»
    Добавляем наш сайт в параметр «network.negotiate-auth.trusted-uris» например
    "http://test.intranet.com"
    Параметр «network.negotiate-auth.using-native-gsslib» должен быть true.
  • Для работы Chrome, его нужно запускать с параметром:
    google-chrome --auth-server-whitelist="http://test.intranet.com" --auth-negotiate-delegate-whitelist="http://test.intranet.com"
    


Собственно вот и весь процесс настройки, убил я на него пару дней, решил поделиться с общественностью. Данный механизм позволяет избавить пользователей в офисе от ввода логина\пароля при доступе к какому-либо внутреннему web приложению.
Поделиться с друзьями
-->

Комментарии (17)


  1. kolu4iy
    07.07.2016 16:39

    Спасибо. Устойчиво работает? Credentials не теряются?


    1. AbyssMoon
      07.07.2016 17:24

      В данный момент в процессе тестирования, в боевой режим для всех еще не внедрено, пока проблем не было.


      1. kolu4iy
        07.07.2016 17:35

        У меня (был момент) схема обновление KVNO не отрабатывала, например. А KVNO раз в неделю увеличивается принудительно. Потому, если не сложно, оставьте попозже более подробный отчёт — как оно там, если без нас…


  1. grossws
    07.07.2016 18:43

    make install, сколько можно… На debian/ubuntu есть checkinstall.


    1. AbyssMoon
      07.07.2016 18:48

      Привычка сильная штука, но спасибо за заметку.


      1. symbix
        07.07.2016 22:12

        В вашем случае можно было бы просто вручную заменить /usr/sbin/nginx.

        А если уж совсем по-хорошему — лучше собрать свой пакет, ничего сложного в этом нет.


  1. brestows
    07.07.2016 19:24

    Если не ошибаюсь на web сервере не обязательно ставить samba keytab можно сгенерить на домен контроллере и перенести его на web сервер, для дальнейшего использования.


    1. AbyssMoon
      07.07.2016 19:58

      Все верно, в моем случае контроллер домена и веб сервер это одна машина.


  1. Sleuthhound
    07.07.2016 21:43

    Что будет если зайти на test.intranet.com c ПК который не в домене?

    У вас написано:
    location / {
    proxy_pass


    то ест nginx используется как frontend, почему про это ни слова? куда все проксируется, в какое web-приложение, оно ведь тоже должно быть настроено соответствующим образом?

    Пробовали собрать модуль spnego-http-auth-nginx-module как динамический?


    1. AbyssMoon
      08.07.2016 06:53

      Как я и написал в статье, если не использовать параметр

      auth_gss_allow_basic_fallback off;
      

      то при попытке входа не с доменного ПК будет вылезать запрос basic auth, который примет доменную учетку.

      А по поводу проксирования, конечно, nginx просто проксирует соеденение на web приложение, в моем случае CRM, приложение же конечно должно быть доработано разработчиками, дабы авторизовывать пользователя прозрачно. С nginx приходят специальные пакеты идентификации пользователя, наш программист их ловит в таком виде:
      [PHP_AUTH_USER] => Administrator [PHP_AUTH_PW] => bogus_auth_gss_passwd

      Собственно красивый вариант — внутри сети офиса на локальном DNS делаем запись внешнего ULR CRM с указанием на nginx, а на самом nginx делаем rewrite на локальный доменный URL, получится, что когда юзеры в офисе, они будут ходить прозрачно в приложение, когда не в офисе — по старинке, с запросом логина пароля.


      1. Sleuthhound
        08.07.2016 12:47

        А какую CRM используете? Там из коробки есть прозрачная авторизация или Вы допиливали?


        1. AbyssMoon
          08.07.2016 13:49

          Самописную, соответственно допилили…


      1. Balek
        09.07.2016 09:13

        Поясните, пожалуйста, а что будет не так, если auth_gss включить для внешнего сайта?


        1. AbyssMoon
          10.07.2016 10:52

          Если сайт не входит в домен, то работать по-идее нет будет, во всяком случае так пишут на гитхабе.


          1. Balek
            10.07.2016 11:00

            Не совсем понял. Если AD домен — example.loc, то аутентификация будет работать только на crm.example.loc, а на crm.example.com не будет?


            1. AbyssMoon
              10.07.2016 11:04

              Все верно.


  1. Botkin
    08.07.2016 06:29
    +1

    Рекомендую посмотреть haproxy также