Skip to content

Deploy authelia on swarm

Intro

I started using Docker Swarm in 2022 and am still very satisfied with it. I am currenyl using it as a one node swarm. This post assumes you deployed Swarm with a Traefik reverse proxy as described on DockerSwarm.rocksi, that all services are deployed under the doomain stored in the DOMAIN environment variable, and that the variable DOCKER_HOST is set correctly.

I wanted to test authelia for protecting a web app to be deployed on a Docker Swarm, and I decided to test it on an existing Docker Swarm. The idea is to deploy Authelia, and protect a newly deployed whoami service running the image traefik/whoami. This is a test to validate the use of Authelia, so it will not use best practices to be deployed in production:

  • it will use a docker volume to store the config consisting of:
    • the list of users in a yaml file
    • the authelia config, which we will edit by hand
  • it will store session info in memory
  • it will record notifications in the mounted volume rather than send an email

Deploy authelia

Authelia can be deployed with this file compose-authelia.yml:

version: "3.8"
services:
  authelia:
    image: authelia/authelia
    ports:
      - 9091
    volumes:
      - authelia-config:/config
    environment:
      TZ: "Europe/Brussels"
      AUTHELIA_STORAGE_ENCRYPTION_KEY: "I3noKDFBshUzefnf89yFef2juS"
      AUTHELIA_SESSION_DOMAIN: "${DOMAIN?Variable not set}"
    deploy:
      labels:
      - traefik.enable=true
      - traefik.http.routers.auth.rule=Host(`auth.${DOMAIN?Variable not set}`)
      - traefik.http.routers.auth.entrypoints=http
      - traefik.http.routers.auth.middlewares=https-redirect
      - traefik.constraint-label=traefik-public
      - traefik.http.services.auth.loadbalancer.server.port=9091
      - traefik.docker.network=traefik-public
      # for https
      - traefik.http.routers.auths.entrypoints=https
      - traefik.http.routers.auths.rule=Host(`auth.${DOMAIN?Variable not set}`)
      # to generate certificates for this service, these labels are required:
      - traefik.http.routers.auths.tls=true
      - traefik.http.routers.auths.tls.certresolver=le
      - traefik.http.middlewares.authelia.forwardAuth.address=http://authelia:9091/api/verify?rd=https%3A%2F%2Fauth.${DOMAIN?Variable not set}%2F
      - traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true
      - traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email
      - traefik.http.middlewares.authelia-basic.forwardAuth.address=http://authelia:9091/api/verify?auth=basic
      - traefik.http.middlewares.authelia-basic.forwardAuth.trustForwardHeader=true
      - traefik.http.middlewares.authelia-basic.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email
    networks:
      - traefik-public
networks:
  # Use the previously created public network "traefik-public", shared with other
  traefik-public:
    external: true
volumes:
  authelia-config:

Deploying it with docker stack deploy -c compose-authelia.yml authelia will fail, but will create all volumes and authelia config files.

We then need to edit the config file stored on the volume, commenting some sections and uncommenting their replacement:

  • remove ldap section
    • add file section
  • remove redis section
  • remove smtp notification
    • add filesystem
  • remove mysql storage
    • add local

Editing a file on a volume is a very bad idea in production, but in this exploration phase it’s a huge time saver. We can use the approach previously published here:

docker run --rm -it -v authelia_authelia-config:/config ubuntu bash
apt update && apt install vim -y
vim /config/configuration.yml

Stop the authelia container to have Docker Swarm restart it:

docker stop $(docker ps -f "name=authelia" -q)

Check that it was restarted as expected: you should get a different id from the previous step with this command:

docker ps -f "name=authelia" -q

Login with authelia/authelia

Authelia generates a default user authelia/authelia that we can use to check we can login. Accessing the url auth.${DOMAIN} should display the Authelia login form, and authenticating with login authelia and password authelia should work. even if it asks to add a device for a second factor authentication, it means the first factor passed successfully.

Deploy the whoami service

Use this compose file:

version: '3'

