Настройка аутентификации в Nginx через Active Directory (LDAP) | Windows для системных администраторов

Basic authentication fallback

The module falls back to basic authentication by default if no negotiation is
attempted by the client. If you are using SPNEGO without SSL, it is recommended
you disable basic authentication fallback, as the password would be sent in
plaintext. This is done by setting auth_gss_allow_basic_fallback in the
config file.

These options affect the operation of basic authentication:

Configuration reference

You can configure GSS authentication on a per-location and/or a global basis:

These options are required.

  • auth_gss: on/off, for ease of unsecuring while leaving other options in
    the config file
  • auth_gss_keytab: absolute path-name to keytab file containing service
    credentials

These options should ONLY be specified if you have a keytab containing
privileged principals. In nearly all cases, you should not put these
in the configuration file, as gss_accept_sec_context will do the right
thing.

Debugging

The module prints all sort of debugging information if nginx is compiled with
the –with-debug option, and the error_log directive has a debug level.

Nginx google-authenticator или не все не так просто…

Авторизация в nginx на базе google одноразовых паролей.

По разным причинам пришлось отказаться от авторизации auth_basic и файла с паролями, несекурно и все тут.
Пользователей много с разным уровнем знаний, поэтому авторизация по сертификатам не подходит.

Подсказали решение на базе Nginx (http_auth_request_module) Apache (google-authenticator-apache-module).

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

Настройка аутентификации в Nginx через Active Directory (LDAP) | Windows для системных администраторов

Идея в следующем. Проверка авторизации происходит не на самом nginx а на сервере apache. Nginx посылает подзапрос auth_request на ‘Сервер авторизации’ apache, если авторизация пройдена apache отвечает HTTP 200, и nginx радостно считает что пользователь авторизацию прошел.

Как это работает. На ‘Сервере авторизации’ вы создаете файл(Имя файла — логин) для каждого пользователя который содержит некий secure key.
Не вдаваясь в подробности будем считать что на базе этого secure key и текущего времени, на стороне сервера и на стороне клиента, независимо друг от друга, каждую минуту генерируются одноразовые пароли.

В качестве приложения генерирующего пароли используется:

Google Authenticator for Android.
Google Authenticator for iOS.
Если у вас нет телефона, то есть приложение и под Windows:
Google Authenticator for Windows.
Тамже есть и под java…

Как синхронизировать приложение с сервером чуть позже, а сейчас что же надо настроить на сервере.

К сожалению модуля под Nginx нету и быть не может из-за особенностей реализации Nginx. Почему точно сказать не могу, но мне говорили слова про state mashine и тд… 🙂

Но под Apache модуль есть!!! Зовется google-authenticator-apache-module

Там есть и бинарные сборки под centsOS 6 — работает проверил. Есть и исходники и есть более свежее в репо. Я поигрался с бинарной сборкой а потом собрал модуль из исходников.

Значит так на apache google-authenticator-apache-module делаем «сервер авторизации» (можно даже 2 сервера для надежности) и к нему подключаем Nginx_ы на всех серверах где нам надо.

Итак берем apache2, собираем или берем бинарный модуль google-authenticator-apache-module.

и пишем такой конфиг:

Loadmodule authn_google_module  modules/mod_authn_google.so
Listen *:8888

<Location ~ "/(|_auth/)" >
# разрешаем только сети где у нас стоят Nginx_ы. Я еще и файрволом прикрыл на всякий случай :)
   Order deny,allow
   Deny from all
   Allow from 10.0.0.0/8
   Allow from 192.168.0.0/24

   AuthType Basic
   AuthName "My Closed Zone  Gauth"
   AuthBasicProvider "google_authenticator"
   Require valid-user
   GoogleAuthUserPath /etc/httpd/ga_auth
   GoogleAuthCookieLife 600
   GoogleAuthEntryWindow 3
   #  GoogleAuthLogLevel - работает только в последней версии собранной из исходников.
   GoogleAuthLogLevel 1
</Location>

Рестартуем apache, пробуем в браузере отрыть его — есть запрос авторизации? Прекрасно!!!

Дальше…

/etc/httpd/ga_auth — директория где лежат файлы с секретными ключами.

Как и чем их создавать: google-authenticator

