Safely expose your services to the world using nginx and 2FA

Creating and securing your reverse proxies with SWAG and Authelia

Containers Sep 25, 2021

Reverse proxies are a great way to access services and apps on your NAS when you're not on your local network. It's also a more secure way to allow others to access your website or service than giving them your public IP address and forwarding your ports through your router. In fact, that last option exposes your whole network to the world and it won't be long before you start seeing intrusion attempts.

Instead, we will use a method which means we only need to keep one, secure, port open.

With the uptake of https, and using a CDN like Cloudflare, you can now protect your public IP from prying eyes, and ensure connections are secure and authorized. We're not going to go into setting up Cloudflare in this article (I'm going to assume you've got your own fully qualified domain name (FQDN) and CDN already set up, and know how to create your A or CNAME records for your reverse proxies) but I will show you how to set up two services:

  1. SWAG: a 3-in-1 service which acts as your nginx reverse proxy server, your SSL certificate requester via LetsEncrypt, and fail2ban jail server (fail2ban is a great tool which monitors attempted auths to your services, and bans IPs which fail to provide correct auth within a certain amount of attempts/time)
  2. Authelia: an authorization service which will act as a single authorizer for your services

The way this works is relatively simple (though it may not look it at first). When your CDN receives a DNS query for your sub.domain.com and forwards it to your public IP, your nginx server sees that query, checks the app.subdomain.conf files and routes it to your app or container. So far so easy.

The app.subdomain.conf file also has an option to route the request through Authelia first. So when nginx receives the query, instead of just saying 'ok here's radarr' it goes 'hold up, this is meant to be sent to authelia first', sends it there, and only once the correct auth has been given by authelia will it then say 'ok, now I can send you to radarr'.

Enough of that, let's get these set up. You can jump to a specific part of the page by opening this drop down box:


SWAG

Prepping for SWAG

