Introduction
In our previous post, we secured the Homer app with trusted Let's Encrypt certificates using Traefik as a reverse proxy. But what if only authorized users should access Homer? In this blog, we'll address this by adding multi-factor authentication to Homer using Authentik as an Identity Provider (IdP).
Objective
The core objectives of this tutorial are to:
- Set Up Secure Access with Authentik: Install Authentik using Docker Compose and create your first user to manage access control.
- Secure Homer with Authentik: Configure Authentik to act as a gatekeeper, ensuring only authorized users can access your Homer application.
- Simplify Logins with Traefik: Integrate Traefik with Authentik to enable Single Sign-On (SSO) for a seamless login experience across your applications.
- Connect Homer to Authentik: Configure Homer to leverage Authentik's authentication system for secure logins.
Topology
For the topology details please see the previous post.
Access Flow
Homer can be accessed through https://homer.mydomain.com. Here's what happens behind the scenes when you try to visit it:
- Login Check: Traefik first checks with Authentik, to see if you're already logged in.
- Login Required: If you're not logged in, Traefik automatically redirects you to a login page on Authentik, located at https://authentik.mydomain.com.
- Successful Login: Once you log in successfully on Authentik, you'll be granted access to Homer.
Remember: Replace mydomain.com with your actual domain name.
Assumptions
You have followed the steps outlined in the previous post.
Setup Secure Access with Authentik
Let's start by creating an authentik directory
mkdir authentik
cd authentik
vim docker-compose.yml
Add the provided content to your compose file. This file was downloaded from the Authentik site and minor modifications were made
---
version: "3.4"
services:
postgresql:
image: docker.io/library/postgres:12-alpine
container_name: authentik-db
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- database:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
env_file:
- .env
networks:
- traefik_web
redis:
image: docker.io/library/redis:alpine
container_name: authentik-redis
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- redis:/data
networks:
- traefik_web
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.2.2}
container_name: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./media:/media
- ./custom-templates:/templates
env_file:
- .env
depends_on:
- postgresql
- redis
labels: # auto-detects port 80 in next line
traefik.enable: true
traefik.http.routers.authentik.tls: true
traefik.http.routers.authentik.rule: Host(`authentik.mydomain.com`)
traefik.http.routers.authentik.tls.certresolver: letsencrypt
traefik.http.routers.authentik.entrypoints: websecure
traefik.http.routers.authentik.service: authentik-svc
traefik.http.services.authentik-svc.loadBalancer.server.port: 9000
networks:
- traefik_web
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.2.2}
container_name: authentik-worker
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
# `user: root` and the docker socket volume are optional.
# See more for the docker socket integration here:
# https://goauthentik.io/docs/outposts/integrations/docker
# Removing `user: root` also prevents the worker from fixing the permissions
# on the mounted folders, so when removing this make sure the folders have the correct UID/GID
# (1000:1000 by default)
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./media:/media
- ./certs:/certs
- ./custom-templates:/templates
env_file:
- .env
depends_on:
- postgresql
- redis
networks:
- traefik_web
networks:
traefik_web:
external: true
volumes:
database:
driver: local
redis:
driver: local
The Postgres password and Authentik secret key are stored in a .env file. Let's create it in the authentik folder
echo "PG_PASS=$(openssl rand -base64 36)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 36)" >> .env
echo "AUTHENTIK_ERROR_REPORTING__ENABLED=true" >> .env
Once the installation is complete, navigate to http://authentik.mydomain.com/if/flow/initial-setup/ and login with username akadmin. Follow the steps to create a password and login to Authentik Web UI Create your first user in Authentik
- Navigate to Admin Interface
- Select Directory --> Users
- Click on Create
- Username - Pick your username
- Name - Your Name
- User Type - Internal
- Email - Provide your email
- Is Active - Should be selected by default
- Hit Create
- Select Groups
- Click on authentik Admins
- Click on Users --> Add existing users
- Add your username and Click Add
- Navigate to back to Users and Select your username
- Click on Set password to create your password
- Logout and log back in with the new user
- Navigate to Admin Interface
- Select Directory --> Users
- Select akadmin user and Deactivate it under Actions
- Go back to the Dashboard
- Navigate to the Settings on the top corner
- Click on MFA Devices
- Click on Enroll and choose TOTP Device
- Follow the instructions to enable MFA by scanning the QR code using the Google Authenticator App for example
- Log out and log in to verify MFA is enabled
That's it! An Authentik user is now configured with MFA
Secure Homer with Authentik
Now lets configure Authentik to secure Homer
- Navigate to the Admin Interface
- Select Application --> Providers --> Create
- Select Proxy Provider and hit Next
- Name - homer
- Authentication flow - Leave it blank
- Authorization flow - default-provider-athorization-implicit-consent
- Select Forward auth (single application)
- External host - https://homer.mydomain.com
- Hit Finish
- Navigate to Applications --> Application
- Name - homer
- Slug - homer
- Provider - Proxy Provide you created in the previous step - homer
- Hit Create
- Navigate to Applications --> Outposts
- Edit authentik Embedded Outpost
- Add homer to the Selected Applications space
That's it! Authentik is all setup to secure homer with SSO and MFA. Now let's configure traefik to forward requests for Authentik.
Simplify Logins with Traefik
Picking up from the previous post, we need to update the traefik docker-compose with 3 additional lines
Under the command section, add the following:
command: # Static file is not required if using commands
- --providers.file=true
- --providers.file.directory=/etc/traefik/conf/
Under the volumes section, add the following:
volumes:
- ./conf:/etc/traefik/conf/
Here is the full traefik docker-compose.yml file
version: "3.3"
services:
traefik:
container_name: traefik
image: traefik:latest # Download traefik
restart: always # Restart the container if stops
ports:
- 443:443 # Expose port 443 on the host machine
- 80:80 # Expose port 80 on the host machine
command: # Static file is not required if using commands
- --providers.docker=true
- --providers.file=true
- --providers.file.directory=/etc/traefik/conf/
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.forwardedheaders.insecure=true
- --serverstransport.insecureskipverify=true
- --log.level=DEBUG
- --api=true
- --api.insecure=true
- --api.dashboard=true
- --api.debug=true
- --entrypoints.websecure.http.tls.certResolver=letsencrypt
- --entrypoints.websecure.http.tls.domains[0].main=mydomain.com
- --entrypoints.websecure.http.tls.domains[0].sans=*.mydomain.com
# It is recommended you uncomment the next line and try out if you get the trusted certs. Once verifed, you can comment it and restart the container
#- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesresolvers.letsencrypt.acme.email=your_cloudflare_email
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
- --certificatesResolvers.cfresolver.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53
secrets:
- cf_email
- cf_api
environment:
# you may choose to use secrets instead of environment variables like this
CF_API_EMAIL_FILE: /run/secrets/cf_email
CF_API_KEY_FILE: /run/secrets/cf_api
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Allow Traefik access to docker API's to dynamically learn applications
- ./certs:/letsencrypt
- ./conf:/etc/traefik/conf/
labels:
# Traefik uses lables to learn about containers and dynamically configures itself. Expose the traefik dashboard on port 443
traefik.enable: true
traefik.http.routers.traefik.tls: true
traefik.http.routers.traefik.rule: Host(`traefik.mydomain.com`)
traefik.http.routers.traefik.entrypoints: websecure
traefik.http.routers.traefik.service: api@internal
traefik.http.routers.traefik.tls.certresolver: letsencrypt
# Use the custom network web that is defined below
networks:
- web
networks:
web:
secrets:
cf_email:
file: ./secrets/cf_email.secret
cf_api:
file: ./secrets/cf_api.secret
Navigate to the traefik directory and create a conf directory with headers.yml
mkdir conf
cd conf
vim headers.yml
Add the provided content to your file. Note authentik-server is the name of our container
http:
middlewares:
authentik:
forwardAuth:
address: http://authentik-server:9000/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
Navigate to the traefik folder and restart treaefik
docker-compose up -d --force-recreate
Traefik should automatically apply the middleware. You can verify if the middleware was applied by navigating to https//traefik.mydomain.com. Now let's update homer to use the middleware authentik Connect homer to Authentik
Navigate to the homer folder and open docker-compose.yml Add the following label.
Note, authentik@file is the name of our middleware. If you used a different name, please use that.
traefik.http.routers.homer1.middlewares: authentik@file
Restart homer
docker-compose up -d --force-recreate
That's it! When you navigate to https://homer.mydomain.com, you will be greeted with an Authentik login.
Comments
Post a Comment