Качаем, libpam-google-authenticator, собираем получаем google-authenticator. Вот она может генерировать то что нам надо.

Я сделал скрипт чтоб файл ложился сразу куда надо.

#!/bin/bash
/usr/bin/google-authenticator -t -D -f [email protected] -r3 -R600 -s /etc/httpd/ga_auth/$1 -w2
/bin/chown apache:apache  /etc/httpd/ga_auth/$1

Скрипту передаем параметром логин пользователя.

Отработав скрипт создаст файл и выдаст в консоль:

https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/[email protected]?secret=QYYY34XXXX534A4QA
Your new secret key is: DQYYY34XXXX534A4QA
Your verification code is 123456
Your emergency scratch codes are:
  99942105
  28654999
  45999608
  33300650
  99907825

в файл /etc/httpd/ga_auth/_user_login_ пришется много разного но ничего не влияет на работу google-authenticator-apache-module кроме «Your new secret key is:», по крайне мере я экспериментировал — ничего… так что можно оставить только первую строку с QUCFKE6AK3PBA4QA а можно и не трогать файл.

дальше открываем ссылку https://www.google.com/chart?chs=200×200&chld=M|0&cht=qr&chl=otpauth://totp/[email protected]?secret=QYYY34XXXX534A4QA
(ссылка намеренно кривая)
и видим

Настройка аутентификации в Nginx через Active Directory (LDAP) | Windows для системных администраторов

Устанавливаем на телефон (если пока еще не установили) Google Authenticator. Сканируем QR код, и все телефон начинает каждую минуту генерировать одноразовый пароль.

Можете попробовать авторизоваться в apache, должно сработать, но может выдать 404 — т.к. после авторизации apache хочет показать какой нибудь index.html а его у вас возможно нет.

Кстати google-authenticator-apache-module модуль честно пишет в /var/log/httpd/error_log.

И так, у нас есть «сервер авторизации» и телефон — оба генерируют одинаковую последовательность одноразовых паролей.

Перейдем к настройке nginx.

берем Nginx посвежее, я взял последний стабильный 1.5.7, собираем с –with-http_auth_request_module (http_auth_request_module ключевой момент).

        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;


        location  = /_auth/ {
           internal;
                proxy_pass http://gauth_pool/;
                proxy_pass_request_body off;
                proxy_buffering off;
                proxy_cache off;
                proxy_set_header Content-Length "";
                proxy_set_header Host mydomain.com;
        }

я выделил этот кусок конфига в отдельный файл и буду include там где будет нужно.

в /etc/nginx/nginx.conf в конце добавляем пул серверов apache хотя бы 2 для надежности:

upstream  gauth_pool  {
        server ga1.mydomain.com:8888 weight=1;
        server ga2.mydomain.com:8888 weight=5;
}

теперь везде где надо для Nginx добавляем:


server {
        listen 443 ssl spdy;
#       listen 80;

        server_name www.mydomain.com;

        satisfy  any;
        include   /etc/nginx/allow_nets.txt;
        deny  all;
        auth_request /_auth/;

        include location_auth.conf;

Проверяем 🙂

/etc/nginx/allow_nets.txt — список IP которым не надо авторизоваться. Думаю не надо всех мучать авторизацией.

Теперь самое интересное — тонкости:

Работает все это следующим образом.

Авторизовались. Пока работаете на сайте постоянно происходит проверка вашей авторизации по куки
set-cookie: google_authn=user:1390714695:FRxZCSDzox/a5KEGXXXXXXX5TYGIYZrRf=

Она постоянно обновляется, 1390714695 — время когда истекает срок действия куки. Она постоянно обновляется текущее время {GoogleAuthCookieLife 600} из конфига apache. Можно сказать это время бездействия на страничке. отошли на 10 минут — заново авторизуйтесь.

И второй момент. Введенный вами одноразовый пароль действует не вечно. Вообще срок ему 1 минута и все. Но с помощью параметра
GoogleAuthEntryWindow 3, можно раздвинуть «временное окно» действия паролей.
Смысл в том что google-authenticator-apache-module может генерировать помимо текущего пароля, пароли до и после в количестве GoogleAuthEntryWindow, это сделано для того что если часы на вашем мобильном и часы на сервер не совпадают — вы все равно смогли залогиниться.

Это же можно сделать чтоб ваш одноразовый пароль дольше проходил авторизацию, это будет позволять дольше сохранять авторизацию «пройденой»;

Ну и напоследок кусок лога из apache:

[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] **** COOKIE AUTH at  T=1390714549, referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] Cookie auth is DECLINED, referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] **** PW AUTH at  T=1390714549  user  "my_user", referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] getUserSecret with username "my_user", referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] OPENING FILENAME /etc/httpd/ga_auth/my_user, referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] Comparing Authentication   @ T=46017151 Code=475002 "332994" vs.  "475002", referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] Comparing Authentication   @ T=46017151 Code=87841 "332994" vs.  "087841", referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] Comparing Authentication   @ T=46017151 Code=627132 "332994" vs.  "627132", referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] Comparing Authentication   @ T=46017151 Code=332994 "332994" vs.  "332994", referer: https://www.mydomain.com/
[Sun Jan 20 09:01:49 2022] [error] [client 1.2.3.4] Created cookie expires 1390715149 hash is sEFQLm92bSI= Cookie: google_authn=my_user:1390715149:sEFQLm92bSI=, referer: https://www.mydomain.com/