There are a few things you will need for SWAG to work effectively:

  1. Knowledge of how to use docker-compose. If you don't, please read up on it here
  2. Knowledge of using .env files to pass information to docker-compose. If you don't know how or don't want to use .env files, then remove anything in the below compose template that begins with a $ (dollar sign) and type in your own information for that variable
  3. A pre-created folder already set up on your system which we will point our SWAG config at (make sure permissions are set up to allow docker to read/write to it)
  4. A docker network created and ready (which I've called proxy)
  5. The details for your certificate validation method (my example below uses DNS validation with Cloudflare as it's my CDN, which is doubly secure as DNS validation doesn't require port 80 to be open on my router for LetsEncrypt, which other methods do)
  6. Port 443 forwarded from your router to your SWAG host machine - note that if you change the host port in the container setup below (say you use 444 instead) you will need to make sure that port 443 from your router is forwarded to port 444 on your host machine
  7. The email address you registered or will use for your let's encrypt certificates
  8. A Maxmind License Key if you want to use geoIP restrictions, such as allowing connections from Mongolia but not from Argentina (go to the maxmind website to create your free account and create your license, be sure to take note of it)
  9. A general read of the SWAG documentation is always a good shout, you can find that here
💡
Since writing this article, linuxserver have modified the method used to do geoIP blocking (although it still requires the maxmind key mentioned above and included in the compose file below). You can visit this page in their documentation to read up on the steps needed to enable it

Creating the SWAG container

First up we're going to create our docker proxy network. SSH into your machine and type in:

sudo docker network create proxy

This creates the proxy network we will connect our SWAG container to.

Once that's done, copy and paste this template to create your docker-compose.yml file:

services:  
  swag:
    image: ghcr.io/linuxserver/swag
    container_name: swag
    cap_add:
      - NET_ADMIN
    environment:
      - TZ=$TZ #change to your timezone
      - URL=$DOMAINNAME #change to the domain you want to protect with a certificate
      - SUBDOMAINS=wildcard
      - VALIDATION=dns
      - DNSPLUGIN=cloudflare
      - EMAIL=$EMAIL ADDRESS #change, can be any email address
      - ONLY_SUBDOMAINS=false
      - MAXMINDDB_LICENSE_KEY=$MAXMINDDB #remove if not necessary
      - DOCKER_MODS=linuxserver/mods:swag-cloudflare-real-ip|linuxserver/mods:swag-auto-reload|linuxserver/mods:universal-docker|linuxserver/mods:swag-auto-proxy #delete if not wanted or necessary
      - DOCKER_HOST=socket-proxy #delete if not wanted or necessary
    volumes:
      - $DOCKERDIR/swag:/config #change before the `:` to your swag path
      #uncomment the next line if you haven't set up a socket-proxy container
      #- /var/run/docker.sock:/var/run/docker.sock:ro 
    ports:
      - 443:443 #change before the ':' if necessary, take note of port-forwarding comment at point 6 above if you do change it
      - 80:80 #change before the ':' if necessary
    restart: unless-stopped
    networks:
      - proxy
      - socket_proxy

networks:
  proxy:
    external: true
  socket_proxy:
    external: true
port 80 isn't 100% necessary if you are using DNS validation. all 
💡
For parts of the above to work as intended, you should set up a socket.proxy container. Follow the link here to set it up. If you don't, remove all mention of the socket_proxy network, the universal-docker and auto-proxy mods in the environment block below, and uncomment the volume mapping of - /var/run/docker.sock:/var/run/docker.sock:ro.

Let's break this down a little:

  • URL is where you put your domain name, without any http, https or www
  • SUBDOMAINS can either be a list of your subdomains for which SWAG will request SSL certificates (i.e. SUDBOMAINS=radarr,sonarr,prowlarr,nextcloud etc.) OR as in the above, it can be a wildcard, which will request a *.domain.com certificate, good for each and every sub.domain.com you could want to create
  • VALIDATION and DNSPLUGIN are specific to your preferences, and it can be found in their documentation here
  • EMAIL is pretty self explanatory
  • ONLY_SUBDOMAINS can either be true or false. If true, it means that SWAG will only request the specified subdomain or wildcard certificate. If false, it will also request a domain.com certificate as well
  • DOCKER_MODS adds 3 very helpful tools, separated by the | character. The first allows your SWAG container to read actual incoming IPs rather than just see everything as originating from your container IP (this only works if you're using Cloudflare as your domain host). The second removes the need to completely restart the container when you make a change to a proxy.conf or other nginx-related files. The third (which requires the universal-docker and auto-proxy mods) allows SWAG to automatically create reverse proxies using labels
  • volumes, ports and networks should be self explanatory

Preparation required for creating your first reverse proxy

💡
before doing this, I highly recommend you create your A or CNAME for one or more of your services on your CDN. A good first option would be something like radarr or sonarr

Once you've created your compose file, create the container with docker-compose up -d and confirm that it's running.

If you're using Cloudflare

(If you're not, jump to here)

First we're going to change the cloudflare.ini file, which can be found inside the dns-conf directory. Here we need to do 2 things:

  1. input the email address you use to log in to your Cloudflare account
  2. input your Global API Key (by clicking the link and then viewing your global api key)
  3. restart your SWAG container (may be unnecessary but just in case)

Cloudflare and proxying your connection (orange cloud)

Once of Cloudflare's best features is that it will protect your public IP by proxying connections through one of its IPs. To make use of this, you need to do a few things.

  • On your Cloudflare DNS page, make sure you've toggled on the orange cloud which says 'Proxied' next to it
  • Navigate to your SWAG config files, and locate nginx.conf
  • Locate the http block, and inside the { } brackets, add the following lines:
    # docker mod cloudflare_real-ip
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    include /config/nginx/cf_real-ip.conf;

I put it at the bottom, which means it looks like the screenshot below, but you can put it anywhere you want within the brackets:

  • Save the file, and restart SWAG (if necessary)

For all users

  • Navigate to the nginx folder and select the ssl.conf folder, ensuring that at least the following lines are uncommented, meaning the whole file should look like this:
# Certificates
ssl_certificate /config/keys/letsencrypt/fullchain.pem;
ssl_certificate_key /config/keys/letsencrypt/privkey.pem;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /config/keys/letsencrypt/fullchain.pem;

# Diffie-Hellman Parameters
ssl_dhparam /config/nginx/dhparams.pem;

# Enable TLS 1.3 early data
ssl_early_data on;

# HSTS, remove # from the line below to enable HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Optional additional headers
#add_header Cache-Control "no-transform" always;
add_header Content-Security-Policy "upgrade-insecure-requests; frame-ancestors 'self'";
#add_header Referrer-Policy "same-origin" always;
add_header X-Content-Type-Options "nosniff" always;
#add_header X-Frame-Options "SAMEORIGIN" always;
#add_header X-UA-Compatible "IE=Edge" always;
add_header X-XSS-Protection "1; mode=block" always;
you can uncomment other optional headers if you know what you're doing
  • Save and close this file
💡
I always prefer having HSTS enabled, however some people may have use-cases where that's not necessary. Comment it again by putting a '#' in front of the line if you want
  • We shouldn't need to touch any other .conf files in this directory just yet. Instead, inside nginx, navigate to the proxy-confs directory, and take a look at all of the 'sample' .confs. These are what we and SWAG use to set up a reverse proxy

Creating a reverse proxy using the auto-proxy docker mod

Provided SWAG has a template service.subdomain.conf.sample file for the service you want to reverse proxy, you can use this method. If it doesn't, you'll need to jump to here.

I'm going to assume you followed the steps on my socket proxy article already. If you haven't, then you're going to need to add a volume mapping to your SWAG container:

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro

Either way, we're going to jump straight in and create our radarr container which will tell SWAG to configure the correct proxy.conf.

Check out the compose file below:

networks:
  default:
    name: proxy
    external: true

services:
  radarr: #movie search agent
    image: ghcr.io/linuxserver/radarr
    container_name: radarr
    environment:
      - PUID=$PUID
      - PGID=$PGID
      - TZ=$TZ
      - UMASK=022
    volumes:
      - $DOCKERDIR/Radarr:/config
      - $MEDIADIR:/media
    ports:
      - 7878:7878
    labels:
      - swag=enable
    restart: unless-stopped

It's a standard radarr compose file, but we've done a few things:

  • We've set the default network to be the same that SWAG uses, called proxy
  • We've added a label which, when SWAG scans the docker socket, enables it to see that radarr wants a proxy.conf

We can see this taking place in our SWAG logs:

**** Remote docker service socket-proxy will be used ****
s6-rc: info: service init-mod-universal-docker-setup successfully started
s6-rc: info: service init-mod-swag-auto-proxy-setup: starting
**** libmaxminddb already installed, skipping ****
**** Installing all mod packages ****
fetch http://dl-cdn.alpinelinux.org/alpine/v3.15/main/x86_64/APKINDEX.tar.gz
s6-rc: info: service init-mod-swag-cloudflare-real-ip-setup successfully started
fetch http://dl-cdn.alpinelinux.org/alpine/v3.15/community/x86_64/APKINDEX.tar.gz
**** Using preset proxy conf for radarr ****
s6-rc: info: service init-mod-swag-auto-proxy-setup successfully started
(1/1) Installing inotify-tools (3.20.11.0-r0)
Executing busybox-1.34.1-r7.trigger
OK: 252 MiB in 231 packages

Note line 9, Using preset proxy conf for radarr.

And that's it. It may need a few seconds to configure, and if you have many containers with the label it may take even longer, but you now have a working auto-proxy service.

Creating your reverse proxy using the template .conf file

This is what you'll need to do when it's not possible to use the auto-proxy mod.

Head over to whatever you set as your $DOCKERDIR/swag folder on your host machine. Via SMB in Windows, it should look something like this:

💡
Make sure your mapped directory has the relevant permissions for Docker to write to it, otherwise the above won't populate and your container won't start properly

We're going to stick with radarr as a reverse proxy, and I'm going to assume you've already set up your A or CNAME for it.

  • Locate the radarr.subdomain.conf.sample file, and open it. You should see something like this:
## Version 2021/05/18
# make sure that your dns has a cname set for radarr and that your radarr container is not using a base url

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name radarr.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    # enable for ldap auth, fill in ldap details in ldap.conf
    #include /config/nginx/ldap.conf;

    # enable for Authelia
    #include /config/nginx/authelia-server.conf;

    location / {
        # enable the next two lines for http auth
        #auth_basic "Restricted";
        #auth_basic_user_file /config/nginx/.htpasswd;

        # enable the next two lines for ldap auth
        #auth_request /auth;
        #error_page 401 =200 /ldaplogin;

        # enable for Authelia
        #include /config/nginx/authelia-location.conf;

        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app radarr;
        set $upstream_port 7878;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;

    }

    location ~ (/radarr)?/api {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app radarr;
        set $upstream_port 7878;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;

    }
}
your version number may be different depending on how long ago I first started using SWAG

Again let's break this down a little bit into the various blocks:

  • server: tells the SWAG container various things such as which port to listen in on and which subdomain will be forwarded by the CDN. It has options to uncomment for LDAP or Authelia integration (we'll come back to the Authelia stuff later on)
if you didn't set your A or CNAME record as radarr, but something like movies for instance, change this in server_name radarr.*; to server_name movies.*;
  • location block 1 and 2: we're interested only in the following (in both blocks):
    include /config/nginx/proxy.conf;
    include /config/nginx/resolver.conf;
    set $upstream_app radarr;
    set $upstream_port 7878;
    set $upstream_proto http;
    proxy_pass $upstream_proto://$upstream_app:$upstream_port;
you'll notice it's the same in both blocks

In fact, we're only interested in the following two lines:

    set $upstream_app radarr;
    set $upstream_port 7878;
these tell SWAG where to look for the service
  • If you've used the default container name and port when creating your radarr container, those being radarr and 7878, and you've got your radarr and SWAG containers on the same docker network then you don't need to change a thing
  • If however you did change your port or container name, then you need to reflect those changes here
  • If you have your SWAG and radarr containers on different docker networks, then where it says set $upstream_app radarr;, you must change radarr to the IP of your host machine, without any http or https such as 192.168.1.10
you need to make any required changes in both blocks
  • When you think you've got this ready to go, rename the file as radarr.subdomain.conf. Do not only save it, as the .sample on the end means that SWAG will not read it
  • You must now restart your SWAG container - any change to a .conf file will not be read other than at container start-up
  • You should now be able to go to radarr.yourdomain.com and access your radarr gui (or movies.yourdomain.com or whatever.yourdomain.com which matches the A or CNAME you set on your CDN)

And with that you've set up your first reverse proxy. Rinse and repeat for any and all other services. Note that some of the .sample files will have differences, either more or less dependent on the service's requirements.

You may also find that a .sample file doesn't exist for your specific service. In that case, choose one which you think will likely fit, make sure you change the name in the server_name part of the server block to your A or CNAME record, change the relevant parts of the location block(s) and it should work.


A few notes on Fail2ban

Some configuration is required for fail2ban to work in the way you want it to. The SWAG github repo has info here which I recommend reading, but essentially you will want to go through your jail.local in the fail2ban directory. It's worth noting that while 5 jails are included by default in the installation, but other jails can be added by creating their files with .local rather than .conf (the .conf files are recreated every time the container is restarted).  

It's possible that your fail2ban instance may read every IP as the docker bridge network gateway it's on (i.e. 172.xx.xx.1). Specific to Synology, this link has a work around for iptables, but may be applicable to other systems. If you think you may get blocked by default, it's worth disabling your banaction in jail.local by commenting it out (putting a # in front of the line) until you're confident fail2ban is reading IPs correctly.

If you're proxying your domain behind Cloudflare, then everything will come through as one of Cloudflare's proxy IP addresses. While it's not going to block you, it also doesn't help you understand your real traffic, and again could just end up blocking Cloudflare's proxy and no external IP would be able to access your service. I've already included the dockermod in the docker-compose file for Cloudflare to pass the real IP to the container, however  you also need to make a change to the nginx.conf file. Click here to get the instructions.

💡
It's also worth noting that IP obfuscation, either due to docker networking or proxying, will affect the accuracy of your GeoIP tracking via maxmind

Authelia

Now that you've set up your reverse proxy, you've decided you want to add some security to access it. Enter Authelia.

Buckle up, this will take a bit of time.

Prepping for Authelia

To create and run this container, you'll need the following prepped:

  1. The ability to create a reverse proxy to your Authelia container (handy that you've just learnt how to do that from SWAG hey? Go search for the authelia.subdomain.conf.sample file)
  2. The following folders set up in your $DOCKERDIR: authelia, and subdirectories inside called app, redis and sql
  3. A docker network set up called auth

Creating the necessary containers

The below is a template compose which will create the Authelia service, as well as a database container, a database management container, and a redis container. If you already have a database container you want to use, you do not need to create them again in this stack, and the auth-mysql, auth-phpmyadmin and auth-redis services can be removed. I however like to have a completely separate database container for Authelia.

networks:
  auth:
    external: true
  proxy:
    external: true

services:
  authelia:
    image: authelia/authelia
    container_name: authelia
    ports:
      - 9091:9091 #change before the ':' as necessary
    environment:
      - PUID=$PUID #change to your user ID
      - PGID=$PGID #change to your group ID
    depends_on:
      - auth-redis
      - auth-mysql
    volumes:
      - $DOCKERDIR/authelia/app:/config/ #change `$DOCKERDIR` to your docker directory
    restart: unless-stopped
    networks:
      - auth
      - proxy

  auth-mysql:
    image: mysql:latest
    container_name: authelia_mysql
    volumes:
      - $DOCKERDIR/authelia/sql:/var/lib/mysql #change the local path to your NAS location where you want the DB data to live
    environment:
      - MYSQL_ROOT_PASSWORD=$MYSQLROOTPWD #enter a password for your main sql root user account 
    ports:
      - 3306:3306 #change before the ':' as necessary
    restart: unless-stopped
    networks:
      - auth
    
  auth-phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    container_name: authelia_php
    ports:
      - 81:80 #changed because we already used port 80 for SWAG
    environment:
      - PMA_PORT=3306 #enter the same port that you have your auth-mysql running on
      - PMA_HOST=auth-mysql
    restart: unless-stopped
    networks:
      - auth
  
  auth-redis:
    image: redis:latest
    container_name: authelia_redis
    volumes:
      - $DOCKERDIR/authelia/redis:/data #change the local path to your NAS location where you want the DB data to live
    ports:
      - 6379:6379 #change before the ':' as necessary
    restart: unless-stopped
    networks:
      - auth
make changes where indicated or necessary

Once more, let's break this down:

  • authelia - depends_on both the mysql and redis containers, meaning it won't start up unless they also have started up; networks to both auth and proxy, as it needs to communicate with SWAG as well
  • auth-mysql - the database container authelia will use, and sets up a root password to access the database server
  • auth-phpmyadmin - a container to create and manage individual databases and users in the mysql container
  • auth-redis - not 100% necessary, but will speed up request returns when authelia is invoked by SWAG
Note that we currently do not have any database configuration set up, or a way to pass user/password details to authelia. This will be set up in the next step

When you're ready, run your docker-compose up -d command, and watch your 4 containers being created. Once spun up, I would stop the authelia container, either through Portainer if you use it, or via the CLI.

Creating the mysql database and user for authelia

I've written an article about MySQL and phpMyAdmin here. Go through it and set up a database with a user, password and database name of your choice. For the sake of this walkthrough, I'm going to say that both the user and database name is 'authelia'. The password is your own.

when you are offered a choice of collation during the creation of the database, it's recommended to use utf8_bin

Setting the Authelia configuration

We now have all the information we need to modify the configuration.yaml file which Authelia uses.

  • Navigate to your $DOCKERDIR/authelia/app directory and open configuration.yaml. It's a rather long file, so below I'm going to include only the information which needs to be changed, and some of the information you shouldn't change. I have used | marks to show where there are gaps
# yamllint disable rule:comments-indentation
---
###############################################################################
#                           Authelia Configuration                            #
###############################################################################

## The host and port to listen on.
server.host: 0.0.0.0
server.port: 9091 #DO NOT CHANGE, this is the INTERNAL authelia port, so will always be 9091
|
|
|
## The theme to display: light, dark, grey.
theme: dark #because why wouldn't you want it to be dark

##
## Server Configuration
##
server:
  buffers:
  ## Buffers usually should be configured to be the same value.
  ## Explanation at https://www.authelia.com/docs/configuration/server.html
  ## Read buffer size adjusts the server's max incoming request size in bytes.
  ## Write buffer size does the same for outgoing responses.
    read: 8192
    write: 8192 #I had issues with the original sizing, this helped

  ## Set the single level path Authelia listens on.
  ## Must be alphanumeric chars and should not contain any slashes.
  path: "authelia" #DO NOT CHANGE THIS
|
|
|
## The secret used to generate JWT tokens when validating user identity by email confirmation. JWT Secret can also be
## set using a secret: https://www.authelia.com/docs/configuration/secrets.html
jwt_secret: #set your own secret here, I recommend a long string of at least 32 characters
|
|
|
##
## TOTP Configuration
##
## Parameters used for TOTP generation.
totp:
  ## The issuer name displayed in the Authenticator application of your choice
  ## See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format for more info on issuer names
  issuer: #your domain, such as auth.yourdomain.com

## Authentication Backend Provider Configuration
##
## Used for verifying user passwords and retrieve information such as email address and groups users belong to.
##
## The available providers are: `file`, `ldap`. You must use only one of these providers.
authentication_backend:
  ## Password Reset Options.
  password_reset:
    ## Disable both the HTML element and the API for reset password functionality.
    disable: false

  ## The amount of time to wait before we refresh data from the authentication backend. Uses duration notation.
  ## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will
  ## always belong to groups they belonged to at the time of login even if they have been removed from them in LDAP.
  ## To force update on every request you can set this to '0' or 'always', this will increase processor demand.
  ## See the below documentation for more information.
  ## Duration Notation docs:  https://www.authelia.com/docs/configuration/index.html#duration-notation-format
  ## Refresh Interval docs: https://www.authelia.com/docs/configuration/authentication/ldap.html#refresh-interval
  refresh_interval: 30m #I found the default refresh interval too short
  |
  |
  |
  ##
  ## File (Authentication Provider)
  ##
  ## With this backend, the users database is stored in a file which is updated when users reset their passwords.
  ## Therefore, this backend is meant to be used in a dev environment and not in production since it prevents Authelia
  ## to be scaled to more than one instance. The options under 'password' have sane defaults, and as it has security
  ## implications it is highly recommended you leave the default values. Before considering changing these settings
  ## please read the docs page below:
  ## https://www.authelia.com/docs/configuration/authentication/file.html#password-hash-algorithm-tuning
  ##
  ## Important: Kubernetes (or HA) users must read https://www.authelia.com/docs/features/statelessness.html
  ##
  file: #uncomment this and following lines
    path: /config/users_database.yml
    password:
      algorithm: argon2id
      iterations: #use 1, 2 or 3
      key_length: #set key length, 32 is default
      salt_length: #set this, 16 is default 
      memory: 1024
      parallelism: 8

##
## Access Control Configuration
##
## Access control is a list of rules defining the authorizations applied for one resource to users or group of users.
##
## If 'access_control' is not defined, ACL rules are disabled and the 'bypass' rule is applied, i.e., access is allowed
## to anyone. Otherwise restrictions follow the rules defined.
##
## Note: One can use the wildcard * to match any subdomain.
## It must stand at the beginning of the pattern. (example: *.mydomain.com)
##
## Note: You must put patterns containing wildcards between simple quotes for the YAML to be syntactically correct.
##
## Definition: A 'rule' is an object with the following keys: 'domain', 'subject', 'policy' and 'resources'.
##
## - 'domain' defines which domain or set of domains the rule applies to.
##
## - 'subject' defines the subject to apply authorizations to. This parameter is optional and matching any user if not
##    provided. If provided, the parameter represents either a user or a group. It should be of the form
##    'user:<username>' or 'group:<groupname>'.
##
## - 'policy' is the policy to apply to resources. It must be either 'bypass', 'one_factor', 'two_factor' or 'deny'.
##
## - 'resources' is a list of regular expressions that matches a set of resources to apply the policy to. This parameter
##   is optional and matches any resource if not provided.
##
## Note: the order of the rules is important. The first policy matching (domain, resource, subject) applies.
access_control:
  ## Default policy can either be 'bypass', 'one_factor', 'two_factor' or 'deny'. It is the policy applied to any
  ## resource if there is no policy to be applied to the user.
  default_policy: deny

  networks:
    - name: internal
      networks:
        - #add your internal network(s) here, each on a new line

  rules:
    ## Rules applied to internal networks
  - domain: "*.yourdomain.com" #change to your domain name
    networks: 
      - internal
    policy: bypass #will mean your internal networks do not need to authenticate
  
  - domain: "*.yourdomain.com" #change to your domain name
    policy: one_factor 

#sample policy	
#  - domain: "radarr.yourdomain.com"
#	 policy: two_factor

##
## Session Provider Configuration
##
## The session cookies identify the user once logged in.
## The available providers are: `memory`, `redis`. Memory is the provider unless redis is defined.
session:
  ## The name of the session cookie.
  name: authelia_session

  ## The domain to protect.
  ## Note: the authenticator must also be in that domain.
  ## If empty, the cookie is restricted to the subdomain of the issuer.
  domain: yourdomain.com #change to your domain name

  ## Sets the Cookie SameSite value. Possible options are none, lax, or strict.
  ## Please read https://www.authelia.com/docs/configuration/session.html#same_site
  same_site: lax

  ## The secret to encrypt the session data. This is only used with Redis / Redis Sentinel.
  ## Secret can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
  secret: #change this to a secret, either a phrase with no spaces or a long password
  | 
  |
  |
  ##
  ## Redis Provider
  ##
  ## Important: Kubernetes (or HA) users must read https://www.authelia.com/docs/features/statelessness.html
  ##
  redis:
    host: auth-redis #change this to your host machine's IP if this doesn't work
    port: 6379 #change if you changed your redis port in the docker compose
    |
	|
	|
##
## Storage Provider Configuration
##
## The available providers are: `local`, `mysql`, `postgres`. You must use one and only one of these providers.
storage:
  |
  |
  |
  ##
  ## MySQL / MariaDB (Storage Provider)
  ##
  encryption_key: #use your own 20 character random string, you can use a password generator if you want to
  mysql:
    host: auth-mysql #change this to your host machine's IP if this doesn't work
    port: 3306 #change if you changed your mysql port in the docker compose
    database: authelia
    username: authelia
    ## Password can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
    password: #the password you set for the database via phpMyAdmin
these items are important for the functionality of Authelia

Creating your authelia users and passwords

The final thing we need to do is create your user(s) and password(s). In the configuration above, we specified that this will be done via a file, called users_database.yml. This is perfectly fine for a handful of users, say up to 10. If you have more than that, you may want to investigate LDAP, but for now...

  • In the same directory as your configuration.yml, create a users_database.yml file. The contents will take the following form:
users:
  user1:
    displayname: "username"
    password: "a long hashed password"
    email: #optional
    groups: #optional
      - a group name
  
  user2:
    displayname: "anotheruser"
    password: "another long hashed password"
etc.
  • To create the hashed password, we need to SSH into our machine and use the command line, where we copy and paste the following command for each user who needs a password:
docker run authelia/authelia:latest authelia hash-password 'yourpassword'
replace 'yourpassword` with a password
  • This will do a one-time run of the container, which will hash your password
  • You'll get an output on the next line of the password hash. For instance, if you kept the defaults as above, and used a password of 'authelia', you would type in:
docker run authelia/authelia:latest authelia hash-password authelia

and your resulting hash would be

$argon2id$v=19$m=65536,t=1,p=8$dnBiQ2NLdTB6d2hMakMvOA$Uk9qK8U9EFjYmfKMoAYhTvzM/oTbh+Ht5mev96TqvOo
you can read up more on hashed password algorithm tuning here and here if you're so inclined
  • Copy your whole password hash string and paste it in between the " marks for your user's password. So a user called authelia, who uses a password authelia, would have the following entry in the users_database.yml
users:
  authelia:
    displayname: "authelia"
    password: "$argon2id$v=19$m=65536,t=1,p=8$dnBiQ2NLdTB6d2hMakMvOA$Uk9qK8U9EFjYmfKMoAYhTvzM/oTbh+Ht5mev96TqvOo"
our full user setup

Authelia should now be fully configured and ready to use - you can now restart the container. When the container is running, you should be able to access your authelia front page by typing http://yourNASip:9091 into your browser, and see this:

When you type in your username authelia and password authelia, if it's all working, it will simply reload this screen. Congratulations!

There's one final step to protecting your radarr (and/or other) container with it.

Modifying the proxy-conf in SWAG to use Authelia

If you're using auto proxy, this is easy. Where you've got your swag=enable label, you simply add another, swag_auth=authelia so the labels part of your compose will look like:

    labels:
      - swag=enable
      - swag_auth=authelia

You can even bypass certain subfolders in your URI such as /api and /anotherSubfolderby adding a third label:

    labels:
      - swag=enable
      - swag_auth=authelia
      - swag_auth_bypass=/api,/anotherSubfolder

If however you don't use the auto-proxy or the container in question doesn't have a proxy.conf template, you'll need to do the below.

Let's head back to our SWAG directory, and the nginx folder. You'll notice that there are two files, called authelia-location.conf and authelia-server.conf. If you have kept things as above, you shouldn't need to change anything in these files, however I bring them up so you understand what is referenced in the next steps.

  • Locate your radarr.subdomain.conf file inside the nginx>proxy-confs directory
  • Inside the first server block, locate the lines # enable for Authelia and #include /config/nginx/authelia-server.conf;
  • Uncomment (remove the #) the line include /config/nginx/authelia-server.conf;
  • Inside the first location block, locate the similar lines # enable for Authelia and #include /config/nginx/authelia-location.conf;
  • Uncomment the line #include /config/nginx/authelia-location.conf;
  • Save the file and restart the SWAG container
  • Now when you access radarr.yourdomain.com you should see a the same authelia login screen as above
  • Type in your username and password, and you will be sent through to your radarr gui

Happy authenticating!



Related Article

Setting up your docker socket proxy
Protect your docker information from intrusion by using a proxy socket to ensure your docker.sock isn’t exposed to the internet
Portainer - Easy Container Management for Docker
A step-by-step docker walkthrough to installing and configuring Portainer, your one-stop container-management resource
Local DNS to serve your apps via URL instead of IP
Tired of remembering a raft of port numbers to access your service GUIs? Here’s a way to access them all using easier-to-remember URLs

PTS

PTS fell down the selfhosted rabbit hole after buying his first NAS in October 2020, only intending to use it as a Plex server. Find him on the Synology discord channel https://discord.gg/vgSq5pcT

Have some feedback or something to add? Comments are welcome!

Please note comments should be respectful, and may be moderated