feat(demo): add full inventories for mbazürich and phbern #2

Open
Simon wants to merge 5 commits from feat/demo-mbaz-phbe-inventories into main
8 changed files with 58 additions and 110 deletions
Showing only changes of commit 2942dabc54 - Show all commits

View file

@ -10,26 +10,9 @@ _authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data
authentik_domains: authentik_domains:
- "auth.gymb.souveredu.ch" - "auth.gymb.souveredu.ch"
# Internal FQDN for server-to-server calls (Nextcloud OIDC discovery,
# token, userinfo; LDAP outpost configuration pull). Traefik rewrites
# the Host header to `authentik_domains[0]` on these routers so authentik
# still emits issuer URLs against the public hostname — that keeps the
# iss claim matching what the browser sees while the traffic itself
# stays inside the LAN (the DMZ has no hairpin-NAT for the public IP).
authentik_host_rewrite_domains:
- "auth.int.gymb.souveredu.ch"
authentik_secret_key: "{{ _authentik.secret_key }}" authentik_secret_key: "{{ _authentik.secret_key }}"
authentik_postgres_password: "{{ _authentik.postgres_password }}" authentik_postgres_password: "{{ _authentik.postgres_password }}"
# Dedicated FQDN for cross-host ForwardAuth (storage Traefik calling
# /outpost.goauthentik.io/auth/traefik). Routing through the public
# auth.gymb.* FQDN doesn't work — Authentik sees Host: auth.gymb.* and
# routes to ASGI which 404s the outpost path. This FQDN sits outside
# authentik_domains so the same request falls through to the embedded
# outpost handler (which matches the protected app via X-Forwarded-Host).
authentik_outpost_domains:
- "outpost.auth.int.gymb.souveredu.ch"
# LDAP outpost (provider for nextcloud) # LDAP outpost (provider for nextcloud)
authentik_ldap_apps: authentik_ldap_apps:
- slug: ldap - slug: ldap
@ -41,9 +24,10 @@ authentik_ldap_outpost:
name: "ldap-outpost" name: "ldap-outpost"
token: "{{ _authentik.ldap_outpost_token }}" token: "{{ _authentik.ldap_outpost_token }}"
config: config:
# Outpost pulls config from authentik over the internal FQDN — keeps # Outpost pulls config from authentik over the public FQDN, which
# the round-trip in the LAN with a valid cert. # resolves (internal DNS view) to the DMZ reverseproxy the backend
authentik_host: "https://auth.int.gymb.souveredu.ch/" # subnet can reach — keeps the round-trip in the LAN with a valid cert.
authentik_host: "https://auth.gymb.souveredu.ch/"
log_level: "info" log_level: "info"
# Proxy providers (ForwardAuth) — gate downstream services behind # Proxy providers (ForwardAuth) — gate downstream services behind

View file

@ -3,5 +3,8 @@
# authenticate against the authentik server it talks to. # authenticate against the authentik server it talks to.
_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" _authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}"
authentik_outpost_ldap_host: "https://auth.int.gymb.souveredu.ch" # Public FQDN resolves (internal DNS view) to the DMZ reverseproxy the
# backend subnet can reach, so the outpost->authentik round-trip stays in
# the LAN with a valid cert and matches the iss claim authentik emits.
authentik_outpost_ldap_host: "https://auth.gymb.souveredu.ch"
authentik_outpost_ldap_token: "{{ _authentik.ldap_outpost_token }}" authentik_outpost_ldap_token: "{{ _authentik.ldap_outpost_token }}"

View file

@ -20,11 +20,11 @@ bookstack_admin_email: "admin@gymb.souveredu.ch"
bookstack_admin_name: "BookStack Admin" bookstack_admin_name: "BookStack Admin"
# OIDC against Authentik. BookStack compares OIDC_ISSUER strictly against # OIDC against Authentik. BookStack compares OIDC_ISSUER strictly against
# the `iss` claim in the discovery response. Authentik emits the public # the `iss` claim in the discovery response, and Authentik emits the
# auth.gymb.* hostname there (host-rewrite middleware ensures the claim # public auth.gymb.* hostname there, so the issuer must use the public
# matches what browsers see during login), so the issuer URL must use the # FQDN. That FQDN resolves (internal DNS view) to the DMZ reverseproxy,
# public FQDN. Pinning auth.gymb.* in /etc/hosts below keeps the actual # which the backend subnet can reach, so the server-to-server calls stay
# server-to-server traffic on the LAN. # in the LAN.
bookstack_oidc_enabled: true bookstack_oidc_enabled: true
bookstack_oidc_name: "Authentik" bookstack_oidc_name: "Authentik"
bookstack_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/bookstack/" bookstack_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/bookstack/"
@ -34,10 +34,3 @@ bookstack_oidc_additional_scopes: "openid profile email"
bookstack_oidc_user_to_groups: true bookstack_oidc_user_to_groups: true
bookstack_oidc_groups_claim: "groups" bookstack_oidc_groups_claim: "groups"
bookstack_oidc_auto_initiate: false bookstack_oidc_auto_initiate: false
# Pin auth.gymb.* to the application host so server-to-server OIDC calls
# (discovery, token, userinfo, jwks) stay in the LAN and reach authentik
# directly without hairpinning through the DMZ (which has no NAT loop
# back to its own public IP).
bookstack_extra_hosts:
- "auth.gymb.souveredu.ch:172.16.19.101"

View file

@ -18,12 +18,11 @@ homarr_admin_password: "{{ _homarr.admin_password }}"
# AUTH_OIDC_AUTO_LOGIN bypasses the normal /login page. # AUTH_OIDC_AUTO_LOGIN bypasses the normal /login page.
# #
# Issuer must match the `iss` claim authentik emits, which is always the # Issuer must match the `iss` claim authentik emits, which is always the
# public FQDN (authentik's host-rewrite middleware aligns the claim with # public FQDN. Homarr (oauth4webapi) does a strict 1:1 comparison between
# what browsers see). Homarr (oauth4webapi) does a strict 1:1 comparison # the discovery response's issuer and this URL — an internal FQDN here
# between the discovery response's issuer and this URL — using the # fails with OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED. The public FQDN
# internal FQDN here fails with OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED. # resolves (internal DNS view) to the DMZ reverseproxy, which the backend
# The extra_hosts pin below keeps the actual discovery/token/userinfo # subnet can reach, so discovery/token/userinfo stay in the LAN.
# traffic on the LAN.
homarr_auth_providers: "credentials,oidc" homarr_auth_providers: "credentials,oidc"
homarr_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/homarr/" homarr_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/homarr/"
homarr_oidc_client_id: "homarr" homarr_oidc_client_id: "homarr"
@ -33,14 +32,6 @@ homarr_oidc_scopes: "openid profile email groups"
homarr_oidc_groups_attribute: "groups" homarr_oidc_groups_attribute: "groups"
homarr_oidc_auto_login: "true" homarr_oidc_auto_login: "true"
# Pin the public authentik FQDN to the application host so OIDC
# discovery (and downstream token/userinfo) calls from the homarr
# container stay in the LAN. Without this, fetch() to auth.gymb.* would
# hit the public IP and time out in the DMZ (no hairpin-NAT). Same
# pattern as nextcloud_extra_hosts.
homarr_extra_hosts:
- "auth.gymb.souveredu.ch:172.16.19.101"
# Default board with shortcuts to the other gymburgdorf services. Width # Default board with shortcuts to the other gymburgdorf services. Width
# values describe horizontal grid cells (1-10 desktop / 6 tablet / 2 # values describe horizontal grid cells (1-10 desktop / 6 tablet / 2
# mobile, packed left-to-right). # mobile, packed left-to-right).

View file