Постепенно, а точнее раз в минуту строки типа
Comparing Authentication @ T=46017151 Code=87841 "332994" vs.
будут сокращаться, пока не останется строка только с вашим паролем, и через минуту — авторизация по новой.

Prerequisites

Authentication has been tested with (at least) the following:

  • Nginx 1.2 through 1.7
  • Internet Explorer 8 and above
  • Firefox 10 and above
  • Chrome 20 and above
  • Curl 7.x (GSS-Negotiate), 7.x (SPNEGO/fbopenssl)

The underlying kerberos library used for these tests was MIT KRB5 v1.8.

Настройка и запуск сервиса аутентификации nginx-ldap-auth

Для запуска сервиса nginx-ldap-auth будем использовать docker. Для начала нужно клонировать репозиторий с сервисом с github:

Настройка контейнера 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. Содержание файла:

Простая аутентификация на nginx с помощью lua

image

Доброго времени суток. В данной заметке хочу рассказать о простой аутентификации с помощь nginx и lua-скриптов.

Подняв у себя домашний сервер на ubuntu с plex и transmission и обзаведясь доменом, через который вывел это добро в большой мир, понял Я, что было бы неплохо обзавестись единой точкой аутентификации. Тем более nginx у меня уже был установлен (даже nginx-extras, что немаловажно, поскольку там есть lua).

Собравшись с мыслями, сформулировал требования:

Вариант с nginx basic auth не устроил по причине отсутствия защиты от перебора, вариант с nginx auth PAM вызвал у меня недоверие по причине аутентификации по логину/паролю ОС. И оба варианта не дают возможности аутентификации через свою отдельную форму.

Алгоритм аутентификации довольно прост:
image

Ну что ж, приступим.

Для начала создадим lua-скрипт с некоторым функциями, которые понадобятся нам в дальнейшем:

/etc/nginx/lua/secure.lua

-- Количество попыток для ip/32 и User-Agent
local ip_ua_max = 10

-- Количество попыток для ip/32
local ip_4_max = 50

-- Количество попыток для ip/16
local ip_3_max = 100

-- Количество попыток для ip/8
local ip_2_max = 500

-- Количество попыток для ip/0
local ip_1_max = 1000

counters = {}
counters["ip_ua"] = {}
counters["ip_4"] = {}
counters["ip_3"] = {}
counters["ip_2"] = {}
counters["ip_1"] = {}

