Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Nginx Reverse Proxy with simple SSO

Nginx Reverse Proxy for homelab services using SSO

NOTE: This document has now been added to the nginx-sso wiki, here. Any further updates will be made there.

Using:

Docker containers

First, create the following docker containers (feel free to adjust local volume paths as you see fit, however all of the following instructions assume these have not been changed):

nginx-rproxy
mkdir -p /srv/nginx-rproxy/{conf,certs,log}
docker run -d nginx:latest                                   \
    --name=nginx-rproxy                                      \
    -p 443:443                                               \
    -p 80:80                                                 \
    --restart=unless-stopped                                 \
    --volume="/srv/nginx-rproxy/conf:/etc/nginx/conf.d:ro"   \
    --volume="/srv/nginx-rproxy/certs:/etc/nginx/certs:ro"   \
    --volume="/srv/nginx-rproxy/log:/var/log/nginx"
nginx-sso
mkdir -p /srv/nginx-sso
docker run -d luzifer/nginx-sso:latest \
    --name=nginx-sso                   \
    -p 8082:8082                       \
    --restart=unless-stopped           \
    --volume="/srv/nginx-sso:/data"

nginx-sso Configuration

nginx-sso will create a default configuration on first start, so you can edit that as a starting point.

Configuration is in YAML, at: /srv/nginx-sso/config.yaml

Edit the file to contain the following at the bare minimum. I recommend editing after copying the existing config to another file for later reference.

---

login:
  title: "yourdomain.com - Login"
  default_method: "simple"
  hide_mfa_field: true
  names:
    simple: "Username / Password"

cookie:
  domain: ".yourdomain.com"
  # You'll want to regenerate this. Use something like: cat /dev/urandom | tr -dc 'A-Za-z0-9' | dd bs=1 count=60
  authentication_key: "5foFtWocwA3hq0tUztgMqn9xaagqNP1wFqfFyZDHTxhr154iQQ60eDI9z6oDVNHF7B"

listen:
  addr: "0.0.0.0"
  port: 8082

audit_log:
  targets:
    - fd://stdout
    - file:///var/log/nginx-sso/audit.jsonl
  events: ['access_denied', 'login_success', 'login_failure', 'logout', 'validate']
  headers: ['x-origin-uri']
  trusted_ip_headers: ["X-Forwarded-For", "RemoteAddr", "X-Real-IP"]

acl:
  rule_sets:
  - rules:
    - field: "x-host"
      regexp: ".*"
    allow: ["@admins"]

providers:
  simple:
    enable_basic_auth: false
    users:
      # This password is 'admin'. Use this to generate a new password:
      #      htpasswd -BnC 10 ""
      admin: "$2y$10$3aJxJ6ttJNPeky/bCdg1OOVvGU8pLVj9L.U9kN0F0JWLN.nt3b5WO"
    groups:
      admins: ["username"]
...

Let's Encrypt SSL certificates

Installation of acme.sh and DNS verification (you need DNS verification working for a wildcard cert) of your domain for Let's Encrypt is left as an exercise for the reader - I'm using bind's nsupdate to my own DNS servers, so YMMV.

Generate a wildcard cert with acme.sh using something like the following:

acme.sh --issue -d yourdomain.com -d \*.yourdomain.comf        \
        --dns dns_<yourdnsprovider>                            \
        --cert-file /srv/nginx-rproxy/certs/yourdomain.com.crt \
        --key-file /srv/nginx-rproxy/certs/yourdomain.com.key  \
        --reloadcmd 'docker restart nginx-rproxy'

Nginx configuration

Now we need to create some configuration for nginx. The first items to create, are some common include files. I've used /srv/nginx-rproxy/conf/include to store them.

First, ssl config, ssl.inc:

ssl_certificate     /etc/nginx/certs/yourdomain.com.crt;
ssl_certificate_key /etc/nginx/certs/yourdomain.com.key;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;

Second, nginx's auth_request parts for nginx-sso, used by your internal web services. I've named this nginx-sso_auth.inc

# Protect this location using the auth_request
auth_request /sso-auth;

# Redirect the user to the login page when they are not logged in
error_page 401 = @error401;