@ -62,25 +62,16 @@ nextcloud_s3_port: 443
nextcloud_s3_ssl: true nextcloud_s3_ssl: true
nextcloud_s3_usepath_style: true nextcloud_s3_usepath_style: true
# OIDC server-to-server discovery / token / userinfo goes to # OIDC discovery/token/userinfo and the S3 backend both target FQDNs
# auth.int.gymb.souveredu.ch (LAN, RFC1918). Nextcloud's DnsPinMiddleware # that resolve to RFC1918 addresses (auth.gymb.* via the reverseproxy,
# would otherwise block that as "local server access". # s3.int.* directly), which Nextcloud's DnsPinMiddleware would otherwise
# block as "local server access".
nextcloud_allow_local_remote_servers: true nextcloud_allow_local_remote_servers: true
# Share the LDAP docker network with the authentik LDAP outpost # Share the LDAP docker network with the authentik LDAP outpost
nextcloud_extra_networks: nextcloud_extra_networks:
- ldap - ldap
# Pin the public authentik FQDN to the application host so server-to-server
# OIDC traffic (token, userinfo, jwks — endpoints the discovery doc lists
# under auth.gymb.* even when discovery itself is fetched via auth.int.*)
# stays in the LAN. Without this, curl in the PHP container would hit the
# public IP and time out in the DMZ (no hairpin-NAT). The DnsPin middleware
# only honours /etc/hosts when allow_local_remote_servers is enabled, so
# that flag (set above) is what makes this entry effective.
nextcloud_extra_hosts:
- "auth.gymb.souveredu.ch:172.16.19.101"
# LDAP backend (Authentik LDAP outpost) # LDAP backend (Authentik LDAP outpost)
nextcloud_ldap_enabled: true nextcloud_ldap_enabled: true
nextcloud_ldap_config: nextcloud_ldap_config:
@ -118,13 +109,12 @@ nextcloud_oidc_providers:
display_name: "Login with Authentik" display_name: "Login with Authentik"
client_id: nextcloud client_id: nextcloud
client_secret: "{{ _authentik.nextcloud_oidc_secret }}" client_secret: "{{ _authentik.nextcloud_oidc_secret }}"
# Discovery via the internal FQDN (LAN-only) — the DMZ has no # Discovery via the public FQDN. It resolves (internal DNS view) to
# hairpin-NAT for the public IP, so server-to-server calls to # the DMZ reverseproxy, which the backend subnet can now reach, so
# auth.gymb.* would time out. The traefik router for auth.int.* # the server-to-server call stays in the LAN while the iss claim and
# rewrites the Host header to auth.gymb.souveredu.ch before the # all listed endpoints match the public hostname the browser sees
# request reaches authentik, so the iss claim authentik emits still # during login — no host-rewrite or /etc/hosts pin needed.
# matches the public hostname the browser sees during login. discovery_url: "https://auth.gymb.souveredu.ch/application/o/nextcloud/.well-known/openid-configuration"
discovery_url: "https://auth.int.gymb.souveredu.ch/application/o/nextcloud/.well-known/openid-configuration"
scope: "openid email profile" scope: "openid email profile"
unique_uid: true unique_uid: true
mapping: mapping:

View file

@ -23,18 +23,13 @@ opnform_admin_name: "OpnForm Admin"
opnform_admin_email: "admin@gymb.souveredu.ch" opnform_admin_email: "admin@gymb.souveredu.ch"
opnform_admin_password: "{{ _opnform.admin_password }}" opnform_admin_password: "{{ _opnform.admin_password }}"
# OIDC against Authentik. Discovery via the internal FQDN keeps # OIDC against Authentik. The public auth.gymb.* FQDN resolves (internal
# server-to-server traffic in the LAN; Authentik's host-rewrite router # DNS view) to the DMZ reverseproxy, which the backend subnet can reach,
# rewrites the Host header to auth.gymb.* before the request reaches # so OpnForm's discovery/token/userinfo calls stay in the LAN while the
# authentik so the iss claim still matches the public hostname browsers # iss claim matches the public hostname browsers see during login.
# see during login.
opnform_oidc_enabled: true opnform_oidc_enabled: true
# Issuer must use the public auth.gymb.* FQDN: OpnForm does OIDC # Issuer must use the public FQDN: OpnForm validates the token's `iss`
# discovery and then validates the token's `iss` claim against this # claim against this value, and Authentik emits the public hostname there.
# value. Authentik emits the public hostname in `iss` (its host-rewrite
# middleware keeps the claim aligned with what browsers see), so an
# internal-FQDN issuer here would fail iss validation. The extra_hosts
# pin below keeps the actual discovery/token/userinfo traffic on the LAN.
opnform_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/opnform/" opnform_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/opnform/"
opnform_oidc_client_id: "opnform" opnform_oidc_client_id: "opnform"
opnform_oidc_client_secret: "{{ _opnform.oidc_client_secret }}" opnform_oidc_client_secret: "{{ _opnform.oidc_client_secret }}"
@ -53,10 +48,3 @@ opnform_oidc_force_login: true
# Break-glass: /login?bypass=1 reaches the email form when the IdP is # Break-glass: /login?bypass=1 reaches the email form when the IdP is
# down. # down.
opnform_oidc_sso_entrypoint: true opnform_oidc_sso_entrypoint: true
# Pin auth.gymb.* to the application host so server-to-server OIDC
# calls (token, userinfo, jwks — endpoints discovery returns under the
# public hostname even when discovery itself is fetched via auth.int.*)
# stay in the LAN.
opnform_extra_hosts:
- "auth.gymb.souveredu.ch:172.16.19.101"

View file