-- Проверка числа попыток (is_cnt=false) и учёт неуспешной попытки (is_cnt=true)
function is_secure(ip, user_agent, is_cnt)
    local md5_ip_ua = ngx.md5(ip..user_agent)
    local md5_ip_4 = ngx.md5(ip)
    local md5_ip_3 = ""
    local md5_ip_2 = ""
    local md5_ip_1 = ""
    local cnt = 0
    for i in string.gmatch(ip, "%d ") do
        cnt = cnt   1
        if cnt < 4 then
            md5_ip_3 = md5_ip_3.."."..i
        end
        if cnt < 3 then
            md5_ip_2 = md5_ip_2.."."..i
        end
        if cnt < 2 then
            md5_ip_1 = md5_ip_1.."."..i
        end
    end
    md5_ip_3 = ngx.md5(md5_ip_3)
    md5_ip_2 = ngx.md5(md5_ip_2)
    md5_ip_1 = ngx.md5(md5_ip_1)
    if is_cnt then
        -- Учитываем неуспешную попытку
        counters["ip_ua"][md5_ip_ua] = (counters["ip_ua"][md5_ip_ua] or 0)   1
        counters["ip_4"][md5_ip_4] = (counters["ip_4"][md5_ip_4] or 0)   1
        counters["ip_3"][md5_ip_3] = (counters["ip_3"][md5_ip_3] or 0)   1
        counters["ip_2"][md5_ip_2] = (counters["ip_2"][md5_ip_2] or 0)   1
        counters["ip_1"][md5_ip_1] = (counters["ip_1"][md5_ip_1] or 0)   1
        
        -- Пишем в лог подробности неуспешной попытки
        log_file = io.open("/var/log/nginx/access.log", "a")
        log_file:write(ip.."	"..(counters["ip_ua"][md5_ip_ua] or 0).."	"..(counters["ip_4"][md5_ip_4] or 0).."	"..(counters["ip_3"][md5_ip_3] or 0).."	"..(counters["ip_2"][md5_ip_2] or 0).."	"..(counters["ip_1"][md5_ip_1] or 0).."	"..user_agent.."n")
        log_file:close()
    else
        -- Проверяем число неуспешных попыток
        if
            (counters["ip_ua"][md5_ip_ua] or 0) > ip_ua_max or
            (counters["ip_4"][md5_ip_4] or 0) > ip_4_max or
            (counters["ip_3"][md5_ip_3] or 0) > ip_3_max or
            (counters["ip_2"][md5_ip_2] or 0) > ip_2_max or
            (counters["ip_1"][md5_ip_1] or 0) > ip_1_max
        then
            return false
        else
            return true
        end
    end
end

-- Проверка логина/пароля
-- В данном примере просто сравнение с хэшом из файла, при желании в данной функции можно реализовать проверку логина/пароля где угодно (в БД например)
function sing_in(log, pass)
    local auth_file = io.open("/etc/nginx/auth/pass","r")
    for line in io.lines("/etc/nginx/auth/pass") do
        if line == log..":"..ngx.md5(pass) then
            auth_file:close()
            return true
        end
    end
    auth_file:close()
    return false
end

-- Сохраняем функции в глобальном контейнере secure
local secure = ngx.shared.secure
secure:set("sing_in", sing_in)
secure:set("is_secure", is_secure)

Добавим инициализацию данного скрипта в глобальный конфиг nginx:

Теперь создадим lua-скрипт для проверки cookie (шаги 2, 2.1, 3):

Добавим проверку данным скриптом в конфиги внутренних сервисов:

Создадим страницу аутентификации:

/var/www/html/auth.html

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <title>somedomain</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <style>
            body{
                height: 100%;
                background-color: rgb(64, 64, 64);
                text-align:center;
                align:center;
                vertical-align: middle;
            }
            form {
                display: inline-block;
                text-align: center;
                vertical-align: middle;
                position:absolute;
                top:50%;
                right:0;
                left:0;
            }
            input{
                color: rgb(0, 255, 0);
                text-align: center; 
                border: 2px solid;
                border-color: rgb(0, 255, 0);
                background-color: rgb(64, 64, 64);
            }
            ::-webkit-input-placeholder{
                color:rgb(0, 255, 0);
                text-align: center;
            }
            ::-moz-placeholder{
                color:rgb(0, 255, 0);
                text-align: center;
            }
            :-moz-placeholder{
                color:rgb(0, 255, 0);
                text-align: center;
            }
            :-ms-input-placeholder{
                color:rgb(0, 255, 0);
                text-align: center;
            }
            br{
                display: block;
                margin: 7px 0;
                line-height: 7px;
                content: " ";
            }
        </style>
    </head>
    <body>
        <form method="post">
            <input type="text" name="login" placeholder="login" autocomplete="off">
            <br>
            <input type="password" name="password" placeholder="password" autocomplete="off">
            <br>
            <input type="submit" value="sign in">
        </form>
    </body>