services:
  whoami:
    # A container that exposes an API to show its IP address
    image: traefik/whoami
    ports:
    - 80
    deploy:
        labels:
            - traefik.enable=true
            - traefik.http.routers.whoami.rule=Host(`whoami.${DOMAIN?Variable not set}`)
            - traefik.http.routers.whoami.entrypoints=http
            - traefik.http.routers.whoami.middlewares=https-redirect
            - traefik.constraint-label=traefik-public
            - traefik.http.services.whoami.loadbalancer.server.port=80
            - traefik.docker.network=traefik-public
            # for https
            - traefik.http.routers.whoamis.entrypoints=https
            - traefik.http.routers.whoamis.rule=Host(`whoami.${DOMAIN?Variable not set}`)
            # to generate certificates for this service, these labels are required:
            - traefik.http.routers.whoamis.tls=true
            - traefik.http.routers.whoamis.tls.certresolver=le
    networks:
      - traefik-public
networks:
  # Use the previously created public network "traefik-public", shared with other
  traefik-public:
    external: true

After deployment with docker stack deploy -c compose-whoami.yml whoami you should be able to access the service at http://whoami.$DOMAIN.

Protecting the whoami service

Protecting the whoami service is just a matter of adding this label to it

            - traefik.http.routers.whoamis.middlewares=authelia@docker

If you prefer using HTTP Basic Authentication, use this label instead:

            - traefik.http.routers.whoamis.middlewares=authelia-basic@docker

But with the default configuration generated by Authelia, no policy will match your request the the whoami service, and access will be denied. Edit the authelia config file as previously, and add a rule the whoami service domain. I first made a mistake setting the policy of single factor (wrong domain name):

rules:
  - domain: 'whoami.example.com'
    policy: one_factor

looking at logs with

docker service logs authelia_authelia

, we have

authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:13+02:00" level=debug msg="Mark 1FA authentication attempt made by user 'authelia'" method=POST path=/api/firstfactor remote_ip=XXX.XXX.XXX.XXX
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:13+02:00" level=debug msg="Successful 1FA authentication attempt made by user 'authelia'" method=POST path=/api/firstfactor remote_ip=XXX.XXX.XXX.XXX
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:13+02:00" level=debug msg="Check authorization of subject username=authelia groups=admins,dev ip=XXX.XXX.XXX.XXX and object https://whoami.$DOMAIN/ (method GET)."
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:13+02:00" level=debug msg="No matching rule for subject username=authelia groups=admins,dev ip=XXX.XXX.XXX.XXX and url https://whoami.$DOMAIN/... Applying default policy."
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:13+02:00" level=debug msg="Required level for the URL https://whoami.$DOMAIN/ is 3" method=POST path=/api/firstfactor remote_ip=XXX.XXX.XXX.XXX
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:13+02:00" level=debug msg="Redirection URL https://whoami.$DOMAIN/ is safe" method=POST path=/api/firstfactor remote_ip=XXX.XXX.XXX.XXX
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:14+02:00" level=debug msg="Check authorization of subject username=authelia groups=admins,dev ip=XXX.XXX.XXX.XXX and object https://whoami.$DOMAIN/ (method GET)."
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:14+02:00" level=debug msg="No matching rule for subject username=authelia groups=admins,dev ip=XXX.XXX.XXX.XXX and url h
ttps://whoami.$DOMAIN/... Applying default policy."
authelia_authelia.1.rl9qtuan54yq@vmi763847    | time="2022-08-29T14:25:14+02:00" level=info msg="Access to https://whoami.$DOMAIN/ is forbidden to user authelia" method=GET path=/api/verify remote_ip=XXX.XXX.XXX.XXX

The correct rule is

rules:
  - domain: 'whoami.$DOMAIN'
    policy: one_factor

Remember to restart the authelia container after the change as previously.

Adding users

As detailed in the Authelia documentation, the yaml file storing users has this format:

  james: # this key is also the login
    displayname: "James Dean"
    password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
    email: james.dean@authelia.com

With Authelia running on your Swarm, you can generate th password hash, using the authelia config file like this:

docker exec -it  $(docker ps -f "name=authelia" -q) authelia --config /config/configuration.yml hash-password "my_secret_password" | sed -e "s/.* //"

The password is passed as argument to the command, which is not ideal (can be read in the terminal, is stored in the shell history unless you take your precaution, eg by undefining the environment variableHISTFILE). After adding a new user, you will need to restart authelia (at v4.36.5, though auto reload is on its way).