Authelia for MFA on docker
I am never going to state this is "the correct way", "the best way", or even "the only way". What I will state is that following these instructions provided me with a working environment with either Nginx Proxy Manager, or Caddy working as the reverse proxy on a self hosted Authelia deployment.
This started as a single page but became too large to manage, I've now split it up into these sections.
- This page: Authelia for MFA on docker
- Deploying test hosts for Authelia MFA authentication
- Configuring Nginx reverse proxy in front of Authelia
- Configuring Caddy reverse proxy in front of Authelia
- Logging in to Authelia
Authelia Background
Authelia is a great open source authentication and authorisation server that you can host yourself to provide increased security to any number of tools. There are examples out there for all types of services. However, whilst working through this to deploy in front of some of my hosted services I've found many of the Youtube Videos a bit misleading, or not explaining why something is configured the way it is shown.
I intend to write up how to use Authelia behind a reverse proxy, and will provide a link from here when it is finished.
I'm not about to try to present all of the various deployment options etc. I would recommend anyone looking for more in-depth answers look on the Authelia web site.
My aim here is to document how I have got it working, for personal reference. If this does assist others, then that is just an added bonus. I will not be detailing the deployment of the NGINX Proxy Manager itself, or Caddy in terms of docker files etc. I will though provide details of the configuration steps required to provide a working solution.
So, first things first we need a domain name to host our services. I will be using Milpixel.com which was a dormant domain that I registered for no other reason that it was on the Million Dollar Homepage. It does however give me a public Domain Name to play with in these examples. Everything will be deleted at the end, so nothing here will remain as public DNS records long term.
Configure the required DNS records
First we need to set up the required DNS records using your providers utilities, they are all very similar. I already have an "A" record for Milpixel.com pointing to my public IP address, so I can just add the two CNAME records as below:
Record | Name | Target |
---|---|---|
A | milpixel.com | My_Public_IP |
CNAME | auth.milpixel.com | milpixel.com |
CNAME | 1-factor-WebSite.milpixel.com | milpixel.com |
CNAME | 2-factor-WebSite.milpixel.com | milpixel.com |
Once these are configured, so long as the initial A record is correct the auth and the ProtectedWebSite will both resolve to the same IP.
Remember, if you are running this in a home lab environment, you must create the same DNS records for auth.milpixel.com etc etc pointing at you internal IP. If you don't do this using Pihole or some other DNS server, it will cause Authelia is likely to fail with the "Error occurred retrieving user session" being reported.
Ensure Ports are available publically
We next need to make sure that the TCP-80 & TCP-443 ports are open and if necessary are forwarded to the host we are going to deploy on. Failure to do this step will result with the deployment failing with the message:
authelia exited with code 1
If you get this error ensure the port forwarding is configured correctly before investigating other reported errors first. This seems to be a common mistake reading the various forums etc.
Ensure Docker is installed and functional
Docker installation is pretty straight forward and is well documented here, I do not intend covering it further in this guide.
Prepare Docker Directories for Authelia. I will be using a docker subdirectory of my home directory, adjust for your preferences.
mkdir -p ~/docker/authelia/{config,config/secrets,postgres,redis}
Configure the required Passwords & Keys.
Authelia requires a number of passwords for various functions, we need to supply those, and I've followed this guide to generate those:
cd ~/docker/authelia
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/JWT_SECRET
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/SESSION_SECRET
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/STORAGE_PASSWORD
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/STORAGE_ENCRYPTION_KEY
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/REDIS_PASSWORD
Finally we need the SMTP password for the mail service you plan on using to send emails. I've used Gmail in my example so this is my gmail password for my account. This password needs to be saved in the following file:
~/docker/authelia/config/secrets/SMTP_PASSWORD
Create the docker-compose.yaml file
I will be using both Redis and Postgres in this docker compose file, other options are available but this docker-compose file works fine for me. The file should be created as per the path below:
~/docker/authelia/docker-compose.yaml
The file below has worked in my environment, I have explored each section below to highlight the key areas of interest below:
name: authelia
services:
app:
image: authelia/authelia:latest
container_name: authelia
depends_on:
- database
- redis
volumes:
- ./config:/config
environment:
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: /config/secrets/JWT_SECRET
AUTHELIA_SESSION_SECRET_FILE: /config/secrets/SESSION_SECRET
AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE: /config/secrets/SMTP_PASSWORD
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /config/secrets/STORAGE_PASSWORD
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /config/secrets/STORAGE_ENCRYPTION_KEY
AUTHELIA_SESSION_REDIS_PASSWORD_FILE: /config/secrets/REDIS_PASSWORD
ports:
- 9091:9091
database:
image: postgres:15
restart: unless-stopped
container_name: auth_database
volumes:
- ./postgres:/var/lib/postgresql/data
environment:
POSTGRES_USER: "authelia"
POSTGRES_PASSWORD: "FhpQLpp6Nl3Lsg3Di5bjrXcCbqmbs6uPkbHiQv3qII8BkohZrWKQSnBKRBiUT3Gy"
redis:
image: redis:7
restart: unless-stopped
container_name: auth_redis
command: "redis-server --save 60 1 --loglevel warning --requirepass bkerbcl5oQsmVknP4nwzT8dDCQLHu7hhTibWqmcvNhtXKwGsc5csiTVfLC6Y3bZ2"
volumes:
- ./redis:/data
Working from the top of the docker-compose file above:
- The app section defines the main Authelia parameters as you would expect. Here we can see a number of password files that we created earlier being referenced. Five of these were created earlier using random strings. the 6th being the SMTP password. This is good practise because it keeps the actual passwords out of the docker-compose file. However on this occasion this might seem redundant as we will see further down the file.
- The port being exposed here is 9091, this can be modified to suit your environment, just make sure you only adjust the port on the left of the ':' the port on the right must be left at 9091 as this is used within the docker container.
- Within the Database section you can see I'm using Postgres with the :15 tag. This is simply because that was in one of the YouTube video's I watched. I do intend updating this to the :latest and will update here accordingly.
- Under the Environment section of the Database, I have specified the Authelia username along with the password. The password specified here is the same as that stored in the config/secrets/STORAGE_PASSWORD file. So whilst it is good practise not to have passwords in the docker compose file, here I couldn't get it working without specifying it. If I do work out a way of doing it, this will be reflected in a future update to this post.
- Moving on the the Redis section, this is all pretty straight forward as you would expect. However, here again I've had to specify the password taken from that stored in the config/secrets/REDIS_PASSWORD file. As before if I find out how to avoid this file in the docker-compose file I will update this section.
That is all there is to say about the docker-compose.yaml file. Once the passwords are ask set and match then we can move on.
Create a users_database.yml file
For my use case with a single user for testing, a simple yaml file for user credentials is sufficient. The Authelia Password Reference documentation is very comprehensive and should be checked if required to clarify settings etc.
Before we look into the file we will first need to generate a password hash. I used the https://argon2.online site to do this. In my example the password is "mystrongpassword" which resulted with the hash of "$argon2i$v=19$m=32,t=2,p=4$c1lZdURxcjJ1bnBzOWZpSQ$bzci8AqOzQJharl5+EsANw".
It is this strong hash value that we write in the password section of the user_database.yml file.
We also need to provide the email address for the user, this is required so that authelia can send you the token's etc to set up MFA. If you don't take the time to set up the email both here and in the Authelia password sections MFA, and the reporting of user account activity will not function. It wouldn't surprise me if Authelia even fails to start without these email credentials being configured.
The file should be created as per the path below:
~/docker/authelia/config/users_database.yml
Containing the information below:
users:
my_user: #username for user 1. change to whatever you'd like
displayname: "Joe Bloggs" #whatever you want the display name to be
password: "$argon2i$v=19$m=32,t=2,p=4$c1lZdURxcjJ1bnBzOWZpSQ$bzci8AqOzQJharl5+EsANw"
email: JoeBloggs-Sample@gmail.com #whatever your email address is
groups: #enter the groups you want the user to be part of below
- admins
- dev
I haven't really delved into the groups, but I believe this to be a way of having certain restrictions applicable too certain groups of users. Now we have our docker-compose file and the user_database.yml we can start to pull it all together in our Authelia configuration.yml file.
Create a configuration.yml file
If you start the Authelia docker without a configuration file it will generate one with the very many options along with remarks. If you wish to see that file simply skip this step start the docker stack using the docker-compose file from earlier and it will generate the template for you to browse / edit as required.
Or, we can just start with a small new file and work from there. This was my approach based on research from other blogs and youtube.
The file should be created as per the path below:
~/docker/authelia/config/configuration.yml
In my example here, this is what is contained in the file:
# yamllint disable rule:comments-indentation
---
###############################################################################
# Authelia Configuration #
###############################################################################
theme: auto #light/dark/auto
server:
host: 0.0.0.0 #IP to listen on, this is fine in our docker container
port: 9091 #This needs to match the docker-compose port
path: ""
read_buffer_size: 8192 #think the default is 4096
write_buffer_size: 8192 #think the default is 4096
enable_pprof: false
enable_expvars: false
disable_healthcheck: false #Provide infomation if something is wrong
tls:
key: ""
certificate: ""
log:
level: debug #This can be changed to info if required
totp:
issuer: milpixel.com #your authelia top-level domain
disable: false
period: 30 #how many days a token is valid for
skew: 1 #how much skew is allowed on that period
authentication_backend:
disable_reset_password: false #I want to control user access
refresh_interval: 5m
file:
path: /config/users_database.yml #this is where your authorized users are stored
password:
algorithm: argon2id
iterations: 1
key_length: 32
salt_length: 16
memory: 1024
parallelism: 8
access_control:
default_policy: deny
rules:
## bypass rule
- domain:
- "auth.milpixel.com" #This should be your authentication URL
policy: 'bypass'
- domain:
- "1-factor-WebSite.milpixel.com" #The 1st Web Site
policy: 'one_factor'
- domain:
- "2-factor-WebSite.milpixel.com" #The 2nd Web Site
policy: 'two_factor'
#any time you add a new subdomain, you will need to restart the Authelia container to recognize the new settings/rules
session:
name: authelia_session
expiration: 3600 # 1 hour
inactivity: 300 # 5 minutes
cookies:
- domain: 'milpixel.com'
authelia_url: 'https://auth.milpixel.com'
name: 'authelia_session'
redis:
host: auth_redis
port: 6379
regulation:
max_retries: 3
find_time: 10m
ban_time: 12h
storage:
postgres:
address: 'tcp://auth_database:5432'
database: authelia
username: authelia
notifier:
disable_startup_check: false #true/false
smtp:
username: JoeBloggs-Sample@gmail.com #your email to use as sender
host: smtp.gmail.com #email smtp server
port: 587 #email smtp port
sender: JoeBloggs-Sample@gmail.com #your email to use as sender
identifier: localhost
subject: "[Authelia] {title}" #email subject
startup_check_address: admin@milpixel.com #your email to receive
disable_require_tls: false
disable_html_emails: false
tls:
skip_verify: false
minimum_version: TLS1.2
Authelia.com has details of all these option as you would expect. I highly recommend reviewing any sections that may not be too clear.
I believe the comments in the file above probably cover most questions, but on the notifier section, if you use gmail, you will need to generate an application password and use that in the SMTP_PASSWORD file. I have set up a new gmail account just to use it for service related emails etc. I would advise this a good approach so they you can receive notification of authentication requests and MFA codes, password resets etc.
I intend to do a short blog about that and will link here when I've completed it. Either way, a quick search should point you in the right direction.
Starting Authelia docker stack with Redis and Postgres
We now have all the elements to deploy the stack to protect our services. The following files and directories should be ready for deployement:
./redis
./postgres
./config
./config/user_database.yml
./config/secrets
./config/secrets/JWT_SECRET
./config/secrets/SMTP_PASSWORD
./config/secrets/SESSION_SECRET
./config/secrets/STORAGE_PASSWORD
./config/secrets/STORAGE_ENCRYPTION_KEY
./config/secrets/REDIS_PASSWORD
./config/configuration.yml
./docker-compose.yaml
After confirming we have all the necessary files in place for our stack, we can deploy:
docker compose up
Once the docker has pulled the required images etc it should all start up and end up with something similar to below listening on port 9091.
authelia | level=info msg="Authelia v4.38.8 is starting"
authelia | level=info msg="Log severity set to debug"
authelia | level=info msg="Storage schema is being checked for updates"
authelia | level=info msg="Storage schema is already up to date"
authelia | level=debug msg="Create Server Service (metrics) skipped"
authelia | level=info msg="Startup complete"
authelia | level=info msg="Listening for non-TLS connections on '[::]:9091' path '/'" server=main service=server
Confirm the Containers are running
Issuing a docker ps command, we can see the containers are running:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cbbea3df4065 authelia/authelia:latest "/app/entrypoint.sh" 3 hours ago Up 2 hours (healthy) 0.0.0.0:9091->9091/tcp, :::9091->9091/tcp authelia
b3ffd4b3d811 postgres:15 "docker-entrypoint.s…" 3 hours ago Up 2 hours 5432/tcp auth_database
1ea07c6a02e4 redis:7 "docker-entrypoint.s…" 3 hours ago Up 2 hours 6379/tcp auth_redis
Now have Authelia configured, up and running we can go to the next step of this blog series.