</html>

И добавим для неё конфиг nginx:

В данном конфиге делаем проверку числа попыток аутентификации с помощью «auth_access.lua» (шаг 4, 4.2)

И проверку логина/пароля с помощью «auth.lua» (шаг 5, 5.1, 2.2)

/etc/nginx/lua/auth.lua

-- Берём из глобального контейнера secure нужные нам функции
local secure = ngx.shared.secure
sing_in = secure:get("sing_in")
is_secure = secure:get("is_secure")

-- Получаем ip адрес клиента
local ip = ngx.var.remote_addr

-- Получаем User-Agent адрес клиента
local ua = ngx.req.get_headers()["User-Agent"]

-- Адрес страницы аутентификации
local req_url_err = "https://auth.somedomain.ru"

-- Адрес назначения из cookie или дефолтный адрес, если в cookie адреса нет
local req_url = "https://"..(ngx.var.cookie_sv_req_url or "somedomain.ru")

-- Проверяем наличие параметров POST-запроса
ngx.req.read_body()
local args, err = ngx.req.get_post_args()
if args then
    -- 4.1. Читаем из POST-запроса логин и пароль
    local log
    local pass
    for key, val in pairs(args) do
        if key == "login" then
            log = val
        elseif key == "password" then
            pass = val
        end
    end

    -- Проверяем, что логин и пароль не пустые
    if log ~= nil and pass ~= nil then
        -- 5. Проверяем валидны ли логин и пароль
        if sing_in(log, pass) then
            -- Если валидны
            -- Задаём время жизни токена (сутки)
            local life_time = ngx.time() 86400
            -- Генерируем токен
            local auth_str = ngx.encode_base64(ngx.hmac_sha1("ОЧЕНЬ_СЕКРЕТНАЯ_ОЧЕНЬ_ДЛИННАЯ_СТРОКА_НАПРИМЕР_КАКОЙ-НИБУДЬ_32-УХЗНАЧНЫЙ_ХЭШ",ua.."|"..life_time)).."|"..life_time
            
            -- 5.1. Записываем токен в cookie и удаляем оттуда url назначения
            ngx.header["Set-Cookie"] = {"sv_auth="..auth_str.."; path=/; domain=.somedomain.ru; Expires="..ngx.cookie_time(ngx.time() 60*60*24).."; Secure; HttpOnly","sv_req_url="..ngx.req.get_headers()["Host"].."; path=/; domain=.somedomain.ru; Expires="..ngx.cookie_time(ngx.time()-60).."; Secure; HttpOnly"}
            
            -- 2.2. Возвращаем редирект на страницу назначения
            return ngx.redirect(req_url)
        end
        
        -- 5.2. Если логин/пароль невалидны, учитываем это в подсчёте неуспешных попыток аутентификации
        is_secure(ip,ua,true)
    end
end

-- 3. Если логин и пароль не переданы или невалидны, возвращаем редирект на страницу аутентификации
ngx.redirect(req_url_err)

Теперь создадим файл с логином и паролем:

md5="`echo -n "PASSWORD" | md5sum`";echo -e "LOGIN"":`sed 's/^([^ ] ) .*$/1/' <<< "$md5"`" > ~/pass; sudo mv ~/pass /etc/nginx/auth/pass; sudo chown nginx:nginx /etc/nginx/auth/pass

Подставив вместо «LOGIN» логин, а вместо «PASSWORD» пароль.

Вот и всё, аутентификации реализована.

При добавлении сервисов, достаточно будет в конфигах указывать проверку по «access.lua»:

access_by_lua_file /etc/nginx/lua/access.lua;

Спасибо за внимание.

UPD 26.03.2022 (спасибо YourChief):
— Убрана функция nvl, за ненадобностью
— md5 при генерации токена заменено на HMAC
— В токен добавлено время его жизни
— md5 и HMAC используются встроенные в nginx

Похожее:  python - Неверный токен или токен с истекшим сроком действия. Запросить новый токен через Tweepy? -

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *