Nginx обратный прокси с аутентификацией Windows, использующей NTLM

Кто-нибудь знает, возможно ли сделать обратный прокси с помощью проверки подлинности Windows, использующей NTLM? Я не могу найти какой-либо пример. Какими должны быть значения поля more_set_headers?

location / {
            proxy_http_version      1.1;
            proxy_pass_request_headers on;
            proxy_set_header        Host            $host;
            proxy_set_header        X-Real-IP       $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;


            more_set_input_headers  'Authorization: $http_authorization';

            proxy_set_header  Accept-Encoding  "";

            proxy_pass              http://host/;
            proxy_redirect          default;
            #This is what worked for me, but you need the headers-more mod
            more_set_headers        -s 401 'WWW-Authenticate: Basic realm="host.local"';
}

Если я получаю доступ к узлу напрямую, аутентификация удастся, если я получаю доступ с обратным прокси-сервером, аутентификация прерывается каждый раз.

Ответ 1

Чтобы включить прохождение NTLM с помощью Nginx -

upstream http_backend {
    server 2.3.4.5:80;
    keepalive 16;
}
server {
    ...
    location / {
       proxy_pass http://http_backend/;
       proxy_http_version 1.1;
       proxy_set_header Connection "";
    ...
    }
 }

- Рамон

Ответ 2

Насколько я знаю, в nginx это невозможно. Я исследовал это в глубине себя совсем недавно. Основная проблема заключается в том, что для проверки подлинности NTLM потребуется один и тот же сокет для последующего запроса, но прокси-сервер этого не делает. До тех пор, пока команда разработчиков nginx не окажет некоторую поддержку этому поведению, я справлялся с этим, применяя аутентификацию в самом обратном прокси. В настоящее время я делаю это с помощью apache 2.2, mod_proxy, mod_auth_sspi (не идеально, но работает). Удачи! Извините nginx, я люблю вас, но мы могли бы действительно использовать некоторую помощь для этого общего использования.

Ответ 3

У меня с тех пор появилось другое решение для этого. Это все равно не то же самое, что nginx делает NTLM (что будет хорошо, если команда nginx когда-либо реализует это). Но на данный момент то, что я делаю, работает для нас.

Я написал код lua, в котором используется зашифрованный файл cookie. Зашифрованный файл cookie содержит идентификатор пользователя, время его аутентификации и ip-адрес, с которого он аутентифицировался. Я прикрепляю этот материал здесь для справки. Он не отполирован, но, возможно, вы можете использовать его для разработки собственной подобной схемы.

В основном, как это работает:

  • Если файл cookie НЕ доступен или если он истек или недействителен, nginx делает служебный вызов (pre-auth) для бэкэнд-приложения IIS, передающего IP-адрес клиента, а затем перенаправляет клиента в веб-приложение IIS, где у меня есть "Windows Authentication". Внутренняя служба IIS-приложения pre-auth генерирует GUID и сохраняет запись в db для этого guid и флаг, указывающий, что этот GUID должен быть аутентифицирован.
  • Браузер перенаправляется nginx в приложение-аутентификатор, передающее GUID.
  • Приложение IIS аутентифицирует пользователя через проверку подлинности Windows и обновляет запись db для этого GUID и IP-адреса клиента с идентификатором пользователя и временем, прошедшим проверку подлинности.
  • Приложение IIS перенаправляет клиент обратно на исходный запрос.
  • Код nginx lua перехватывает этот вызов и снова вызывает вызов службы поддержки в приложение IIS (пост-авторизация) и запрашивает идентификатор пользователя и время, прошедшее проверку подлинности. Эта информация задается в зашифрованном файле cookie и отправляется в браузер. Запрос разрешен для прохождения, и REMOTE_USER отправляется вместе.
  • последующие запросы браузера передают файл cookie, а код nginx lua видит действительный файл cookie и проксирует запрос напрямую (без необходимости повторной проверки подлинности), передавая заголовок запроса REMOTE_USER.

access.lua:

local enc     = require("enc");
local strings = require("strings");
local dkjson  = require("dkjson");


function beginAuth()
    local headers = ngx.req.get_headers();
    local contentTypeOriginal = headers["Content-Type"];
    print( contentTypeOriginal ); 
    ngx.req.set_header( "Content-Type", "application/json" );
    local method = ngx.req.get_method();
    local body = "";
    if method == "POST" then
        local requestedWith = headers["X-Requested-With"];
        if requestedWith ~= nil and requestedWith == "XMLHttpRequest" then
            print( "bailing, won't allow post during re-authentication." );
            ngx.exit(ngx.HTTP_GONE); -- for now, we are NOT supporting a post for re-authentication.  user must do a get first.  cookies can't be set on these ajax calls when redirecting, so for now we can't support it.
            ngx.say("Reload the page.");
            return;
        else
            print( "Attempting to handle POST for request uri: " .. ngx.var.uri );
        end
        ngx.req.read_body();
        local bodyData = ngx.req.get_body_data();
        if bodyData ~= nil then
            body = bodyData;
        end
    end
    local json = dkjson.encode( { c = contentTypeOriginal, m = method, d = body } );
    local origData = enc.base64encode( json );
    local res = ngx.location.capture( "/preauth", { method = ngx.HTTP_POST, body = "{'clientIp':'" .. ngx.var.remote_addr .. "','originalUrl':'" .. ngx.var.FrontEndProtocol .. ngx.var.host .. ngx.var.uri .. "','originalData':'" .. origData .. "'}" } );
    if contentTypeOriginal ~= nil then
        ngx.req.set_header( "Content-Type", contentTypeOriginal );
    else
        ngx.req.clear_header( "Content-Type" );
    end
    if res.status == 200 then
        ngx.header["Access-Control-Allow-Origin"] = "*";
        ngx.header["Set-Cookie"] = "pca=guid:" .. enc.encrypt( res.body ) .. "; path=/"
        ngx.redirect( ngx.var.authurl .. "auth/" .. res.body );
    else
        ngx.exit(res.status);
    end
end

function completeAuth( cookie )
    local guid = enc.decrypt( string.sub( cookie, 6 ) );
    local contentTypeOriginal = ngx.header["Content-Type"];
    ngx.req.set_header( "Content-Type", "application/json" );
    local res = ngx.location.capture( "/postauth", { method = ngx.HTTP_POST, body = "{'clientIp':'" .. ngx.var.remote_addr .. "','guid':'" .. guid .. "'}" } );
    if contentTypeOriginal ~= nil then
        ngx.req.set_header( "Content-Type", contentTypeOriginal );
    else
        ngx.req.clear_header( "Content-Type" );
    end
    if res.status == 200 then
        local resJson = res.body;
        -- print( "here a1" );
        -- print( resJson );
        local resTbl = dkjson.decode( resJson );
        if resTbl.StatusCode == 0 then
            resTbl = resTbl.Result;
            local time = os.time();
            local sessionData = dkjson.encode( { u = resTbl.user, t = time, o = time } );
            ngx.header["Set-Cookie"] = "pca=" .. enc.encrypt( sessionData ) .. "; path=/"
            ngx.req.set_header( "REMOTE_USER", resTbl.user );
            if resTbl.originalData ~= nil and resTbl.originalData ~= "" then
                local tblJson = enc.base64decode( resTbl.originalData );
                local tbl = dkjson.decode( tblJson );
                if tbl.m ~= nil and tbl.m == "POST" then
                    ngx.req.set_method( ngx.HTTP_POST );
                    ngx.req.set_header( "Content-Type", tbl.c );
                    ngx.req.read_body();
                    ngx.req.set_body_data( tbl.d );
                end
            end
        else
            ngx.log( ngx.ERR, "error parsing json " .. resJson );
            ngx.exit(500);
        end
    else
        print( "error completing auth." );
        ngx.header["Set-Cookie"] = "pca=; path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; token=deleted;"
        print( res.status );
        ngx.exit(res.status);
    end
end


local cookie = ngx.var.cookie_pca;
print( cookie );
if cookie == nil then 
    beginAuth();
elseif strings.starts( cookie, "guid:" ) then
    completeAuth( cookie );
else
    -- GOOD TO GO...
    local json = enc.decrypt( cookie );
    local d = dkjson.decode( json );
    local now = os.time();
    local diff = now - d.t;
    local diffOriginal = 0;
    if d.o ~= nil then 
        diffOriginal = now - d.o;
    end
    if diff > 3600 or diffOriginal > 43200 then
        beginAuth();
    elseif diff > 300 then
        print( "regenerating new cookie after " .. tostring( diff ) .. " seconds." );
        local sessionData = dkjson.encode( { u = d.u, t = now, o = d.t } );
        ngx.header["Set-Cookie"] = "pca=" .. enc.encrypt( sessionData ) .. "; path=/"
    end
    ngx.req.set_header( "REMOTE_USER", d.u );
end

strings.lua:

local private = {};
local public = {};
strings = public;

function public.starts(String,Start)
   return string.sub(String,1,string.len(Start))==Start
end

function public.ends(String,End)
   return End=='' or string.sub(String,-string.len(End))==End
end

return strings;

enc.lua:

-- for base64, try something like: http://lua-users.org/wiki/BaseSixtyFour
local private = {};
local public = {};
enc = public;

local aeslua = require("aeslua");

private.key = "f8d7shfkdjfhhggf";

function public.encrypt( s )
    return base64.base64encode( aeslua.encrypt( private.key, s ) );
end

function public.decrypt( s )
    return aeslua.decrypt( private.key, base64.base64decode( s ) );
end

return enc;

образец nginx conf:

upstream dev {
    ip_hash;
    server app.server.local:8080;
}
set $authurl http://auth.server.local:8082/root/;
set $FrontEndProtocol https://;
location / {
    proxy_pass     http://dev/;
    proxy_set_header Host $host;
    proxy_redirect default;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_buffers 128 8k;
    access_by_lua_file conf/lua/app/dev/access.lua;
}

Ответ 4

Хорошо, мы написали код lua для nginx/openresty, который решает проблему обратного прокси ntlm с некоторыми разрешимыми ограничениями и без необходимости коммерческой версии nginx