location /sso-auth {
    # Do not allow requests from outside
    internal;

    # Access /auth endpoint to query login state
    proxy_pass http://172.17.0.1:8082/auth;

    # Do not forward the request body (nginx-sso does not care about it)
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";

    # Set custom information for ACL matching: Each one is available as
    # a field for matching: X-Host = x-host, ...
    proxy_set_header X-Origin-URI $request_uri;
    proxy_set_header X-Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

# If the user is lead to /logout redirect them to the logout endpoint
# of ngninx-sso which then will redirect the user to / on the current host
location /sso-logout {
    return 302 https://login.yourdomain.com/logout?go=$scheme://$http_host/;
}

# Define where to send the user to login and specify how to get back
location @error401 {
    return 302 https://login.yourdomain.com/login?go=$scheme://$http_host$request_uri;
}

The following items are all placed into /srv/nginx-rproxy/conf/ as .conf files, for the main nginx.conf file inside the docker container to include.

The next file we create is a basic config for HTTP->HTTPS redirection, and for the login domain you can see in the 302 redirects above.

I've called this 000-nginx-sso.conf so that it's included first:

# Redirect all requests to this server to https
server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
        return 301 https://$host$request_uri;
}

server {
        listen 443 ssl http2;
        server_name login.yourdomain.com;

        access_log  /var/log/nginx/login.yourdomain.com_access.log;
        error_log   /var/log/nginx/login.yourdomain.com_error.log;

        include conf.d/include/ssl.inc;

        location / {
                proxy_pass http://172.17.0.1:8082/;
        }
}

That's the last of the basic support infrastructure to get things going. The following files are all basically the same, with any service specific configuration included.

Here's a basic template which I use for portainer. Name it whatever you like - I've been using <dnsname>.conf. For example, portainer.yourdomain.com.conf.

server {
        listen 443 ssl http2;
        server_name portainer.yourdomain.com;

        access_log  /var/log/nginx/portainer.yourdomain.com_access.log;
        error_log   /var/log/nginx/portainer.yourdomain.com_error.log;

        include conf.d/include/ssl.inc;
        include conf.d/include/nginx-sso_auth.inc;

        location / {

                # Automatically renew SSO cookie on request
                auth_request_set $cookie $upstream_http_set_cookie;
                add_header Set-Cookie $cookie;

                proxy_pass http://172.17.0.1:9000/;
        }
}

The url for proxy_pass is that which the nginx container can reach portainer on. In this example, I've published port 9000 on my docker host for the portainer container. You do not have to use docker only, you can point nginx at any internal IP address or hostname (if you have internal DNS working) - I have one configuration for my VMWare vcenter appliance for example.

Some services will require extra configuration to work, and that'll be another exercise for the reader. But to give one example, here's what's required for Home Assisstant:

map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
}

server {
        listen 443 ssl http2;
        server_name hass.yourdomain.com;

        access_log  /var/log/nginx/hass.yourdomain.com_access.log;
        error_log   /var/log/nginx/hass.yourdomain.com_error.log;

        include conf.d/include/ssl.inc;
        include conf.d/include/nginx-sso_auth.inc;

        location / {

                # Automatically renew SSO cookie on request
                auth_request_set $cookie $upstream_http_set_cookie;
                add_header Set-Cookie $cookie;

                proxy_pass http://172.17.0.1:8123/;

                proxy_set_header Host $host;
                proxy_redirect http:// https://;
                proxy_http_version 1.1;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
        }
}

Usage

Make sure you've restarted both containers after modifying their config:

docker restart nginx-rproxy
docker restart nginx-sso

After all that, ensure that you create DNS names in your domain pointing to the nginx server (your external public IP for example), and ensure that you're forwarding port 80 and 443 through to your docker host on your router, or however your network is configured.

Point your browser at one of the DNS names you've created, and you should get redirected to HTTPS, then on to login.yourdomain.com where you'll be presented with a login form. Log in with the user account you created, or if you used the config above for nginx-sso, it's admin/admin.

After logging in, you should be redirected again back to the dns name you started with, and have access through to your web service. You will also have access to any other service you've configured without needing to log in to the SSO backedn again... This is by design - it's Single Sign On after all.

Your session cookie with the SSO service is set to last for an hour. See nginx-sso's default config.yml that you copied (you copied it, right?) to see how to change that timeout if you want longer or shorter. By default, your cookie should get renewed as you keep using it, however I have not tested that.

If you would like to logout of the SSO session (to use another username for example), you can visit the path /sso-logout on any of your configured subdomains, and you'll get redirected back to the login page after being logged out by the SSO backend.

Final Thoughts

The SSO login page html is in /srv/nginx-sso/frontend/index.html if you wanted to adjust it, skin it, theme it, etc etc.

There's good information on the nginx-sso GitHub wiki for configuration of nginx-sso. You probably saw while editing the config file that there's fairly decent support for other authentication providers, and even 2FA.... You should use 2FA.

The default nginx-sso config above is very generous with the access acls (which are very powerful - see the nginx-sso wiki linked above). You can basically access anything you've configured once you're logged in. You can be very granular about what services a given username or group can log into.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.