Nginx reverse proxy with Windows authentication using NTLM

Does anyone know if it is possible to do reverse proxies using Windows authentication using NTLM? I can not find any example. What should be the values โ€‹โ€‹of the more_set_headers field?

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"'; } 

If I get access to the node directly, authentication will succeed, if I get access with a reverse proxy, authentication is interrupted every time.

+10
reverse-proxy nginx ntlm
source share
4 answers

To enable NTLM traversal using 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 ""; ... } } 

- Ramon

+10
source share

As far as I know, in nginx this is not possible. I explored this in my depths recently. The main problem is that NTLM authentication will require the same socket for a subsequent request, but the proxy does not. Until the nginx development team provided some support for this behavior, I dealt with this by using authentication in the reverse proxy itself. I am currently doing this using apache 2.2, mod_proxy, mod_auth_sspi (not perfect, but works). Good luck Sorry nginx, I love you, but we could really use some help for this general use.

+3
source share

I have since gotten another solution for this. It's still not the same as nginx doing NTLM (which would be nice if the nginx command ever implements this). But at the moment, what I'm doing is working for us.

I wrote lua code that uses an encrypted cookie. The encrypted cookie contains the user ID, the time of its authentication, and the IP address from which it was authenticated. I am attaching this material here for reference. It is not polished, but perhaps you can use it to develop your own similar scheme.

Basically how it works:

  • If the cookie is NOT accessible or if it has expired or is invalid, nginx makes a pre-auth call to the IIS backend application that transmits the client IP address and then redirects the client to the IIS web application where I have "Windows Authentication " The pre-auth internal IIS application service generates a GUID and saves a db entry for this guid and a flag indicating that this GUID should be authenticated.
  • The browser redirects nginx to the authenticator application that passes the GUID.
  • The IIS application authenticates the user through Windows authentication and updates the db entry for this GUID and client IP address with the user ID and the authentication time.
  • The IIS application redirects the client back to the original request.
  • The nginx lua code intercepts this call and again calls the support service call to the IIS application (post-authorization) and requests the user ID and the authentication time. This information is set in an encrypted cookie and sent to the browser. The request is allowed to pass, and REMOTE_USER is sent together.
  • subsequent browser requests send a cookie, and the nginx lua code sees a valid cookie and proxies the request directly (without the need for re-authentication), passing the request header 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 - dt; local diffOriginal = 0; if do ~= nil then diffOriginal = now - do; 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 = du, t = now, o = dt } ); ngx.header["Set-Cookie"] = "pca=" .. enc.encrypt( sessionData ) .. "; path=/" end ngx.req.set_header( "REMOTE_USER", du ); 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 sample:

 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; } 
+3
source share

Ok, we wrote lua code for nginx / openresty, which solves the ntlm reverse proxy problem with some permissible restrictions and without the need for a commercial version of nginx

0
source share

All Articles