Nginx – a correct way(s) to allow login to an IIS site through a reverse proxy

iisnginxpasswordreverse-proxy

I have an nginx reverse proxy, and I'm trying to get an IIS website with login to work behind it. I've found this question asked several times, but each answer appears to be different, and some of the problems deviate a bit from the problem I'm having.

With my current config, I am able to get to the login, but I'm getting a 401 error, and it keeps asking for credentials.

My current config:

/etc/nginx/sites-available/default

server {
    listen 80 default;
    server_name _;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name server2.mydomain.com;

    ssl_certificate /usr/local/nginx/conf/mydomain.com.crt;
    ssl_certificate_key /usr/local/nginx/conf/mydomain.com.key;
    ssl_session_cache shared:SSL:10m;

    ssl_session_timeout 5m;

    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://192.168.0.20:80;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_redirect http:// $scheme://;

    }
}

My question is, what is the correct way that this is supposed to work?

Here's what I've researched so far:

This link, says it isn't possible.

This link, says I should use keepalive on upstream. That may be the answer, but whenever I add an upstream server2.mydomain.com with any configuration in it, nginx fails to restart. I'm sure I have some syntax incorrect, but I've tried several things. I'd post everything I've tried, but I'm not even certain this is the correct approach, and I've tried so many things, I figured it would be easier just to ask the general population how this is done.

This link, says you can just add a proxy_pass_request_headers on; line, and it will somehow work…but it doesn't for me.

This link, seemed the most likely to work, but after trying to figure out how to encode to base64 using this link, I was getting nowhere.

Any help is greatly appreciated. Here's a previous question I've asked, that are related to this, but are different questions.


EDIT1

I apologize for this being so late, was gone for a bit.

Here are my access logs for the nginx reverse proxy server. 192.168.0.5 is my client FYI.

These logs are from me accessing the site, getting a login prompt, trying the login once (without success), and exiting the login prompt.

/var/log/nginx/access.log

192.168.0.5 - - [09/Feb/2016:14:04:14 -0600] "GET / HTTP/1.1" 401 1293 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0"
192.168.0.5 - - [09/Feb/2016:14:04:31 -0600] "GET / HTTP/1.1" 401 341 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0"
192.168.0.5 - - [09/Feb/2016:14:04:31 -0600] "GET / HTTP/1.1" 401 1293 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0"
192.168.0.5 - - [09/Feb/2016:14:04:34 -0600] "GET /favicon.ico HTTP/1.1" 401 1293 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0"

/var/log/nginx/error.log

This file is empty

IIS Logs

