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:
parent
a8954f525c
commit
0733d5710f
12 changed files with 195 additions and 2 deletions
58
roles/authentik_outpost_proxy/README.md
Normal file
58
roles/authentik_outpost_proxy/README.md
Normal 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
|
||||||
28
roles/authentik_outpost_proxy/defaults/main.yml
Normal file
28
roles/authentik_outpost_proxy/defaults/main.yml
Normal 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"
|
||||||
3
roles/authentik_outpost_proxy/handlers/main.yml
Normal file
3
roles/authentik_outpost_proxy/handlers/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# handlers file for authentik_outpost_proxy
|
||||||
28
roles/authentik_outpost_proxy/meta/main.yml
Normal file
28
roles/authentik_outpost_proxy/meta/main.yml
Normal 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: []
|
||||||
26
roles/authentik_outpost_proxy/tasks/main.yml
Normal file
26
roles/authentik_outpost_proxy/tasks/main.yml
Normal 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
|
||||||
|
|
@ -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
|
||||||
2
roles/authentik_outpost_proxy/tests/inventory
Normal file
2
roles/authentik_outpost_proxy/tests/inventory
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
localhost
|
||||||
6
roles/authentik_outpost_proxy/tests/test.yml
Normal file
6
roles/authentik_outpost_proxy/tests/test.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
- hosts: localhost
|
||||||
|
remote_user: root
|
||||||
|
roles:
|
||||||
|
- authentik_outpost_proxy
|
||||||
3
roles/authentik_outpost_proxy/vars/main.yml
Normal file
3
roles/authentik_outpost_proxy/vars/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#SPDX-License-Identifier: MIT-0
|
||||||
|
---
|
||||||
|
# vars file for authentik_outpost_proxy
|
||||||
|
|
@ -20,7 +20,15 @@ garage_image: "dxflrs/garage:v2.1.0"
|
||||||
garage_s3_domains:
|
garage_s3_domains:
|
||||||
- "storage.local.test"
|
- "storage.local.test"
|
||||||
garage_web_domain: "web.storage.local.test"
|
garage_web_domain: "web.storage.local.test"
|
||||||
|
# Canonical WebUI console hostname (first entry of garage_webui_domains).
|
||||||
garage_webui_domain: "console.storage.local.test"
|
garage_webui_domain: "console.storage.local.test"
|
||||||
|
# FQDNs the WebUI console router accepts. The first entry is the canonical
|
||||||
|
# public domain; further entries cover internal *.int.* names so a DMZ
|
||||||
|
# reverseproxy can use a backend_host whose cert SAN matches (same pattern
|
||||||
|
# as garage_s3_domains). Defaults to just the canonical domain so existing
|
||||||
|
# inventories that only set garage_webui_domain keep working unchanged.
|
||||||
|
garage_webui_domains:
|
||||||
|
- "{{ garage_webui_domain }}"
|
||||||
|
|
||||||
# Garage WebUI configuration
|
# Garage WebUI configuration
|
||||||
garage_webui_enabled: true
|
garage_webui_enabled: true
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,16 @@ argument_specs:
|
||||||
garage_webui_domain:
|
garage_webui_domain:
|
||||||
type: str
|
type: str
|
||||||
default: console.storage.local.test
|
default: console.storage.local.test
|
||||||
description: Hostname serving the WebUI console.
|
description: Canonical WebUI console hostname (first entry of C(garage_webui_domains)).
|
||||||
|
garage_webui_domains:
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
default: ['console.storage.local.test']
|
||||||
|
description:
|
||||||
|
- FQDNs the WebUI console router accepts. The first entry is the
|
||||||
|
canonical public domain; further entries cover internal
|
||||||
|
C(*.int.*) names so a DMZ reverseproxy can use a C(backend_host)
|
||||||
|
whose cert SAN matches. Defaults to just C(garage_webui_domain).
|
||||||
|
|
||||||
garage_webui_enabled:
|
garage_webui_enabled:
|
||||||
type: bool
|
type: bool
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ services:
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=true
|
- traefik.enable=true
|
||||||
- traefik.docker.network={{ garage_traefik_network }}
|
- traefik.docker.network={{ garage_traefik_network }}
|
||||||
- traefik.http.routers.{{ garage_service_name }}-console.rule=Host(`{{ garage_webui_domain }}`)
|
- traefik.http.routers.{{ garage_service_name }}-console.rule={% for d in garage_webui_domains %}Host(`{{ d }}`){% if not loop.last %} || {% endif %}{% endfor +%}
|
||||||
{% if garage_use_ssl %}
|
{% if garage_use_ssl %}
|
||||||
- traefik.http.routers.{{ garage_service_name }}-console.entrypoints=websecure
|
- traefik.http.routers.{{ garage_service_name }}-console.entrypoints=websecure
|
||||||
- traefik.http.routers.{{ garage_service_name }}-console.tls=true
|
- traefik.http.routers.{{ garage_service_name }}-console.tls=true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue