feat(garage,authentik): console multi-domain + standalone proxy outpost

Two related additions for gating a cross-host service (garage WebUI on a
storage host) behind authentik without breaking on TLS or X-Forwarded-Host:

garage role:
- Add garage_webui_domains (list), mirroring garage_s3_domains. The
  console router now accepts every entry, so a DMZ reverseproxy can use a
  backend_host whose cert SAN matches an internal *.int.* name instead of
  connecting by IP (which fails acme cert verification). Defaults to the
  single garage_webui_domain so existing inventories are unaffected.

authentik_outpost_proxy role (new):
- Standalone proxy (ForwardAuth) outpost, modelled on authentik_outpost_ldap.
  Co-locate it with the protected service so the ForwardAuth subrequest
  reaches authentik over the local docker network. The embedded outpost
  only works for services on the authentik host: a cross-host subrequest
  routed through an extra reverse-proxy hop arrives with a polluted
  X-Forwarded-Host (port/comma-appended), which the outpost no longer
  matches against the provider's external_host (404).
This commit is contained in:
Simon Bärlocher 2026-06-05 14:22:36 +02:00
parent a8954f525c
commit 0733d5710f
No known key found for this signature in database
GPG key ID: 63DE20495932047A
12 changed files with 195 additions and 2 deletions

View file

@ -0,0 +1,58 @@
# authentik_outpost_proxy
Deploys an [authentik](https://goauthentik.io) proxy (ForwardAuth)
outpost via Docker Compose. The outpost serves the
`/outpost.goauthentik.io/auth/*` endpoints locally, so a Traefik
`ForwardAuth` middleware on the same host can gate services behind
authentik without routing the auth subrequest through additional reverse
proxies.
Use this when the protected service runs on a **different host** than the
authentik server: the embedded outpost only works for services co-located
with authentik (the subrequest must reach it without an intermediate proxy
mangling `X-Forwarded-Host`). Co-locating a proxy outpost with the service
keeps the ForwardAuth subrequest on the local docker network.
The outpost connects back to an authentik server using an outpost token
issued in the authentik admin interface (register the outpost there and
assign the proxy providers it should serve). The image version must match
the authentik server version.
## Requirements
- Docker and Docker Compose on the target host (e.g. via
`digitalboard.core.base`)
- Ansible collection: `community.docker`
- An authentik proxy outpost registered on the server, with the proxy
providers for the protected services assigned to it.
## Role variables
| Variable | Default | Description |
| --- | --- | --- |
| `authentik_outpost_proxy_image` | `ghcr.io/goauthentik/proxy:2026.2.2` | Outpost image (match the server version). |
| `authentik_outpost_proxy_host` | `https://authentik.local.test` | URL of the authentik server. |
| `authentik_outpost_proxy_token` | `changeme` | Outpost token — **override this**. |
| `authentik_outpost_proxy_insecure` | `"true"` | Skip TLS verification toward the authentik server. |
| `authentik_outpost_proxy_network` | `proxy` | Docker network the local Traefik routers and this outpost share. |
| `authentik_outpost_proxy_extra_hosts` | `[]` | Extra `host:ip` entries for in-container DNS. |
The local ForwardAuth middleware then points at the container over the
shared network, e.g.
`http://authentik-outpost-proxy-proxy-1:9000/outpost.goauthentik.io/auth/traefik`.
## Example
```yaml
- hosts: storage
become: true
roles:
- role: digitalboard.core.authentik_outpost_proxy
vars:
authentik_outpost_proxy_host: "https://auth.example.com"
authentik_outpost_proxy_token: "{{ vault_authentik_proxy_outpost_token }}"
```
## License
MIT-0

View file

@ -0,0 +1,28 @@
#SPDX-License-Identifier: MIT-0
---
# defaults file for authentik_outpost_proxy
# Base directory configuration (inherited from base role or defined here)
docker_compose_base_dir: /etc/docker/compose
docker_volume_base_dir: /srv/data
# Service configuration
authentik_outpost_proxy_service_name: authentik-outpost-proxy
authentik_outpost_proxy_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ authentik_outpost_proxy_service_name }}"
# Container image (must match authentik server version)
authentik_outpost_proxy_image: "ghcr.io/goauthentik/proxy:2026.2.2"
# Connection to authentik server
authentik_outpost_proxy_host: "https://authentik.local.test"
authentik_outpost_proxy_token: "changeme"
authentik_outpost_proxy_insecure: "true"
# Traefik network the service-side routers live on, so the local
# ForwardAuth middleware can reach this outpost over the docker network.
authentik_outpost_proxy_network: "proxy"
# Extra hosts for DNS resolution within the container (e.g. pinning the
# authentik FQDN to a backend IP when the public name is unreachable).
authentik_outpost_proxy_extra_hosts: []
# - "auth.example.com:192.168.56.11"

View file

@ -0,0 +1,3 @@
#SPDX-License-Identifier: MIT-0
---
# handlers file for authentik_outpost_proxy

View file

@ -0,0 +1,28 @@
#SPDX-License-Identifier: MIT-0
galaxy_info:
author: digitalboard
description: Deploy an authentik proxy (ForwardAuth) outpost via Docker Compose, co-located with the services it protects
company: Digitalboard
license: MIT-0
min_ansible_version: "2.14"
platforms:
- name: Debian
versions:
- bookworm
- name: Ubuntu
versions:
- jammy
- noble
galaxy_tags:
- authentik
- proxy
- forwardauth
- outpost
- sso
- docker
- digitalboard
dependencies: []

View file

@ -0,0 +1,26 @@
#SPDX-License-Identifier: MIT-0
---
# tasks file for authentik_outpost_proxy
- name: Create docker compose directory
file:
path: "{{ authentik_outpost_proxy_docker_compose_dir }}"
state: directory
mode: '0755'
- name: Create docker-compose file for authentik proxy outpost
template:
src: docker-compose.yml.j2
dest: "{{ authentik_outpost_proxy_docker_compose_dir }}/docker-compose.yml"
mode: '0644'
- name: Start authentik proxy outpost container
community.docker.docker_compose_v2:
project_src: "{{ authentik_outpost_proxy_docker_compose_dir }}"
state: present
wait: true
wait_timeout: 120
retries: 3
delay: 15
register: result
until: result is not failed

View file

@ -0,0 +1,22 @@
services:
proxy:
image: {{ authentik_outpost_proxy_image }}
restart: unless-stopped
environment:
AUTHENTIK_HOST: {{ authentik_outpost_proxy_host }}
AUTHENTIK_TOKEN: {{ authentik_outpost_proxy_token }}
AUTHENTIK_INSECURE: "{{ authentik_outpost_proxy_insecure }}"
{% if authentik_outpost_proxy_extra_hosts | length > 0 %}
extra_hosts:
{% for host in authentik_outpost_proxy_extra_hosts %}
- "{{ host }}"
{% endfor %}
{% endif %}
labels:
- traefik.enable=false
networks:
- {{ authentik_outpost_proxy_network }}
networks:
{{ authentik_outpost_proxy_network }}:
external: true

View file

@ -0,0 +1,2 @@
#SPDX-License-Identifier: MIT-0
localhost

View file

@ -0,0 +1,6 @@
#SPDX-License-Identifier: MIT-0
---
- hosts: localhost
remote_user: root
roles:
- authentik_outpost_proxy

View file

@ -0,0 +1,3 @@
#SPDX-License-Identifier: MIT-0
---
# vars file for authentik_outpost_proxy