Traefik + Authentik - experiments in SSO
A while ago I did some research and looked into various open source SSO (Single Sign On) options. Amongst those I considered were Gluu, Keycloak and Authentik. This is about Authentik, as you've probably guessed from the title.
First, lets start with the Traefik docker compose:
services:
reverse-proxy:
image: traefik:v3.2.2
environment:
AWS_ACCESS_KEY_ID: ""
AWS_SECRET_ACCESS_KEY: ""
DUCKDNS_TOKEN: ""
NAMECHEAP_API_USER: "<your-namecheap-username>"
NAMECHEAP_API_KEY: "<your-namecheap-api-key>"
DO_AUTH_TOKEN: ""
# TRACING
TRAEFIK_TRACING: true # Enable tracing
#TRAEFIK_TRACING_JAEGER_SAMPLINGPARAM: 0 # Set the Jaeger sampling parameter
#TRAEFIK_TRACING_JAEGER_TRACECONTEXTHEADERNAME: X-Request-ID # Set the header to use for the X-Request-ID
command:
- --api.insecure=true
- --providers.docker=true
- --providers.docker.network=traefik_default
- --providers.docker.exposedbydefault=false
- --accesslog=true
- --accesslog.filepath=access.log
- --log.level=INFO
- --log.filePath=traefik.log
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http3
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --certificatesresolvers.myresolver.acme.dnschallenge=true
- --certificatesresolvers.myresolver.acme.dnschallenge.provider=namecheap
- --certificatesresolvers.myresolver.acme.email=your+email@address.com
- --certificatesresolvers.myresolver.acme.storage=/etc/traefik/acme.json
- --global.checknewversion=false
- --global.sendanonymoususage=false
ports:
- "80:80"
- "443:443/tcp"
- "443:443/udp"
volumes:
# So that Traefik can listen to Docker events and auto-configure.
# This has security implications.
# See https://doc.traefik.io/traefik/providers/docker/#docker-api-access
- /var/run/docker.sock:/var/run/docker.sock:ro
# For storing Let's Encrypt cert
- /data/traefik/etc:/etc/traefik:rw
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard-https.entrypoints=websecure"
- "traefik.http.routers.dashboard-https.rule=Host(`traefik-dashboard.example.domain`)"
- "traefik.http.routers.dashboard-https.service=api@internal"
- "traefik.http.routers.dashboard-https.tls.certresolver=myresolver"
- "traefik.http.routers.dashboard-https.tls.domains[0].main=example.domain"
- "traefik.http.routers.dashboard-https.tls.domains[0].sans=*.example.domain"
- "traefik.http.routers.dashboard-https.middlewares=lan-only"
- "traefik.http.middlewares.lan-only.ipallowlist.sourcerange=123.123.123.123/32"
# pulled from https://github.com/brokenscripts/authentik_traefik
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.address=http://authentik_server:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.trustForwardHeader=true"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-username"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-groups"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-email"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-name"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-uid"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-jwt"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-meta-jwks"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-meta-outpost"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-meta-provider"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-meta-app"
- "traefik.http.middlewares.middlewares-authentik.forwardAuth.authResponseHeaders=X-authentik-meta-version"
restart: unless-stopped
Next, we'll need an Authentik docker compose:
services:
postgresql:
image: docker.io/library/postgres:12-alpine
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:
- authentik
redis:
image: docker.io/library/redis:alpine
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:
- authentik
server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.12.1}
restart: unless-stopped
command: server
container_name: authentik_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
labels:
- "traefik.enable=true"
## HTTP Routers
- "traefik.http.routers.authentik-rtr.rule=Host(`authentik.example.domain`)"
- "traefik.http.routers.authentik-rtr.entrypoints=websecure"
- "traefik.http.routers.authentik-rtr.tls=true"
- "traefik.http.routers.nc-https.tls.certresolver=myresolver"
## Individual Application forwardAuth regex (catch any subdomain using individual application forwardAuth)
- "traefik.http.routers.authentik-rtr-outpost.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.example.domain`) && PathPrefix(`/outpost.goauthentik.io/`)"
- "traefik.http.routers.authentik-rtr-outpost.entrypoints=websecure"
- "traefik.http.routers.authentik-rtr-outpost.tls=true"
- "traefik.http.routers.authentik-rtr-outpost.tls.certresolver=myresolver"
## HTTP Services
- "traefik.http.routers.authentik-rtr.service=authentik-svc"
- "traefik.http.services.authentik-svc.loadBalancer.server.port=9000"
env_file:
- .env
ports:
- "${COMPOSE_PORT_HTTP:-9000}:9000"
- "${COMPOSE_PORT_HTTPS:-9443}:9443"
depends_on:
- postgresql
- redis
networks:
- authentik
- traefik_default
worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.12.1}
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:
- authentik
volumes:
database:
driver: local
redis:
driver: local
networks:
authentik:
name: authentik
traefik_default:
external: true
You'll also need a .env
file, here's a snippet of mine:
PG_PASS=<super-top-secret-postgres-password>
AUTHENTIK_SECRET_KEY=<really-really-really-secure-authentik-secret-key>
AUTHENTIK_ERROR_REPORTING__ENABLED=true
# SMTP Host Emails are sent to
AUTHENTIK_EMAIL__HOST=smtp.sendgrid.net
AUTHENTIK_EMAIL__PORT=587
# Optionally authenticate (don't add quotation marks to your password)
AUTHENTIK_EMAIL__USERNAME=apikey
AUTHENTIK_EMAIL__PASSWORD=<sendgrid-api-key-goes-here>
# Use StartTLS
AUTHENTIK_EMAIL__USE_TLS=true
# Use SSL
AUTHENTIK_EMAIL__USE_SSL=false
AUTHENTIK_EMAIL__TIMEOUT=10
# Email address authentik will send from, should have a correct @domain
AUTHENTIK_EMAIL__FROM=your+email@address.com