Ryan Schachte's Blog
Authentik Embedded Outpost with Docker Compose, Kubernetes and NGINX August 18th, 2024

In light of debugging for many hours and after realizing many people online having issues with Authentik and Docker, I’m going to share my configs that worked. In particular, many people were claiming the latest version of the Authentik embedded outpost no longer worked. While I think the docs are not very good in this area, the outpost is working fine.


Some things to take note of:

  • Using the embedded outpost and not a separate manual outpost (although I got this working too)
  • version: authentik version 2024.6.3

Docker-Compose Setup

Configuration

nginx.conf
http {
    # Basic HTTP configuration
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging settings
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error_debug.log debug;

    # This can be very useful for debugging (See usage in blocks below)
    log_format detailed_headers_log '[$time_local] $remote_addr - $remote_user - $server_name to: $upstream_addr: $request $status '
                                    'Host: "$host" '
                                    'X-Real-IP: "$remote_addr" '
                                    'X-Forwarded-For: "$proxy_add_x_forwarded_for" '
                                    'X-Forwarded-Proto: "$scheme" '
                                    'X-Forwarded-Host: "$host" '
                                    'X-Forwarded-Port: "$server_port" '
                                    'X-Original-URL: "$scheme://$http_host$request_uri" '
                                    'http_host: "$http_host" '
                                    'request_uri: "$request_uri" '
                                    'cookie: "$http_cookie" '
                                    'set_cookie: "$sent_http_set_cookie" '
                                    'upstream_http_set_cookie: "$upstream_http_set_cookie"';

    server {
        listen 80;
        server_name localhost;

        # I'm serving a dummy hello world behind auth
        root /usr/share/nginx/html;
        index index.html;

        # Increase buffer size for large headers
        # This is needed only if you get 'upstream sent too big header while reading response
        # header from upstream' error when trying to access an application protected by goauthentik
        proxy_buffers 8 16k;
        proxy_buffer_size 32k;

        # Make sure not to redirect traffic to a port 4443
        port_in_redirect off;

        location / {
            try_files $uri $uri/ /index.html;
            proxy_set_header Host $host;

            ##############################
            # authentik-specific config
            ##############################
            auth_request     /outpost.goauthentik.io/auth/nginx;
            error_page       401 = @goauthentik_proxy_signin;
            auth_request_set $auth_cookie $upstream_http_set_cookie;
            add_header       Set-Cookie $auth_cookie;

            # translate headers from the outposts back to the actual upstream
            auth_request_set $authentik_username $upstream_http_x_authentik_username;
            auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
            auth_request_set $authentik_email $upstream_http_x_authentik_email;
            auth_request_set $authentik_name $upstream_http_x_authentik_name;
            auth_request_set $authentik_uid $upstream_http_x_authentik_uid;

            proxy_set_header X-authentik-username $authentik_username;
            proxy_set_header X-authentik-groups $authentik_groups;
            proxy_set_header X-authentik-email $authentik_email;
            proxy_set_header X-authentik-name $authentik_name;
            proxy_set_header X-authentik-uid $authentik_uid;
        }

        location /outpost.goauthentik.io {
            proxy_pass              http://server:9000/outpost.goauthentik.io;
            proxy_set_header        Host $http_host;
            proxy_set_header        X-Original-URL $scheme://$http_host$request_uri;

            # More detailed debug logs for header debug
            access_log /var/log/nginx/authentik_access_debug.log detailed_headers_log;

            add_header              Set-Cookie $auth_cookie;
            auth_request_set        $auth_cookie $upstream_http_set_cookie;
            proxy_pass_request_body off;
            proxy_set_header        Content-Length "";
        }

        location @goauthentik_proxy_signin {
            internal;

            # More detailed debug logs for header debug
            access_log /var/log/nginx/redirect_authentik_access_debug.log detailed_headers_log;

            add_header Set-Cookie $auth_cookie;
            return 302 $scheme://$http_host/outpost.goauthentik.io/start?rd=$request_uri;
        }
    }
}

events {
    worker_connections 1024;
}
docker-compose.yml
version: "3.4"
 
