Как мы корпоративно от винды убегали

в 18:50, , рубрики: devops, docker, kerberos, nginx

Так повелось, что в нашей компании основным языком для backend разработки был выбран C#. Мы, кстати, этим выбором всегда были довольны, а когда MS начали развивать платформу .net Core, стало еще интереснее, так как C# — это хорошо, но C# под Linux — еще лучше.

Путь перехода на кросс-платформенную разработку я описывать не стану, так как уже очень многие прошли путь перехода с Framework на Core.

Сделаю акцент на одном моменте. Помимо всего прочего, в сторону хостинга наших приложений под Linux нас подтолкнул Docker, так как очень хотелось приобщиться к молодежному течению контейнеризации всего что только возможно.

Поскольку мы разрабатываем enterprise, то вместе с нами должна была убежать под linux и сквозная windows аутентификация. Собственно это и стало побудителем к написанию статьи. Так как информация находилась весьма трудно, отдельными кусками, и общением со многими людьми, идея собрать все необходимое в одном месте и описать работающий вариант показалась неплохой.

В качестве решения был выбран вариант с обратным прокси под nginx с kerberos аутентификацией. А чтобы решением могли пользоваться товарищи из разных проектов, было решено запилить образ docker, который бы решал базовую задачу, и от которого могли бы наследоваться другие, или использовать его как есть.

Для того чтобы заработал kerberos, надо было собрать nginx с дополнительными модулями.
В итоге получилась примерно такая команда. Все слеплено в два вызова, чтобы создавать меньше слоев.

Разберем наш Dockerfile. Базироваться будем на весьма компактном образе с alpine

FROM alpine:3.7

Далее затянем нужные пакеты, исходники nginx и требуемого модуля spnego-http-auth-nginx-module. В итоге получается примерно такая команда

ENV NGINX_VERSION 1.15.1

RUN set -ex 
  && apk add --no-cache 
    git 
    krb5 
    krb5-dev 
    ca-certificates 
    libressl 
    pcre 
    zlib 
  && apk add --no-cache --virtual .build-deps 
    build-base 
    linux-headers 
    libressl-dev 
    pcre-dev 
    wget 
    zlib-dev 
  && cd /tmp 
  && wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz 
  && tar xzf nginx-${NGINX_VERSION}.tar.gz 
  && git clone https://github.com/stnoonan/spnego-http-auth-nginx-module.git nginx-${NGINX_VERSION}/spnego-http-auth-nginx-module

Данный блок был выделен отдельно, чтобы при повторной сборке данный слой мог браться из кэша, так как он самый долгий по времени.

Следующим набором команд соберем nginx и приберемся за собой, чтобы образ не распухал понапрасну

