From 4237d1566f8d4c9a308852a43750f33826de77db Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Sun, 10 Feb 2019 12:21:12 +0100 Subject: [PATCH] Import documentation from https://gist.github.com/mjbnz/b402edf819a69e517b0c59710f291da9 (#36) --- ...se-Proxy-for-homelab-services-using-SSO.md | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 Example:-Nginx-Reverse-Proxy-for-homelab-services-using-SSO.md diff --git a/Example:-Nginx-Reverse-Proxy-for-homelab-services-using-SSO.md b/Example:-Nginx-Reverse-Proxy-for-homelab-services-using-SSO.md new file mode 100644 index 0000000..cb90e15 --- /dev/null +++ b/Example:-Nginx-Reverse-Proxy-for-homelab-services-using-SSO.md @@ -0,0 +1,285 @@ +# Nginx Reverse Proxy for homelab services using SSO + +##### Using: +* Docker +* Nginx +* [nginx-sso](https://github.com/Luzifer/nginx-sso) +* [acme.sh](https://acme.sh) + +### 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 + +```sh +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 + +```sh +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. + +```yaml +--- + +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: + +```sh +acme.sh --issue -d yourdomain.com -d \*.yourdomain.comf \ + --dns dns_ \ + --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`: + +```nginx +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` + +```nginx +# 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: + +```nginx +# 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 `.conf`. For example, `portainer.yourdomain.com.conf`. + +```nginx +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: + + +```nginx +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: +```sh +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 *S*ingle *S*ign *O*n 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](https://github.com/Luzifer/nginx-sso/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.