2016-02-11 19:39:22 192.168.0.20 GET /login - 80 - 192.168.0.10 Mozilla/5.0+(X11;+Linux+x86_64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Ubuntu+Chromium/45.0.2454.101+Chrome/45.0.2454.101+Safari/537.36 401 2 5 125
2016-02-11 19:39:28 192.168.0.20 GET /login - 80 - 192.168.0.10 Mozilla/5.0+(X11;+Linux+x86_64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Ubuntu+Chromium/45.0.2454.101+Chrome/45.0.2454.101+Safari/537.36 401 1 21480424 0
2016-02-11 19:39:36 192.168.0.20 GET /login - 80 - 192.168.0.10 Mozilla/5.0+(X11;+Linux+x86_64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Ubuntu+Chromium/45.0.2454.101+Chrome/45.0.2454.101+Safari/537.36 401 1 21480724 0
2016-02-11 19:40:16 192.168.0.20 GET /login - 80 - 192.168.0.10 Mozilla/5.0+(X11;+Linux+x86_64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Ubuntu+Chromium/45.0.2454.101+Chrome/45.0.2454.101+Safari/537.36 401 1 21407424 15
2016-02-11 19:40:22 192.168.0.20 GET /login - 80 - 192.168.0.10 Mozilla/5.0+(X11;+Linux+x86_64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Ubuntu+Chromium/45.0.2454.101+Chrome/45.0.2454.101+Safari/537.36 401 1 21480742 0

There are no security errors for logins, so it's never actually submitting the login to the system I guess. It simply gives me the login popup again every time I hit "login".

Live HTTP Headers plugin output

https://server2.mydomain.com/

GET / HTTP/1.1
Host: server2.mydomain.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive

HTTP/1.1 401 Unauthorized
Server: nginx/1.4.6 (Ubuntu)
Date: Tue, 09 Feb 2016 19:21:04 GMT
Content-Type: text/html
Content-Length: 1293
Connection: keep-alive
WWW-Authenticate: NTLM
WWW-Authenticate: Negotiate
X-Powered-By: ASP.NET
----------------------------------------------------------
https://server2.mydomain.com/

GET / HTTP/1.1
Host: server2.mydomain.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Authorization: NTLM TlRNTVMTUAAAB4IIAAAAAAAAAAAAACDAFGAAAAAAAAAAAAAAAAA=

HTTP/1.1 401 Unauthorized
Server: nginx/1.4.6 (Ubuntu)
Date: Tue, 09 Feb 2016 19:22:00 GMT
Content-Type: text/html; charset=us-ascii
Content-Length: 341
Connection: keep-alive
WWW-Authenticate: NTLM TlRMTVNTUAACAAAAGgAaADgAAuzKir6ADucAAAAAAAAAAL4A&%$DSDADvgBSAAAABgGxHQAAAA9HAEUARQBLAFMAQQBOAEQATgBFAFIARABTAAIAGgBHRQBLAFMAQQBOAEQATgBFDFAFIARABTAAEADABLAEUATABWAEkATgAEACIAZwBlAGUAawBzAGEAbgBkAG4AZQByAGQAcwAuAGMAbwBtAAMAMABLAEUATABWAEkATgAuAGcAZQBlAGsAcwBhAG4AZABuAGUAcgBkAHMALgBjAG8AbQAFACIAZwBlAGUAawBzAGEAbgBkAG4AZQByAGQAcwAuAGMAbwBtAAcACAABRuM1b2PRAQAAAAA=
----------------------------------------------------------
https://server2.mydomain.com/

GET / HTTP/1.1
Host: server2.mydomain.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHAAAADqAOoAiAAAAAAAAAAGgAaAEAAAAAWABYAWgAAAAAAAAAAAAAABYIIAGEAbQBhAG4AZABhAC4AYgBsAG8AdQBuAHQAVwBPAFIASwBTAFQAQQBUAEkATwBOAHorEf/j46zta4wONTUAADAA-98uH//ZL0Am16vGzdWutoAAAAGAAYAHQAAAAAAAACFdzVEB9QHmLWLCuQQAAAAAAgAaAEcARQBFAEsAUwBBAE4ARABOAEUAUgBEAFMAAQAMAEsARQBMAFYASQBOAAQAIgBnAGUAZQBrAHMAYQBuAGQAbgBlAHIAZABzAC4AYwBvAG0AAwAwAEsARQBMAFYASQBOAC4AZwBlAGUAawBzAGEAbgBkAG4AZQByAGQAcwAuAGMAbwBtAAUAIgBnAGUAZQBrAHMAYQBuAGQAbgBlAHIAZABzAC4AYwBvAG0ABwAIAAFG4zVvY9EBAAAAAA==

HTTP/1.1 401 Unauthorized
Server: nginx/1.4.6 (Ubuntu)
Date: Tue, 09 Feb 2016 19:22:00 GMT
Content-Type: text/html
Content-Length: 1293
Connection: keep-alive
WWW-Authenticate: NTLM
WWW-Authenticate: Negotiate
X-Powered-By: ASP.NET
----------------------------------------------------------

EDIT2 – For clarity, here is the setup by IP.


Client machine

192.168.0.5

Ubuntu 14.04 desktop


Reverse proxy server

192.168.0.10

nginx 1.4.6

Ubuntu 14.04 server


Server 2

192.168.0.20

server2.mydomain.com

apache2

Ubuntu 14.04 server


EDIT3 – Maybe this works and I'm doing it wrong,….maybe not

From this post, the answer written by Fizz.

I tried this below

/etc/nginx/sites-available/default

server {
    listen 80 default;
    server_name _;
    return 301 https://$host$request_uri;
}

upstream server2.mydomain.com {
        server 192.168.0.20:80
keepalive 16;
}

server {
    listen 443 ssl;
    server_name server2.mydomain.com;

    ssl_certificate /usr/local/nginx/conf/mydomain.com.crt;
    ssl_certificate_key /usr/local/nginx/conf/mydomain.com.key;
    ssl_session_cache shared:SSL:10m;

    ssl_session_timeout 5m;

    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://192.168.0.20:80;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_redirect http:// $scheme://;

    }
}

Same results. Based on the other answers….maybe apache2 is the better way to go?


EDIT4 – Edit based on answer from Maxim Dounin

I'm trying to use Nginx 1.9.9 now, and the stream proxy method mentioned in Maxim Dounin's answer.

I compiled from source, so my file locations are different now.

/opt/nginx/nginx.conf

worker_processes 1;
events {
    worker_connections 1024;
}

stream {
    upstream backend {
       hash $remote_addr consistent;

       server server2.mydomain.com:80 weight=5;
       server 192.168.0.20:80            max_fails=3 fail_timeout=30s;

    }

    server {
        listen 443 ssl;        #Line 27
        server_name server2.mydomain.com;

        ssl_certificate /usr/local/nginx/conf/mydomain.com.crt;
        ssl_certificate_key /usr/local/nginx/conf/mydomain.com.key;
        ssl_session_cache shared:SSL:10m;

        ssl_session_timeout 5m;

        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass backend;
    }

#    server {
#        listen [::1]:12345;
#        proxy_pass unix:/tmp/stream.socket;
#    }
}

I commented out the last recommended server line, because I had no idea what to do with that, but my config file doesn't get there anyway due to other errors. Right now my /opt/nginx/logs/error.log is having problems with line 27

the "ssl" parameter requires ngx_stream_ssl_module in /opt/nginx/nginx.conf:27

I definitely compiled with the ngx_stream_ssl_module because when I do a nginx -V I get configure arguments: --with-stream

Hopefully I'm on the right track.

Best Answer

The problem is NTLM authentication (note WWW-Authenticate: NTLM ...), AKA Windows Authentication.

NTLM authentication authenticates connections instead of requests, and this is somewhat contradicts HTTP protocol, which is expected to be stateless. As a result it doesn't generally work through proxies, including nginx.

Simplest solution would be to change authentication to "Basic" on IIS side. If this not an option for some reason, other possibilities include:

  • Use stream proxy as available in nginx 1.9.x. This will map connections from clients to upstream server, and thus NTLM authentication will work.

  • Use ntlm feature as available in commercial nginx version.

Note that there are some recommendations to use upstream with keepalive for NTLM authentication to work. These recommendations are incorrect and harmful - unless you are using the proxy for just one user. And the worst thing is that it may appear to work correctly. The problem is that keepalive connections to upstream server are kept in a common cache, and these connections can be used for all clients. So if there is an authenticated connection in the cache, an unrelated client who happens to use this connection will be able to bypass authentication.

Related Topic