RUN cd /tmp/nginx-${NGINX_VERSION} 
  && ./configure 
    
    --prefix=/etc/nginx 
    --sbin-path=/usr/sbin/nginx 
    --conf-path=/etc/nginx/nginx.conf 
    --error-log-path=/var/log/nginx/error.log 
    --pid-path=/var/run/nginx.pid 
    --lock-path=/var/run/nginx.lock 
    --user=nginx 
    --group=nginx 
    --with-threads 
    --with-file-aio 
    --with-http_ssl_module 
    --with-http_v2_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_auth_request_module 
    --with-http_random_index_module 
    --with-http_secure_link_module 
    --with-http_slice_module 
    --with-http_stub_status_module 
    --http-log-path=/var/log/nginx/access.log 
    --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 
    --with-mail 
    --with-mail_ssl_module 
    --with-stream 
    --with-stream_ssl_module 
    --with-stream_realip_module 
    --add-module=spnego-http-auth-nginx-module 
  && make -j$(getconf _NPROCESSORS_ONLN) 
  && make install 
  && sed -i -e 's/#access_log  logs/access.log  main;/access_log /dev/stdout;/' -e 's/#error_log  logs/error.log  notice;/error_log stderr notice;/' /etc/nginx/nginx.conf 
  && adduser -D nginx 
  && mkdir -p /var/cache/nginx 
  && apk del .build-deps 
  && rm -rf /tmp/*

И чтобы все это имело смысл, поднимем nginx

CMD ["nginx", "-g", "daemon off;"]

Можно считать, что образ готов, теперь приступаем к тому, чтобы наш сервер имел возможность авторизовывать пользователей.

Для этого нужно найти администратора домена, мне с ним крайне повезло — парень оказался отзывчивым и сделал то о чем его попросили очень быстро. А сделать нужно следующее.
Допустим, у хостовой машины hostname — "host-linux", а ваш домен — "DOMAIN.LOCAL".
В домене надо завести машину с именем "host-linux" и создать учетку, к которой ее привяжем, например, "host-linux-user". Далее надо создать SPN и сгенерировать keytab файл, который нам будет нужен при поднятии контейнера.

У нас команда получилась примерно такая

C:Windowssystem32>ktpass -princ HTTP/HOST-LINUX.domain.local@DOMAIN.LOCAL -mapuser host-linux-user@DOMAIN.LOCAL -pass yourpassword -cryptoAll -ptype KRB5_NT_PRINCIPAL -out C:Tempweb.keytab

После того как я получил файл, можно было идти экспериментировать. В результате у меня получился следующий nginx.conf

http {
    #Whatever is there by default

    server {
        listen       80;
        server_name  localhost;

        #Here kerberos stuff starts
        auth_gss     on;
        auth_gss_realm DOMAIN.LOCAL;
        #Keytab file from the mounted folder
        auth_gss_keytab /home/spnego/config/web.keytab;
        auth_gss_service_name HTTP/HOST-LINUX.domain.local;
        auth_gss_allow_basic_fallback off;
        #Here kerberos stuff ends

        location / {
            root   html;
            index  index.html index.htm;
        }
#bla-bla-bla

Теперь, чтобы все завелось, надо при поднятии контейнера закинуть ему актуальный nginx.conf и скормить полученный web.keytab. Для этого воспользуемся магией docker-compose

version: "2"
services:
    nginx-spnego:
        image: fclmman/alpine-nginx-spnego
#опишем проброс портов. Например такой
        ports:
            - 80:80
            - 5010:5010
            - 443:443
            - 8001:8001
#примонтируем раздел с web.keytab, и закинем в контейнер наш конфиг
        volumes:
            - ./config:/home/spnego/config
            - ./config/nginx.conf:/etc/nginx/nginx.conf

Проследуем в директорию, где у нас лежит docker-compose.yml. В нашем случае, в той же папке должна быть директория ./config с файлами nginx.conf и web.keytab. Выполним команду

docker-compose -f ./docker-compose.yml  up -d

Контейнер поднялся и не умер. Это дает надежду на успех.

Откроем браузер на доменной виндовой машине.

В одной вкладке откроем chrome://net-internals/ и запишем запросы, которые у нас ходят. В другой вкладке откроем http://host-linux:80/. Вернемся в chrome://net-internals/ и посмотрим результаты.

#Видим что сервер потребовал negotiate
t= 3 [st= 3]        HTTP_TRANSACTION_READ_RESPONSE_HEADERS
                    --> HTTP/1.1 401 Unauthorized
                        Server: nginx/1.15.1
                        Date: Fri, 10 Aug 2018 14:15:54 GMT
                        Content-Type: text/html
                        Content-Length: 597
                        Connection: keep-alive
                        WWW-Authenticate: Negotiate

t= 4 [st= 4]        HTTP_TRANSACTION_SEND_REQUEST_HEADERS
                    --> GET / HTTP/1.1
                        Host: host-linux
                        Connection: keep-alive
                        Pragma: no-cache
                        Cache-Control: no-cache
                        Authorization: Negotiate #очень длинный набор букв
                        Upgrade-Insecure-Requests: 1
                        User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
                        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
                        Accept-Encoding: gzip, deflate
                        Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
t= 4 [st= 4]     -HTTP_TRANSACTION_SEND_REQUEST
t= 4 [st= 4]     +HTTP_TRANSACTION_READ_HEADERS  [dt=47]
t= 4 [st= 4]        HTTP_STREAM_PARSER_READ_HEADERS  [dt=47]
t=51 [st=51]        HTTP_TRANSACTION_READ_RESPONSE_HEADERS
                    --> HTTP/1.1 200 OK
                        Server: nginx/1.15.1
                        Date: Fri, 10 Aug 2018 14:15:54 GMT
                        Content-Type: text/html
                        Content-Length: 612
                        Last-Modified: Fri, 10 Aug 2018 12:21:36 GMT
                        Connection: keep-alive
                        WWW-Authenticate: Negotiate #Набор букв покороче
                        ETag: "5b6d8350-264"
                        Accept-Ranges: bytes

В результате видим, что операция прошла успешно и видим приветственный экран nginx.
Стоит сделать одно уточнение, работать будет все только по hostname, но насколько я понимаю это правильно, ибо kerberos мы привязывали как раз к нему.

Спасибо за внимание, если вы дочитали до этого места, и очень надеюсь что статья окажется полезна.

Автор: Vahman

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js