services:
  # For Authentik persistence
  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
 
  # For Authentik persistence
  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
 
  # Authentik Core
  server:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-latest}
    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
    ports:
      - "${COMPOSE_PORT_HTTP:-9000}:9000"
      - "${COMPOSE_PORT_HTTPS:-9443}:9443"
    depends_on:
      - postgresql
      - redis
 
  # Authentik Worker Node
  worker:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-latest}
    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
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./media:/media
      - ./certs:/certs
      - ./custom-templates:/templates
    env_file:
      - .env
    depends_on:
      - postgresql
      - redis
 
  # Reverse proxy for talking to our applications that are protected by Authentik
  nginx:
    image: nginx:latest
    ports:
      - "8181:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./index.html:/usr/share/nginx/html/index.html:ro
      - ./index.html:/usr/share/nginx/index.html:ro
    restart: unless-stopped
 
volumes:
  database:
    driver: local
  redis:
    driver: local
index.html for NGINX dummy page
<html>
    <body>
        hi
    </body>
</html

Tips & Debugging

Debugging NGINX is what really saved me here from logs.

Add an error log (see above) with detailed logging fields containing the headers:

error_log /var/log/nginx/error_debug.log debug;

# This can be very useful for debugging (See usage in blocks below)
log_format detailed_headers_log '[$time_local] $remote_addr - $remote_user - $server_name to: $upstream_addr: $request $status '
                                'Host: "$host" '
                                'X-Real-IP: "$remote_addr" '
                                'X-Forwarded-For: "$proxy_add_x_forwarded_for" '
                                'X-Forwarded-Proto: "$scheme" '
                                'X-Forwarded-Host: "$host" '
                                'X-Forwarded-Port: "$server_port" '
                                'X-Original-URL: "$scheme://$http_host$request_uri" '
                                'http_host: "$http_host" '
                                'request_uri: "$request_uri" '
                                'cookie: "$http_cookie" '
                                'set_cookie: "$sent_http_set_cookie" '
                                'upstream_http_set_cookie: "$upstream_http_set_cookie"';

Add them to each /location block by dropping in:

access_log /var/log/nginx/authentik_access_debug.log detailed_headers_log;

Kubernetes Setup

First, read this https://docs.goauthentik.io/docs/providers/proxy/server_nginx.

I am using an NGINX ingress where:

  • test.homelab.dev is my protected route
  • sso.homelab.dev is my authentik server
    • authentik-server:80 is the internal service and port
    • Runs in default namespace

Enable auth snippets on NGINX ingress controller

In order for the recommended annotations to work, I needed to enable auth snippets on my K8s cluster. See: https://stackoverflow.com/questions/77274854/ingress-controller-does-not-allow-snippets

  1. Get the config map related to your ingress controller
$ kubectl get -A cm | grep ingress | grep controller
  kube-system                   rke2-ingress-nginx-controller      1      377d
  1. Edit the config map by changing allow-snippet-annotations: “false” to allow-snippet-annotations: “true”
$ kubectl -n kube-system edit cm rke2-ingress-nginx-controller
"/tmp/kubectl-edit-1940673600.yaml" 25L, 910C written
configmap/rke2-ingress-nginx-controller edited
  1. Retart the controller
$ kubectl -n kube-system rollout restart ds rke2-ingress-nginx-controller
daemonset.apps/rke2-ingress-nginx-controller restarted

Configure NGINX ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: authentik-outpost
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/auth-url: http://authentik-server.default.svc.cluster.local/outpost.goauthentik.io/auth/nginx
    nginx.ingress.kubernetes.io/auth-signin: https://test.homelab.dev/outpost.goauthentik.io/start?rd=$escaped_request_uri
    nginx.ingress.kubernetes.io/auth-response-headers: Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid
    nginx.ingress.kubernetes.io/auth-snippet: |
      proxy_set_header X-Forwarded-Host $http_host;

spec:
  tls:
    - hosts:
        - test.homelab.dev
      secretName: homelab-wildcard-tls
  rules:
    - host: test.homelab.dev
      http:
        paths:
          - path: /outpost.goauthentik.io
            pathType: Prefix
            backend:
              service:
                name: authentik-server
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx
                port:
                  number: 80

Setup Authentik Outpost

...
authentik_host: https://sso.homelab.dev
...
  • Hit Update

Setup Authentik Provider

The External host will be my application https://test.homelab.dev.

Care to comment?