@ -14,17 +14,19 @@ garage_use_ssl: true
garage_webui_enabled: true garage_webui_enabled: true
# Gate the WebUI behind authentik (admins-only, via policy-binding on the # Gate the WebUI behind authentik (admins-only, via policy-binding on the
# authentik proxy app). Replaces the htpasswd Basic-Auth — AUTH_USER_PASS # authentik proxy app). Replaces the htpasswd Basic-Auth — AUTH_USER_PASS
# is dropped from the compose env when this is true. The forwardauth URL # is dropped from the compose env when this is true.
# uses a dedicated outpost-only FQDN that's deliberately outside #
# authentik_domains so Authentik routes it to the embedded outpost (not # The embedded outpost only serves the /outpost.goauthentik.io/auth path
# ASGI). The public auth.gymb.* FQDN would 404 here — Authentik routes # when the request reaches authentik with Host set to a configured
# any Host matching an auth-domain to ASGI which doesn't serve the outpost # authentik_domain (auth.gymb.*); it then matches the protected app
# path. The outpost itself then matches the protected app via # (console.s3.*) via X-Forwarded-Host. So the ForwardAuth must hit the
# X-Forwarded-Host (Traefik forwards it via trustForwardHeader=true). # app-host's `authentik` router (Host(auth.gymb), passHostHeader=true) —
# The FQDN is pinned to the application host via traefik_extra_hosts so # NOT the DMZ reverseproxy (strips X-Forwarded-Host) and NOT the auth.int
# the request stays in the LAN. # rewrite router (overwrites X-Forwarded-Host). auth.gymb.* is therefore
# pinned to the application host in storage/traefik.yml so this single hop
# preserves the forwarded headers and stays in the LAN.
garage_webui_authentik_forward_auth: true garage_webui_authentik_forward_auth: true
garage_webui_authentik_forward_auth_url: "https://outpost.auth.int.gymb.souveredu.ch/outpost.goauthentik.io/auth/traefik" garage_webui_authentik_forward_auth_url: "https://auth.gymb.souveredu.ch/outpost.goauthentik.io/auth/traefik"
# Kept for completeness — only used when authentik ForwardAuth is off. # Kept for completeness — only used when authentik ForwardAuth is off.
garage_webui_username: "admin" garage_webui_username: "admin"
garage_webui_password: "{{ _garage.webui_password | default('disabled') }}" garage_webui_password: "{{ _garage.webui_password | default('disabled') }}"

View file

@ -1,18 +1,12 @@
--- ---
# Local traefik needs to reach authentik for the ForwardAuth subrequest # The garage-webui ForwardAuth middleware fires a subrequest to authentik
# the garage-webui router fires. The public IP is unreachable from this # (see garage.yml). It must reach the app-host's `authentik` router
# subnet (no DMZ hairpin), so pin both auth FQDNs directly at the # directly — going via the DMZ reverseproxy strips X-Forwarded-Host, which
# application host where authentik runs. Without this the forwardauth # breaks the embedded outpost's app matching. The internal DNS view points
# middleware would time out and every garage-console request would 502. # auth.gymb.* at the reverseproxy, so pin it to the application host here
# - auth.gymb.* covers any future server-to-server traffic on the public # to force the single direct hop that preserves the forwarded headers.
# FQDN.
# - outpost.auth.int.gymb.* is the dedicated outpost endpoint actually
# used by the ForwardAuth middleware (see garage.yml). It exists only
# to skip Authentik's ASGI handler, which 404s the outpost path when
# Host is one of the configured authentik_domains.
traefik_extra_hosts: traefik_extra_hosts:
- "auth.gymb.souveredu.ch:172.16.19.101" - "auth.gymb.souveredu.ch:172.16.19.101"
- "outpost.auth.int.gymb.souveredu.ch:172.16.19.101"
# Services hosted on `storage` that the DMZ reverseproxy should forward # Services hosted on `storage` that the DMZ reverseproxy should forward
# public traffic to. See application/traefik.yml for the mechanism. # public traffic to. See application/traefik.yml for the mechanism.
@ -24,8 +18,11 @@ traefik_dmz_exposed_services:
protocol: https protocol: https
- name: garage-webui - name: garage-webui
domain: console.s3.gymb.souveredu.ch domain: console.s3.gymb.souveredu.ch
# No internal FQDN/cert SAN for console.s3 yet — would need an # Pre-existing limitation, orthogonal to the DNS cleanup: the DMZ
# extra_domain on garage-webui. Until then this route will 500 # verifies the storage backend cert (acme cert_mode), but with no
# against the storage backend (cert mismatch on raw IP). # backend_host set it connects by IP, which the storage cert has no
# SAN for, so this route fails the TLS verify. Fixing it needs the
# garage role to expose a console extra_domain (e.g. console.s3.int.*)
# for the backend_host + cert SAN — tracked separately.
port: 443 port: 443
protocol: https protocol: https