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