diff --git a/inventories/demo-gymburgdorf/host_vars/application/authentik.yml b/inventories/demo-gymburgdorf/host_vars/application/authentik.yml index d5a354f..62ffa06 100644 --- a/inventories/demo-gymburgdorf/host_vars/application/authentik.yml +++ b/inventories/demo-gymburgdorf/host_vars/application/authentik.yml @@ -1,7 +1,7 @@ --- # Bao secret expected at /data/authentik with keys: # secret_key, postgres_password, admin_password, -# ldap_outpost_token, proxy_outpost_token, +# ldap_outpost_token, # nextcloud_oidc_secret, # opnform_oidc_secret, homarr_oidc_secret, bookstack_oidc_secret _authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" @@ -10,9 +10,26 @@ _authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data authentik_domains: - "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_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) authentik_ldap_apps: - slug: ldap @@ -24,10 +41,9 @@ authentik_ldap_outpost: name: "ldap-outpost" token: "{{ _authentik.ldap_outpost_token }}" config: - # Outpost pulls config from authentik over the public FQDN, which - # resolves (internal DNS view) to the DMZ reverseproxy the backend - # subnet can reach — keeps the round-trip in the LAN with a valid cert. - authentik_host: "https://auth.gymb.souveredu.ch/" + # Outpost pulls config from authentik over the internal FQDN — keeps + # the round-trip in the LAN with a valid cert. + authentik_host: "https://auth.int.gymb.souveredu.ch/" log_level: "info" # Proxy providers (ForwardAuth) — gate downstream services behind @@ -63,25 +79,15 @@ authentik_proxy_apps: authorization_slug: default-provider-authorization-implicit-consent invalidation_slug: default-provider-invalidation-flow -# Outpost bindings. drawio runs on this (application) host, so its -# ForwardAuth can use the embedded outpost (same host:9000 as the -# authentik server). garage-webui runs on the storage host; a cross-host -# ForwardAuth to the embedded outpost arrives with a mangled -# X-Forwarded-Host (an extra reverseproxy hop appends :443 / a comma list) -# and the outpost then fails to match the provider's external_host (404). -# So garage-webui is bound to a dedicated proxy outpost deployed on -# storage (role digitalboard.core.authentik_outpost_proxy), whose -# ForwardAuth endpoint the local storage Traefik reaches over the docker -# network — no extra hop, clean X-Forwarded-Host. +# Bind both proxy providers to authentik's built-in embedded outpost so +# we don't have to deploy a separate proxy outpost container. The +# embedded outpost listens on the same host:9000 as the authentik server +# and exposes /outpost.goauthentik.io/auth/traefik for ForwardAuth. authentik_proxy_outposts: - name: "authentik Embedded Outpost" type: proxy providers: - drawio - - name: "storage-proxy-outpost" - type: proxy - token: "{{ _authentik.proxy_outpost_token }}" - providers: - garage-webui # OIDC clients diff --git a/inventories/demo-gymburgdorf/host_vars/application/authentik_outpost_ldap.yml b/inventories/demo-gymburgdorf/host_vars/application/authentik_outpost_ldap.yml index b280ce4..562f979 100644 --- a/inventories/demo-gymburgdorf/host_vars/application/authentik_outpost_ldap.yml +++ b/inventories/demo-gymburgdorf/host_vars/application/authentik_outpost_ldap.yml @@ -3,11 +3,5 @@ # authenticate against the authentik server it talks to. _authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" -# 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_host: "https://auth.int.gymb.souveredu.ch" authentik_outpost_ldap_token: "{{ _authentik.ldap_outpost_token }}" -# auth.gymb.* presents a valid Let's Encrypt cert via the reverseproxy, so -# verify the chain instead of relying on the role's insecure default. -authentik_outpost_ldap_insecure: "false" diff --git a/inventories/demo-gymburgdorf/host_vars/application/bookstack.yml b/inventories/demo-gymburgdorf/host_vars/application/bookstack.yml index 0185c11..1d0beac 100644 --- a/inventories/demo-gymburgdorf/host_vars/application/bookstack.yml +++ b/inventories/demo-gymburgdorf/host_vars/application/bookstack.yml @@ -20,11 +20,11 @@ bookstack_admin_email: "admin@gymb.souveredu.ch" bookstack_admin_name: "BookStack Admin" # OIDC against Authentik. BookStack compares OIDC_ISSUER strictly against -# the `iss` claim in the discovery response, and Authentik emits the -# public auth.gymb.* hostname there, so the issuer must use the public -# FQDN. That FQDN resolves (internal DNS view) to the DMZ reverseproxy, -# which the backend subnet can reach, so the server-to-server calls stay -# in the LAN. +# the `iss` claim in the discovery response. Authentik emits the public +# auth.gymb.* hostname there (host-rewrite middleware ensures the claim +# matches what browsers see during login), so the issuer URL must use the +# public FQDN. Pinning auth.gymb.* in /etc/hosts below keeps the actual +# server-to-server traffic on the LAN. bookstack_oidc_enabled: true bookstack_oidc_name: "Authentik" bookstack_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/bookstack/" @@ -34,3 +34,10 @@ bookstack_oidc_additional_scopes: "openid profile email" bookstack_oidc_user_to_groups: true bookstack_oidc_groups_claim: "groups" 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" diff --git a/inventories/demo-gymburgdorf/host_vars/application/homarr.yml b/inventories/demo-gymburgdorf/host_vars/application/homarr.yml index 21e9e36..96b2b7d 100644 --- a/inventories/demo-gymburgdorf/host_vars/application/homarr.yml +++ b/inventories/demo-gymburgdorf/host_vars/application/homarr.yml @@ -18,11 +18,12 @@ homarr_admin_password: "{{ _homarr.admin_password }}" # AUTH_OIDC_AUTO_LOGIN bypasses the normal /login page. # # Issuer must match the `iss` claim authentik emits, which is always the -# public FQDN. Homarr (oauth4webapi) does a strict 1:1 comparison between -# the discovery response's issuer and this URL — an internal FQDN here -# fails with OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED. The public FQDN -# resolves (internal DNS view) to the DMZ reverseproxy, which the backend -# subnet can reach, so discovery/token/userinfo stay in the LAN. +# public FQDN (authentik's host-rewrite middleware aligns the claim with +# what browsers see). Homarr (oauth4webapi) does a strict 1:1 comparison +# between the discovery response's issuer and this URL — using the +# internal FQDN here fails with OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED. +# The extra_hosts pin below keeps the actual discovery/token/userinfo +# traffic on the LAN. homarr_auth_providers: "credentials,oidc" homarr_oidc_issuer: "https://auth.gymb.souveredu.ch/application/o/homarr/" homarr_oidc_client_id: "homarr" @@ -32,6 +33,14 @@ homarr_oidc_scopes: "openid profile email groups" homarr_oidc_groups_attribute: "groups" 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 # values describe horizontal grid cells (1-10 desktop / 6 tablet / 2 # mobile, packed left-to-right). diff --git a/inventories/demo-gymburgdorf/host_vars/application/nextcloud.yml b/inventories/demo-gymburgdorf/host_vars/application/nextcloud.yml index c82c487..b30a0f9 100644 --- a/inventories/demo-gymburgdorf/host_vars/application/nextcloud.yml +++ b/inventories/demo-gymburgdorf/host_vars/application/nextcloud.yml @@ -62,16 +62,25 @@ nextcloud_s3_port: 443 nextcloud_s3_ssl: true nextcloud_s3_usepath_style: true -# OIDC discovery/token/userinfo and the S3 backend both target FQDNs -# that resolve to RFC1918 addresses (auth.gymb.* via the reverseproxy, -# s3.int.* directly), which Nextcloud's DnsPinMiddleware would otherwise -# block as "local server access". +# OIDC server-to-server discovery / token / userinfo goes to +# auth.int.gymb.souveredu.ch (LAN, RFC1918). Nextcloud's DnsPinMiddleware +# would otherwise block that as "local server access". nextcloud_allow_local_remote_servers: true # Share the LDAP docker network with the authentik LDAP outpost nextcloud_extra_networks: - 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) nextcloud_ldap_enabled: true nextcloud_ldap_config: @@ -109,12 +118,13 @@ nextcloud_oidc_providers: display_name: "Login with Authentik" client_id: nextcloud client_secret: "{{ _authentik.nextcloud_oidc_secret }}" - # Discovery via the public FQDN. It resolves (internal DNS view) to - # the DMZ reverseproxy, which the backend subnet can now reach, so - # the server-to-server call stays in the LAN while the iss claim and - # all listed endpoints match the public hostname the browser sees - # during login — no host-rewrite or /etc/hosts pin needed. - discovery_url: "https://auth.gymb.souveredu.ch/application/o/nextcloud/.well-known/openid-configuration" + # Discovery via the internal FQDN (LAN-only) — the DMZ has no + # hairpin-NAT for the public IP, so server-to-server calls to + # auth.gymb.* would time out. The traefik router for auth.int.* + # rewrites the Host header to auth.gymb.souveredu.ch before the + # request reaches authentik, so the iss claim authentik emits still + # matches the public hostname the browser sees during login. + discovery_url: "https://auth.int.gymb.souveredu.ch/application/o/nextcloud/.well-known/openid-configuration" scope: "openid email profile" unique_uid: true mapping: diff --git a/inventories/demo-gymburgdorf/host_vars/application/opnform.yml b/inventories/demo-gymburgdorf/host_vars/application/opnform.yml index 028e98f..6bf41dc 100644 --- a/inventories/demo-gymburgdorf/host_vars/application/opnform.yml +++ b/inventories/demo-gymburgdorf/host_vars/application/opnform.yml @@ -23,13 +23,18 @@ opnform_admin_name: "OpnForm Admin" opnform_admin_email: "admin@gymb.souveredu.ch" opnform_admin_password: "{{ _opnform.admin_password }}" -# OIDC against Authentik. The public auth.gymb.* FQDN resolves (internal -# DNS view) to the DMZ reverseproxy, which the backend subnet can reach, -# so OpnForm's discovery/token/userinfo calls stay in the LAN while the -# iss claim matches the public hostname browsers see during login. +# OIDC against Authentik. Discovery via the internal FQDN keeps +# server-to-server traffic in the LAN; Authentik's host-rewrite router +# rewrites the Host header to auth.gymb.* before the request reaches +# authentik so the iss claim still matches the public hostname browsers +# see during login. opnform_oidc_enabled: true -# Issuer must use the public FQDN: OpnForm validates the token's `iss` -# claim against this value, and Authentik emits the public hostname there. +# Issuer must use the public auth.gymb.* FQDN: OpnForm does OIDC +# discovery and then validates the token's `iss` claim against this +# 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_client_id: "opnform" opnform_oidc_client_secret: "{{ _opnform.oidc_client_secret }}" @@ -48,3 +53,10 @@ opnform_oidc_force_login: true # Break-glass: /login?bypass=1 reaches the email form when the IdP is # down. 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" diff --git a/inventories/demo-gymburgdorf/host_vars/storage/authentik_outpost_proxy.yml b/inventories/demo-gymburgdorf/host_vars/storage/authentik_outpost_proxy.yml deleted file mode 100644 index df7b628..0000000 --- a/inventories/demo-gymburgdorf/host_vars/storage/authentik_outpost_proxy.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -# Proxy (ForwardAuth) outpost co-located with garage on storage. The -# garage-webui Traefik router's ForwardAuth middleware points at this -# container over the local `proxy` docker network, so the auth subrequest -# never leaves storage — the embedded outpost on the application host -# can't be used here because the cross-host hop mangles X-Forwarded-Host. -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# Outpost pulls its config from authentik over the public FQDN (resolves -# via the internal DNS view to the DMZ reverseproxy the storage subnet can -# reach). Token must match the storage-proxy-outpost registered in -# authentik (see application/authentik.yml authentik_proxy_outposts). -# Verify the authentik TLS chain: auth.gymb.* presents a valid Let's -# Encrypt cert via the reverseproxy and the storage subnet reaches it, so -# there's no reason to disable verification (verified: ssl_verify_result=0). -authentik_outpost_proxy_host: "https://auth.gymb.souveredu.ch" -authentik_outpost_proxy_token: "{{ _authentik.proxy_outpost_token }}" -authentik_outpost_proxy_insecure: "false" -authentik_outpost_proxy_network: "proxy" diff --git a/inventories/demo-gymburgdorf/host_vars/storage/garage.yml b/inventories/demo-gymburgdorf/host_vars/storage/garage.yml index aa0b8f1..ae23880 100644 --- a/inventories/demo-gymburgdorf/host_vars/storage/garage.yml +++ b/inventories/demo-gymburgdorf/host_vars/storage/garage.yml @@ -9,27 +9,22 @@ _garage: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/ga garage_s3_domains: - "s3.gymb.souveredu.ch" - "s3.int.gymb.souveredu.ch" -# First entry is the canonical public console FQDN. The *.int.* entry -# gives the DMZ a backend_host whose cert SAN matches (storage/traefik.yml), -# so the DMZ->storage hop verifies TLS instead of failing on a raw IP. -garage_webui_domains: - - "console.s3.gymb.souveredu.ch" - - "console.s3.int.gymb.souveredu.ch" +garage_webui_domain: "console.s3.gymb.souveredu.ch" garage_use_ssl: true garage_webui_enabled: true # Gate the WebUI behind authentik (admins-only, via policy-binding on the # authentik proxy app). Replaces the htpasswd Basic-Auth — AUTH_USER_PASS -# is dropped from the compose env when this is true. -# -# ForwardAuth talks to the storage-local proxy outpost over the docker -# network (role digitalboard.core.authentik_outpost_proxy). The embedded -# outpost on the application host can't be used cross-host: the extra -# reverseproxy hop mangles X-Forwarded-Host (appends :443 / a comma list) -# and the outpost then 404s instead of matching console.s3.* against the -# provider's external_host. A co-located outpost keeps the subrequest on -# the local network with a clean X-Forwarded-Host. +# is dropped from the compose env when this is true. The forwardauth URL +# uses a dedicated outpost-only FQDN that's deliberately outside +# authentik_domains so Authentik routes it to the embedded outpost (not +# ASGI). The public auth.gymb.* FQDN would 404 here — Authentik routes +# any Host matching an auth-domain to ASGI which doesn't serve the outpost +# path. The outpost itself then matches the protected app via +# X-Forwarded-Host (Traefik forwards it via trustForwardHeader=true). +# The FQDN is pinned to the application host via traefik_extra_hosts so +# the request stays in the LAN. garage_webui_authentik_forward_auth: true -garage_webui_authentik_forward_auth_url: "http://authentik-outpost-proxy-proxy-1:9000/outpost.goauthentik.io/auth/traefik" +garage_webui_authentik_forward_auth_url: "https://outpost.auth.int.gymb.souveredu.ch/outpost.goauthentik.io/auth/traefik" # Kept for completeness — only used when authentik ForwardAuth is off. garage_webui_username: "admin" garage_webui_password: "{{ _garage.webui_password | default('disabled') }}" diff --git a/inventories/demo-gymburgdorf/host_vars/storage/traefik.yml b/inventories/demo-gymburgdorf/host_vars/storage/traefik.yml index 6c60ffc..36530ef 100644 --- a/inventories/demo-gymburgdorf/host_vars/storage/traefik.yml +++ b/inventories/demo-gymburgdorf/host_vars/storage/traefik.yml @@ -1,10 +1,21 @@ --- +# Local traefik needs to reach authentik for the ForwardAuth subrequest +# the garage-webui router fires. The public IP is unreachable from this +# subnet (no DMZ hairpin), so pin both auth FQDNs directly at the +# application host where authentik runs. Without this the forwardauth +# middleware would time out and every garage-console request would 502. +# - auth.gymb.* covers any future server-to-server traffic on the public +# 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: + - "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 # public traffic to. See application/traefik.yml for the mechanism. -# The garage-webui ForwardAuth no longer needs an auth FQDN pinned here — -# it talks to the storage-local proxy outpost over the docker network -# (see garage.yml), and the outpost reaches authentik via the public FQDN -# over the reverseproxy (firewall now permits backend -> DMZ). traefik_dmz_exposed_services: - name: garage-s3 domain: s3.gymb.souveredu.ch @@ -13,9 +24,8 @@ traefik_dmz_exposed_services: protocol: https - name: garage-webui domain: console.s3.gymb.souveredu.ch - # Internal name so the DMZ verifies the storage backend cert against a - # matching SAN (acme cert_mode, no insecureSkipVerify). garage now - # serves this SAN via garage_webui_domains (see garage.yml). - backend_host: console.s3.int.gymb.souveredu.ch + # No internal FQDN/cert SAN for console.s3 yet — would need an + # extra_domain on garage-webui. Until then this route will 500 + # against the storage backend (cert mismatch on raw IP). port: 443 protocol: https diff --git a/inventories/demo-gymburgdorf/hosts.yml b/inventories/demo-gymburgdorf/hosts.yml index 4e4f5c2..66261bc 100644 --- a/inventories/demo-gymburgdorf/hosts.yml +++ b/inventories/demo-gymburgdorf/hosts.yml @@ -48,14 +48,6 @@ all: hosts: application: - # Proxy (ForwardAuth) outpost co-located with garage on storage, so - # the garage-webui ForwardAuth subrequest stays on the local docker - # network instead of crossing an extra reverseproxy hop (which mangles - # X-Forwarded-Host and breaks the embedded outpost's app matching). - authentik_outpost_proxy_servers: - hosts: - storage: - send_servers: hosts: application: diff --git a/inventories/demo-mbazürich/group_vars/all/ansible.yml b/inventories/demo-mbazürich/group_vars/all/ansible.yml deleted file mode 100644 index e425179..0000000 --- a/inventories/demo-mbazürich/group_vars/all/ansible.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -ansible_python_interpreter: /usr/bin/python3 diff --git a/inventories/demo-mbazürich/group_vars/all/docker.yml b/inventories/demo-mbazürich/group_vars/all/docker.yml deleted file mode 100644 index 73bc820..0000000 --- a/inventories/demo-mbazürich/group_vars/all/docker.yml +++ /dev/null @@ -1 +0,0 @@ -docker_registry_mirrors: ["https://registry-mirror.wksbern.ch"] diff --git a/inventories/demo-mbazürich/group_vars/all/vault.yml b/inventories/demo-mbazürich/group_vars/all/vault.yml deleted file mode 100644 index 4f59021..0000000 --- a/inventories/demo-mbazürich/group_vars/all/vault.yml +++ /dev/null @@ -1,2 +0,0 @@ -vault_addr: "https://bao.digitalboard.ch" -vault_mount: "demo-mbazürich" diff --git a/inventories/demo-mbazürich/group_vars/backend_servers/traefik.yml b/inventories/demo-mbazürich/group_vars/backend_servers/traefik.yml deleted file mode 100644 index 9ae17fa..0000000 --- a/inventories/demo-mbazürich/group_vars/backend_servers/traefik.yml +++ /dev/null @@ -1 +0,0 @@ -traefik_mode: backend diff --git a/inventories/demo-mbazürich/group_vars/traefik_servers/traefik.yml b/inventories/demo-mbazürich/group_vars/traefik_servers/traefik.yml deleted file mode 100644 index e22d467..0000000 --- a/inventories/demo-mbazürich/group_vars/traefik_servers/traefik.yml +++ /dev/null @@ -1,19 +0,0 @@ -_acme_tsig: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/acme-tsig', url=vault_addr ) }}" - -traefik_use_ssl: true -traefik_cert_mode: "acme" -traefik_ssl_email: "hostmaster@digitalboard.ch" -traefik_log_level: DEBUG -traefik_network: proxy - -traefik_acme_dns_zone: "demo-mbaz._acme.digitalboard.ch" -traefik_acme_dns_nameserver: "{{ _acme_tsig.server }}" -traefik_acme_tsig_algorithm: "hmac-sha256" -traefik_acme_tsig_key: "{{ _acme_tsig.tsig_key }}" -traefik_acme_tsig_secret: "{{ _acme_tsig.tsig_secret }}" - -# UDP/53 egress from the traefik container reaches ns1.digitalboard.ch -# unreliably (i/o timeouts on lego's recursive SOA pre-check), while -# TCP/53 to the same nameserver is open. Force lego to do its DNS -# lookups over TCP so the DNS-01 challenge can proceed. -traefik_acme_tcp_only: true diff --git a/inventories/demo-mbazürich/host_vars/application/authentik.yml b/inventories/demo-mbazürich/host_vars/application/authentik.yml deleted file mode 100644 index 633343b..0000000 --- a/inventories/demo-mbazürich/host_vars/application/authentik.yml +++ /dev/null @@ -1,167 +0,0 @@ ---- -# Bao secret expected at /data/authentik with keys: -# secret_key, postgres_password, admin_password, -# ldap_outpost_token, proxy_outpost_token, -# nextcloud_oidc_secret, opnform_oidc_secret, -# homarr_oidc_secret, bookstack_oidc_secret -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# First entry is the canonical public FQDN (browsers + OIDC iss-claim). -# The *.int.* entry makes the router match the internal name too, so -# Traefik requests a cert covering it — the DMZ reverseproxy uses -# auth.int.mbaz as backend_host and verifies the chain (acme mode, -# no insecureSkipVerify), which fails against the default cert otherwise. -authentik_domains: - - "auth.mbaz.souveredu.ch" - - "auth.int.mbaz.souveredu.ch" - -authentik_secret_key: "{{ _authentik.secret_key }}" -authentik_postgres_password: "{{ _authentik.postgres_password }}" - -# LDAP outpost (provider for nextcloud) -authentik_ldap_apps: - - slug: ldap - name: LDAP - base_dn: "dc=mbaz,dc=souveredu,dc=ch" - search_group: admins - -authentik_ldap_outpost: - name: "ldap-outpost" - token: "{{ _authentik.ldap_outpost_token }}" - config: - # Outpost pulls config from authentik over the public FQDN, which - # resolves (internal DNS view) to the DMZ reverseproxy the backend - # subnet can reach — keeps the round-trip in the LAN with a valid cert. - authentik_host: "https://auth.mbaz.souveredu.ch/" - log_level: "info" - -# Proxy providers (ForwardAuth) — gate downstream services behind -# authentik. The embedded outpost (which authentik ships out of the box) -# hosts these providers under /outpost.goauthentik.io/auth/traefik on the -# canonical FQDN; the service-side traefik attaches a ForwardAuth -# middleware that talks to that endpoint. -authentik_proxy_apps: - - slug: drawio - name: Drawio - external_host: "https://draw.mbaz.souveredu.ch" - internal_host: "http://drawio:8080" - # drawio is embedded in Nextcloud as an iframe (nextcloud_enable_drawio). - # Every authenticated Nextcloud user must therefore pass the ForwardAuth - # gate, otherwise the editor loads a 403 inside the iframe. Allow both - # standard groups; tightening this back to admins-only would break the - # Nextcloud integration for regular users. - allowed_groups: - - admins - - users - flows: - authentication_slug: default-authentication-flow - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - - slug: garage-webui - name: "Garage S3 Console" - external_host: "https://console.s3.mbaz.souveredu.ch" - internal_host: "http://garage-webui:3909" - allowed_groups: - - admins - flows: - authentication_slug: default-authentication-flow - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - -# Outpost bindings. drawio runs on this (application) host, so its -# ForwardAuth can use the embedded outpost (same host:9000 as the -# authentik server). garage-webui runs on the storage host; a cross-host -# ForwardAuth to the embedded outpost arrives with a mangled -# X-Forwarded-Host (an extra reverseproxy hop appends :443 / a comma list) -# and the outpost then fails to match the provider's external_host (404). -# So garage-webui is bound to a dedicated proxy outpost deployed on -# storage (role digitalboard.core.authentik_outpost_proxy), whose -# ForwardAuth endpoint the local storage Traefik reaches over the docker -# network — no extra hop, clean X-Forwarded-Host. -authentik_proxy_outposts: - - name: "authentik Embedded Outpost" - type: proxy - providers: - - drawio - - name: "storage-proxy-outpost" - type: proxy - token: "{{ _authentik.proxy_outpost_token }}" - providers: - - garage-webui - # config is required by the outpost blueprint serializer. The outpost - # runs on the storage host and reaches authentik over the public FQDN - # (resolves to the DMZ reverseproxy the storage subnet can reach). - config: - authentik_host: "https://auth.mbaz.souveredu.ch/" - log_level: "info" - -# OIDC clients -authentik_oidc_apps: - - slug: nextcloud - name: Nextcloud - client_id: nextcloud - client_secret: "{{ _authentik.nextcloud_oidc_secret }}" - redirect_uris: - - url: "https://cloud.mbaz.souveredu.ch/apps/user_oidc/code" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - scopes: [openid, email, profile, offline_access] - - slug: opnform - name: OpnForm - client_id: opnform - client_secret: "{{ _authentik.opnform_oidc_secret }}" - redirect_uris: - - url: "https://forms.mbaz.souveredu.ch/auth/authentik/callback" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - # No separate `groups` scope — authentik's default `profile` mapping - # already emits a `groups` claim built from request.user.groups, so - # OpnForm's admin-group mapping works without an extra scope. - scopes: [openid, email, profile] - - slug: homarr - name: Homarr - client_id: homarr - client_secret: "{{ _authentik.homarr_oidc_secret }}" - redirect_uris: - - url: "https://home.mbaz.souveredu.ch/api/auth/callback/oidc" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - scopes: [openid, email, profile] - - slug: bookstack - name: BookStack - client_id: bookstack - client_secret: "{{ _authentik.bookstack_oidc_secret }}" - redirect_uris: - - url: "https://wiki.mbaz.souveredu.ch/oidc/callback" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - scopes: [openid, email, profile] - -authentik_groups: - - name: admins - - name: users - - name: opnform-admins - - name: homarr-admins - - name: bookstack-admins - -authentik_local_users: - - username: akadmin - name: "Authentik Admin" - email: "admin@mbaz.souveredu.ch" - password: "{{ _authentik.admin_password }}" - is_active: true - groups: - - authentik Admins - - admins diff --git a/inventories/demo-mbazürich/host_vars/application/authentik_outpost_ldap.yml b/inventories/demo-mbazürich/host_vars/application/authentik_outpost_ldap.yml deleted file mode 100644 index 89aa0f6..0000000 --- a/inventories/demo-mbazürich/host_vars/application/authentik_outpost_ldap.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -# Same token as authentik_ldap_outpost.token above — outpost uses it to -# authenticate against the authentik server it talks to. -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# 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.mbaz.souveredu.ch" -authentik_outpost_ldap_token: "{{ _authentik.ldap_outpost_token }}" -# auth.mba.* presents a valid Let's Encrypt cert via the reverseproxy, so -# verify the chain instead of relying on the role's insecure default. -authentik_outpost_ldap_insecure: "false" diff --git a/inventories/demo-mbazürich/host_vars/application/bookstack.yml b/inventories/demo-mbazürich/host_vars/application/bookstack.yml deleted file mode 100644 index 8f02b50..0000000 --- a/inventories/demo-mbazürich/host_vars/application/bookstack.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -# Bao secret /data/bookstack expected to contain: -# db_root_password, db_password, admin_password, oidc_client_secret, -# app_key (optional — only set when restoring) -_bookstack: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/bookstack', url=vault_addr) }}" - -bookstack_domain: "wiki.mbaz.souveredu.ch" -bookstack_extra_domains: - - "wiki.int.mbaz.souveredu.ch" -bookstack_base_url: "https://wiki.mbaz.souveredu.ch" - -# Override the role-default certresolver ("le") with the value used -# across this demo (matches traefik_ssl_cert_resolver in group_vars). -bookstack_traefik_certresolver: "dns" - -bookstack_db_root_password: "{{ _bookstack.db_root_password }}" -bookstack_db_password: "{{ _bookstack.db_password }}" -bookstack_admin_password: "{{ _bookstack.admin_password }}" -bookstack_admin_email: "admin@mbaz.souveredu.ch" -bookstack_admin_name: "BookStack Admin" - -# OIDC against Authentik. BookStack compares OIDC_ISSUER strictly against -# the `iss` claim in the discovery response, and Authentik emits the -# public auth.mbaz.* hostname there, so the issuer must use the public -# FQDN. That FQDN resolves (internal DNS view) to the DMZ reverseproxy, -# which the backend subnet can reach, so the server-to-server calls stay -# in the LAN. -bookstack_oidc_enabled: true -bookstack_oidc_name: "Authentik" -bookstack_oidc_issuer: "https://auth.mbaz.souveredu.ch/application/o/bookstack/" -bookstack_oidc_client_id: "bookstack" -bookstack_oidc_client_secret: "{{ _bookstack.oidc_client_secret }}" -bookstack_oidc_additional_scopes: "openid profile email" -bookstack_oidc_user_to_groups: true -bookstack_oidc_groups_claim: "groups" -bookstack_oidc_auto_initiate: false diff --git a/inventories/demo-mbazürich/host_vars/application/collabora.yml b/inventories/demo-mbazürich/host_vars/application/collabora.yml deleted file mode 100644 index c7b202a..0000000 --- a/inventories/demo-mbazürich/host_vars/application/collabora.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -# First entry is the canonical public FQDN. Additional entries cover -# internal *.int.* names so nextcloud's WOPI discovery hits collabora -# in the LAN with a valid internal cert. -collabora_domains: - - "office.mbaz.souveredu.ch" - - "office.int.mbaz.souveredu.ch" - -# Hosts allowed to issue WOPI calls. Both names are listed so collabora -# accepts the callback from nextcloud regardless of which FQDN it uses. -collabora_allowed_domains: - - "cloud.mbaz.souveredu.ch" - - "cloud.int.mbaz.souveredu.ch" - -collabora_frame_ancestors: - - "cloud.mbaz.souveredu.ch" diff --git a/inventories/demo-mbazürich/host_vars/application/drawio.yml b/inventories/demo-mbazürich/host_vars/application/drawio.yml deleted file mode 100644 index 5782b7e..0000000 --- a/inventories/demo-mbazürich/host_vars/application/drawio.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -drawio_domain: "draw.mbaz.souveredu.ch" - -# Internal FQDN the DMZ reverseproxy uses as backend host so its TLS -# verify matches a cert SAN (the canonical IP-only route has no SAN -# and breaks with "cannot validate certificate ... no IP SANs"). Same -# split-horizon pattern as cloud.int.* / auth.int.* / office.int.*. -drawio_extra_domains: - - "draw.int.mbaz.souveredu.ch" - -# Gate drawio behind the authentik embedded outpost. The allow-list is -# managed on the authentik proxy application (admins + users) so the -# Nextcloud drawio iframe works for every authenticated user. -# ForwardAuth talks to the embedded outpost on the authentik server's -# in-network address. Going via the public FQDN routes through a second -# traefik hop that strips/rewrites X-Forwarded-Host, which breaks -# authentik's provider matching (it returns 404). Plain HTTP to the -# container is the path docs recommend for the embedded outpost. -drawio_authentik_forward_auth: true -drawio_authentik_forward_auth_url: "http://authentik-server-1:9000/outpost.goauthentik.io/auth/traefik" diff --git a/inventories/demo-mbazürich/host_vars/application/homarr.yml b/inventories/demo-mbazürich/host_vars/application/homarr.yml deleted file mode 100644 index 9bee263..0000000 --- a/inventories/demo-mbazürich/host_vars/application/homarr.yml +++ /dev/null @@ -1,78 +0,0 @@ ---- -# Bao secret /data/homarr expected to contain: -# secret_encryption_key (64 hex chars), admin_password, oidc_client_secret -_homarr: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/homarr', url=vault_addr) }}" - -homarr_domain: "home.mbaz.souveredu.ch" -homarr_extra_domains: - - "home.int.mbaz.souveredu.ch" -homarr_base_url: "https://home.mbaz.souveredu.ch" - -homarr_secret_encryption_key: "{{ _homarr.secret_encryption_key }}" -homarr_admin_username: "admin" -homarr_admin_email: "admin@mbaz.souveredu.ch" -homarr_admin_password: "{{ _homarr.admin_password }}" - -# OIDC against Authentik. credentials provider stays enabled as a -# break-glass account — reach it via /auth/login/credentials when -# AUTH_OIDC_AUTO_LOGIN bypasses the normal /login page. -# -# Issuer must match the `iss` claim authentik emits, which is always the -# public FQDN. Homarr (oauth4webapi) does a strict 1:1 comparison between -# the discovery response's issuer and this URL — an internal FQDN here -# fails with OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED. The public FQDN -# resolves (internal DNS view) to the DMZ reverseproxy, which the backend -# subnet can reach, so discovery/token/userinfo stay in the LAN. -homarr_auth_providers: "credentials,oidc" -homarr_oidc_issuer: "https://auth.mbaz.souveredu.ch/application/o/homarr/" -homarr_oidc_client_id: "homarr" -homarr_oidc_client_secret: "{{ _homarr.oidc_client_secret }}" -homarr_oidc_client_name: "Authentik" -homarr_oidc_scopes: "openid profile email groups" -homarr_oidc_groups_attribute: "groups" -homarr_oidc_auto_login: "true" - -# Default board with shortcuts to the other mbazürich services. Width -# values describe horizontal grid cells (1-10 desktop / 6 tablet / 2 -# mobile, packed left-to-right). -homarr_apps: - - id: nextcloud - name: Nextcloud - description: "Cloud Storage & Collaboration" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png - href: https://cloud.mbaz.souveredu.ch - width: 2 - - id: collabora - name: Collabora Office - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/collaboraonline.png - href: https://office.mbaz.souveredu.ch - width: 2 - - id: drawio - name: Draw.io - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/drawio.png - href: https://draw.mbaz.souveredu.ch - width: 2 - - id: send - name: Send - description: "Encrypted file-share" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/firefox-send.png - href: https://send.mbaz.souveredu.ch - width: 2 - - id: opnform - name: OpnForm - description: "Self-hosted forms" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/opnform.png - href: https://forms.mbaz.souveredu.ch - width: 2 - - id: bookstack - name: BookStack - description: "Wiki & documentation" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bookstack.png - href: https://wiki.mbaz.souveredu.ch - width: 2 - - id: authentik - name: Authentik - description: "Identity provider" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/authentik.png - href: https://auth.mbaz.souveredu.ch - width: 2 diff --git a/inventories/demo-mbazürich/host_vars/application/main.yml b/inventories/demo-mbazürich/host_vars/application/main.yml deleted file mode 100644 index 49248a6..0000000 --- a/inventories/demo-mbazürich/host_vars/application/main.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -# application host runs: authentik, authentik-ldap-outpost, -# nextcloud, collabora, drawio diff --git a/inventories/demo-mbazürich/host_vars/application/nextcloud.yml b/inventories/demo-mbazürich/host_vars/application/nextcloud.yml deleted file mode 100644 index 5ae6e7e..0000000 --- a/inventories/demo-mbazürich/host_vars/application/nextcloud.yml +++ /dev/null @@ -1,122 +0,0 @@ ---- -# Bao secret /data/nextcloud expected to contain: -# postgres_password, admin_password -_nextcloud: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/nextcloud', url=vault_addr) }}" -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# No nextcloud_image pin: the role default (nextcloud:fpm) already ships -# the fix for the PHP UserConfig::getValueBool TypeError (#59629), so the -# 33.0.3 pin gymb still carries is unnecessary here. - -# First entry is the canonical public FQDN (used for OVERWRITEHOST and -# OIDC redirects). Additional entries cover internal *.int.* names so -# collabora's WOPI callbacks hit nextcloud on a name with a valid -# internal cert instead of routing through the DMZ. -nextcloud_domains: - - "cloud.mbaz.souveredu.ch" - - "cloud.int.mbaz.souveredu.ch" -nextcloud_postgres_password: "{{ _nextcloud.postgres_password }}" -nextcloud_admin_user: admin -nextcloud_admin_password: "{{ _nextcloud.admin_password }}" - -nextcloud_enable_notify_push: true -# Use the internal FQDN for the notify_push setup check so curl from the -# nextcloud container hits the local traefik directly instead of -# hairpinning through the DMZ reverseproxy. -nextcloud_notify_push_domain: "cloud.int.mbaz.souveredu.ch" - -# Collabora integration -# wopi_url (server-to-server: nextcloud calls collabora for discovery / -# capabilities) goes to the internal FQDN so the call stays in the LAN. -# public_wopi_url is what the browser loads the office iframe from — that -# stays on the public name reachable through the DMZ. -nextcloud_enable_collabora: true -nextcloud_collabora_domain: "office.int.mbaz.souveredu.ch" -nextcloud_collabora_public_domain: "office.mbaz.souveredu.ch" - -# Draw.io integration -nextcloud_enable_drawio: true -nextcloud_drawio_url: "https://draw.mbaz.souveredu.ch" - -nextcloud_apps_to_install: - - groupfolders - - richdocuments - - spreed - - user_ldap - - user_oidc - - whiteboard - - drawio - - files_lock - - notify_push - -# S3 primary storage via Garage — server-to-server, so use the internal FQDN. -# Resolves through the internal DNS to the storage host and presents a valid -# cert from the local traefik on storage. -nextcloud_use_s3_storage: true -nextcloud_s3_key: "{{ lookup('digitalboard.core.garage_credentials', 'nextcloud', host='storage')['key_id'] }}" -nextcloud_s3_secret: "{{ lookup('digitalboard.core.garage_credentials', 'nextcloud', host='storage')['secret_key'] }}" -nextcloud_s3_bucket: "nextcloud" -nextcloud_s3_host: "s3.int.mbaz.souveredu.ch" -nextcloud_s3_port: 443 -nextcloud_s3_ssl: true -nextcloud_s3_usepath_style: true - -# OIDC discovery/token/userinfo and the S3 backend both target FQDNs -# that resolve to RFC1918 addresses (auth.mba.* via the reverseproxy, -# s3.int.* directly), which Nextcloud's DnsPinMiddleware would otherwise -# block as "local server access". -nextcloud_allow_local_remote_servers: true - -# Share the LDAP docker network with the authentik LDAP outpost -nextcloud_extra_networks: - - ldap - -# LDAP backend (Authentik LDAP outpost) -nextcloud_ldap_enabled: true -nextcloud_ldap_config: - ldapHost: "ldap://authentik-outpost-ldap-ldap-1" - ldapPort: "3389" - ldapAgentName: "cn=akadmin,ou=users,dc=mbaz,dc=souveredu,dc=ch" - ldapAgentPassword: "{{ _authentik.admin_password }}" - ldapBase: "dc=mbaz,dc=souveredu,dc=ch" - ldapBaseUsers: "ou=users,dc=mbaz,dc=souveredu,dc=ch" - ldapTLS: "0" - turnOffCertCheck: "1" - ldapUserFilter: "(&(objectClass=user)(cn=*))" - ldapUserFilterObjectclass: "user" - ldapLoginFilter: "(&(objectClass=user)(cn=%uid))" - ldapLoginFilterUsername: "1" - ldapUserDisplayName: "cn" - ldapEmailAttribute: "mail" - ldapExpertUsernameAttr: "cn" - ldapExpertUUIDUserAttr: "uid" - ldapExpertUUIDGroupAttr: "uid" - ldapBaseGroups: "ou=groups,dc=mbaz,dc=souveredu,dc=ch" - ldapGroupFilter: "(&(objectClass=group))" - ldapGroupFilterObjectclass: "group" - ldapGroupDisplayName: "cn" - ldapGroupMemberAssocAttr: "member" - ldapAdminGroup: "admins" - ldapCacheTTL: "600" - ldapPagingSize: "500" - ldapExperiencedAdmin: "1" - ldapConfigurationActive: "1" - -# OIDC providers for login (Authentik) -nextcloud_oidc_providers: - - identifier: authentik - display_name: "Login with Authentik" - client_id: nextcloud - client_secret: "{{ _authentik.nextcloud_oidc_secret }}" - # Discovery via the public FQDN. It resolves (internal DNS view) to - # the DMZ reverseproxy, which the backend subnet can now reach, so - # the server-to-server call stays in the LAN while the iss claim and - # all listed endpoints match the public hostname the browser sees - # during login — no host-rewrite or /etc/hosts pin needed. - discovery_url: "https://auth.mbaz.souveredu.ch/application/o/nextcloud/.well-known/openid-configuration" - scope: "openid email profile" - unique_uid: true - mapping: - uid: preferred_username - display_name: name - email: email diff --git a/inventories/demo-mbazürich/host_vars/application/opnform.yml b/inventories/demo-mbazürich/host_vars/application/opnform.yml deleted file mode 100644 index fa3edd7..0000000 --- a/inventories/demo-mbazürich/host_vars/application/opnform.yml +++ /dev/null @@ -1,50 +0,0 @@ ---- -# Bao secret /data/opnform expected to contain: -# app_key (must start with "base64:"), jwt_secret, front_api_secret, -# db_password, admin_password, oidc_client_secret -_opnform: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/opnform', url=vault_addr) }}" -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -opnform_domain: "forms.mbaz.souveredu.ch" -opnform_extra_domains: - - "forms.int.mbaz.souveredu.ch" -opnform_base_url: "https://forms.mbaz.souveredu.ch" - -opnform_app_key: "{{ _opnform.app_key }}" -opnform_jwt_secret: "{{ _opnform.jwt_secret }}" -opnform_front_api_secret: "{{ _opnform.front_api_secret }}" -opnform_db_password: "{{ _opnform.db_password }}" - -# Bootstrap admin via API on first run so the manual setup page is -# skipped. The admin credentials are also required to seed the OIDC -# IdentityConnection through OpnForm's API (only an authenticated admin -# can create connections). -opnform_admin_name: "OpnForm Admin" -opnform_admin_email: "admin@mbaz.souveredu.ch" -opnform_admin_password: "{{ _opnform.admin_password }}" - -# OIDC against Authentik. The public auth.mbaz.* FQDN resolves (internal -# DNS view) to the DMZ reverseproxy, which the backend subnet can reach, -# so OpnForm's discovery/token/userinfo calls stay in the LAN while the -# iss claim matches the public hostname browsers see during login. -opnform_oidc_enabled: true -# Issuer must use the public FQDN: OpnForm validates the token's `iss` -# claim against this value, and Authentik emits the public hostname there. -opnform_oidc_issuer: "https://auth.mbaz.souveredu.ch/application/o/opnform/" -opnform_oidc_client_id: "opnform" -opnform_oidc_client_secret: "{{ _opnform.oidc_client_secret }}" -opnform_oidc_client_name: "Authentik" -opnform_oidc_slug: "authentik" -opnform_oidc_domain: "mbaz.souveredu.ch" -opnform_oidc_admin_group: "opnform-admins" - -# Disable password login entirely — every user goes through Authentik. -# All real users have @mbaz.souveredu.ch addresses (matching -# opnform_oidc_domain above), so no password fallback is needed. -opnform_oidc_force_login: true - -# `/` and `/login` are intercepted and jump straight to Authentik. -# Public form deep-links (`/forms/`, `/admin/...`) keep working. -# Break-glass: /login?bypass=1 reaches the email form when the IdP is -# down. -opnform_oidc_sso_entrypoint: true diff --git a/inventories/demo-mbazürich/host_vars/application/send.yml b/inventories/demo-mbazürich/host_vars/application/send.yml deleted file mode 100644 index 58cee46..0000000 --- a/inventories/demo-mbazürich/host_vars/application/send.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# Send: anonymized self-hosted file-share (no login). First entry is the -# canonical public FQDN (used as BASE_URL); the *.int.* entry covers the -# server-to-server hop from the DMZ reverseproxy with a cert SAN that -# matches the backend hostname (same split-horizon pattern as cloud/draw). -send_domains: - - "send.mbaz.souveredu.ch" - - "send.int.mbaz.souveredu.ch" diff --git a/inventories/demo-mbazürich/host_vars/application/traefik.yml b/inventories/demo-mbazürich/host_vars/application/traefik.yml deleted file mode 100644 index 395443a..0000000 --- a/inventories/demo-mbazürich/host_vars/application/traefik.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -# Services hosted on `application` that the DMZ reverseproxy should -# forward public traffic to. The DMZ traefik picks this up via -# hostvars[backend].traefik_dmz_exposed_services and renders a router + -# service for each entry into /config/services.yml. -traefik_dmz_exposed_services: - - name: authentik - domain: auth.mbaz.souveredu.ch - backend_host: auth.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: nextcloud - domain: cloud.mbaz.souveredu.ch - backend_host: cloud.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: collabora - domain: office.mbaz.souveredu.ch - backend_host: office.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: drawio - domain: draw.mbaz.souveredu.ch - backend_host: draw.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: send - domain: send.mbaz.souveredu.ch - backend_host: send.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: opnform - domain: forms.mbaz.souveredu.ch - backend_host: forms.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: homarr - domain: home.mbaz.souveredu.ch - backend_host: home.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: bookstack - domain: wiki.mbaz.souveredu.ch - backend_host: wiki.int.mbaz.souveredu.ch - port: 443 - protocol: https diff --git a/inventories/demo-mbazürich/host_vars/reverseproxy/traefik.yml b/inventories/demo-mbazürich/host_vars/reverseproxy/traefik.yml deleted file mode 100644 index 48f6d08..0000000 --- a/inventories/demo-mbazürich/host_vars/reverseproxy/traefik.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -traefik_mode: dmz - -# The DMZ traefik discovers which services to expose by reading -# traefik_dmz_exposed_services from each backend host's host_vars -# (application/traefik.yml, storage/traefik.yml). See the role's -# tasks/main.yml — set_fact "Build service registry from backend -# servers (DMZ mode)". - -# From the DMZ network the public ns1 IP (193.43.183.169) is not -# reachable on port 53, but the internal address (172.16.9.169) is. -# Override the group-level traefik_acme_dns_nameserver from bao so -# lego's RFC2136 updates land at the internal interface. The TSIG -# key/secret are the same; only the transport target changes. -traefik_acme_dns_nameserver: "172.16.9.169" - -# Lego's propagation check normally polls the NS hostnames listed in -# the zone's SOA (ns1.digitalboard.ch.) — which resolves to the -# public IP that's unreachable from this DMZ host. Skip that check; -# lego still polls via the resolver above before asking LE to -# validate. -traefik_acme_disable_ans_checks: true diff --git a/inventories/demo-mbazürich/host_vars/storage/authentik_outpost_proxy.yml b/inventories/demo-mbazürich/host_vars/storage/authentik_outpost_proxy.yml deleted file mode 100644 index d0cb1df..0000000 --- a/inventories/demo-mbazürich/host_vars/storage/authentik_outpost_proxy.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -# Proxy (ForwardAuth) outpost co-located with garage on storage. The -# garage-webui Traefik router's ForwardAuth middleware points at this -# container over the local `proxy` docker network, so the auth subrequest -# never leaves storage — the embedded outpost on the application host -# can't be used here because the cross-host hop mangles X-Forwarded-Host. -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# Outpost pulls its config from authentik over the public FQDN (resolves -# via the internal DNS view to the DMZ reverseproxy the storage subnet can -# reach). Token must match the storage-proxy-outpost registered in -# authentik (see application/authentik.yml authentik_proxy_outposts). -# Verify the authentik TLS chain: auth.mba.* presents a valid Let's -# Encrypt cert via the reverseproxy and the storage subnet reaches it, so -# there's no reason to disable verification (verified: ssl_verify_result=0). -authentik_outpost_proxy_host: "https://auth.mbaz.souveredu.ch" -authentik_outpost_proxy_token: "{{ _authentik.proxy_outpost_token }}" -authentik_outpost_proxy_insecure: "false" -authentik_outpost_proxy_network: "proxy" diff --git a/inventories/demo-mbazürich/host_vars/storage/garage.yml b/inventories/demo-mbazürich/host_vars/storage/garage.yml deleted file mode 100644 index cd8949e..0000000 --- a/inventories/demo-mbazürich/host_vars/storage/garage.yml +++ /dev/null @@ -1,51 +0,0 @@ ---- -# Bao secret /data/garage expected to contain: -# rpc_secret, admin_token, metrics_token, webui_password -_garage: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/garage', url=vault_addr) }}" - -# First entry is the canonical public S3 FQDN. Additional entries -# cover internal *.int.* names so server-to-server S3 traffic (e.g. -# nextcloud → garage) stays in the LAN. -garage_s3_domains: - - "s3.mbaz.souveredu.ch" - - "s3.int.mbaz.souveredu.ch" -# First entry is the canonical public console FQDN. The *.int.* entry -# gives the DMZ a backend_host whose cert SAN matches (storage/traefik.yml), -# so the DMZ->storage hop verifies TLS instead of failing on a raw IP. -garage_webui_domains: - - "console.s3.mbaz.souveredu.ch" - - "console.s3.int.mbaz.souveredu.ch" -garage_use_ssl: true -garage_webui_enabled: true -# Gate the WebUI behind authentik (admins-only, via policy-binding on the -# authentik proxy app). Replaces the htpasswd Basic-Auth — AUTH_USER_PASS -# is dropped from the compose env when this is true. -# -# ForwardAuth talks to the storage-local proxy outpost over the docker -# network (role digitalboard.core.authentik_outpost_proxy). The embedded -# outpost on the application host can't be used cross-host: the extra -# reverseproxy hop mangles X-Forwarded-Host (appends :443 / a comma list) -# and the outpost then 404s instead of matching console.s3.* against the -# provider's external_host. A co-located outpost keeps the subrequest on -# the local network with a clean X-Forwarded-Host. -garage_webui_authentik_forward_auth: true -garage_webui_authentik_forward_auth_url: "http://authentik-outpost-proxy-proxy-1:9000/outpost.goauthentik.io/auth/traefik" -# Kept for completeness — only used when authentik ForwardAuth is off. -garage_webui_username: "admin" -garage_webui_password: "{{ _garage.webui_password | default('disabled') }}" - -garage_rpc_secret: "{{ _garage.rpc_secret }}" -garage_admin_token: "{{ _garage.admin_token }}" -garage_metrics_token: "{{ _garage.metrics_token }}" - -# Initial cluster bootstrap (single-node) -garage_bootstrap_enabled: true -garage_bootstrap_zone: "zurich1" -garage_bootstrap_capacity: "100G" - -# Buckets and keys consumed by nextcloud -garage_s3_keys: - - name: nextcloud - buckets: - - name: nextcloud - permissions: ["read", "write"] diff --git a/inventories/demo-mbazürich/host_vars/storage/main.yml b/inventories/demo-mbazürich/host_vars/storage/main.yml deleted file mode 100644 index 75e487d..0000000 --- a/inventories/demo-mbazürich/host_vars/storage/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -# storage host runs: garage (S3 + WebUI) diff --git a/inventories/demo-mbazürich/host_vars/storage/traefik.yml b/inventories/demo-mbazürich/host_vars/storage/traefik.yml deleted file mode 100644 index 4d00cef..0000000 --- a/inventories/demo-mbazürich/host_vars/storage/traefik.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -# Services hosted on `storage` that the DMZ reverseproxy should forward -# public traffic to. See application/traefik.yml for the mechanism. -# The garage-webui ForwardAuth no longer needs an auth FQDN pinned here — -# it talks to the storage-local proxy outpost over the docker network -# (see garage.yml), and the outpost reaches authentik via the public FQDN -# over the reverseproxy (firewall now permits backend -> DMZ). -traefik_dmz_exposed_services: - - name: garage-s3 - domain: s3.mbaz.souveredu.ch - backend_host: s3.int.mbaz.souveredu.ch - port: 443 - protocol: https - - name: garage-webui - domain: console.s3.mbaz.souveredu.ch - # Internal name so the DMZ verifies the storage backend cert against a - # matching SAN (acme cert_mode, no insecureSkipVerify). garage now - # serves this SAN via garage_webui_domains (see garage.yml). - backend_host: console.s3.int.mbaz.souveredu.ch - port: 443 - protocol: https diff --git a/inventories/demo-mbazürich/hosts.yml b/inventories/demo-mbazürich/hosts.yml index 4e7f7d4..6185519 100644 --- a/inventories/demo-mbazürich/hosts.yml +++ b/inventories/demo-mbazürich/hosts.yml @@ -22,7 +22,6 @@ all: backend_servers: hosts: application: - storage: garage_servers: hosts: @@ -32,6 +31,10 @@ all: hosts: application: + opencloud_servers: + hosts: + application: + collabora_servers: hosts: application: @@ -42,33 +45,4 @@ all: authentik_servers: hosts: - application: - - authentik_outpost_ldap_servers: - hosts: - application: - - # Proxy (ForwardAuth) outpost co-located with garage on storage, so - # the garage-webui ForwardAuth subrequest stays on the local docker - # network instead of crossing an extra reverseproxy hop (which mangles - # X-Forwarded-Host and breaks the embedded outpost's app matching). - authentik_outpost_proxy_servers: - hosts: - storage: - - - send_servers: - hosts: - application: - - opnform_servers: - hosts: - application: - - homarr_servers: - hosts: - application: - - bookstack_servers: - hosts: - application: + application: \ No newline at end of file diff --git a/inventories/demo-phbern/group_vars/all/ansible.yml b/inventories/demo-phbern/group_vars/all/ansible.yml deleted file mode 100644 index e425179..0000000 --- a/inventories/demo-phbern/group_vars/all/ansible.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -ansible_python_interpreter: /usr/bin/python3 diff --git a/inventories/demo-phbern/group_vars/all/docker.yml b/inventories/demo-phbern/group_vars/all/docker.yml deleted file mode 100644 index 73bc820..0000000 --- a/inventories/demo-phbern/group_vars/all/docker.yml +++ /dev/null @@ -1 +0,0 @@ -docker_registry_mirrors: ["https://registry-mirror.wksbern.ch"] diff --git a/inventories/demo-phbern/group_vars/all/vault.yml b/inventories/demo-phbern/group_vars/all/vault.yml deleted file mode 100644 index bd56791..0000000 --- a/inventories/demo-phbern/group_vars/all/vault.yml +++ /dev/null @@ -1,2 +0,0 @@ -vault_addr: "https://bao.digitalboard.ch" -vault_mount: "demo-phbern" diff --git a/inventories/demo-phbern/group_vars/backend_servers/traefik.yml b/inventories/demo-phbern/group_vars/backend_servers/traefik.yml deleted file mode 100644 index 9ae17fa..0000000 --- a/inventories/demo-phbern/group_vars/backend_servers/traefik.yml +++ /dev/null @@ -1 +0,0 @@ -traefik_mode: backend diff --git a/inventories/demo-phbern/group_vars/traefik_servers/traefik.yml b/inventories/demo-phbern/group_vars/traefik_servers/traefik.yml deleted file mode 100644 index 082170b..0000000 --- a/inventories/demo-phbern/group_vars/traefik_servers/traefik.yml +++ /dev/null @@ -1,19 +0,0 @@ -_acme_tsig: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/acme-tsig', url=vault_addr ) }}" - -traefik_use_ssl: true -traefik_cert_mode: "acme" -traefik_ssl_email: "hostmaster@digitalboard.ch" -traefik_log_level: DEBUG -traefik_network: proxy - -traefik_acme_dns_zone: "demo-phbe._acme.digitalboard.ch" -traefik_acme_dns_nameserver: "{{ _acme_tsig.server }}" -traefik_acme_tsig_algorithm: "hmac-sha256" -traefik_acme_tsig_key: "{{ _acme_tsig.tsig_key }}" -traefik_acme_tsig_secret: "{{ _acme_tsig.tsig_secret }}" - -# UDP/53 egress from the traefik container reaches ns1.digitalboard.ch -# unreliably (i/o timeouts on lego's recursive SOA pre-check), while -# TCP/53 to the same nameserver is open. Force lego to do its DNS -# lookups over TCP so the DNS-01 challenge can proceed. -traefik_acme_tcp_only: true diff --git a/inventories/demo-phbern/host_vars/application/authentik.yml b/inventories/demo-phbern/host_vars/application/authentik.yml deleted file mode 100644 index 1c4759d..0000000 --- a/inventories/demo-phbern/host_vars/application/authentik.yml +++ /dev/null @@ -1,167 +0,0 @@ ---- -# Bao secret expected at /data/authentik with keys: -# secret_key, postgres_password, admin_password, -# ldap_outpost_token, proxy_outpost_token, -# nextcloud_oidc_secret, opnform_oidc_secret, -# homarr_oidc_secret, bookstack_oidc_secret -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# First entry is the canonical public FQDN (browsers + OIDC iss-claim). -# The *.int.* entry makes the router match the internal name too, so -# Traefik requests a cert covering it — the DMZ reverseproxy uses -# auth.int.phbe as backend_host and verifies the chain (acme mode, -# no insecureSkipVerify), which fails against the default cert otherwise. -authentik_domains: - - "auth.phbe.souveredu.ch" - - "auth.int.phbe.souveredu.ch" - -authentik_secret_key: "{{ _authentik.secret_key }}" -authentik_postgres_password: "{{ _authentik.postgres_password }}" - -# LDAP outpost (provider for nextcloud) -authentik_ldap_apps: - - slug: ldap - name: LDAP - base_dn: "dc=phbe,dc=souveredu,dc=ch" - search_group: admins - -authentik_ldap_outpost: - name: "ldap-outpost" - token: "{{ _authentik.ldap_outpost_token }}" - config: - # Outpost pulls config from authentik over the public FQDN, which - # resolves (internal DNS view) to the DMZ reverseproxy the backend - # subnet can reach — keeps the round-trip in the LAN with a valid cert. - authentik_host: "https://auth.phbe.souveredu.ch/" - log_level: "info" - -# Proxy providers (ForwardAuth) — gate downstream services behind -# authentik. The embedded outpost (which authentik ships out of the box) -# hosts these providers under /outpost.goauthentik.io/auth/traefik on the -# canonical FQDN; the service-side traefik attaches a ForwardAuth -# middleware that talks to that endpoint. -authentik_proxy_apps: - - slug: drawio - name: Drawio - external_host: "https://draw.phbe.souveredu.ch" - internal_host: "http://drawio:8080" - # drawio is embedded in Nextcloud as an iframe (nextcloud_enable_drawio). - # Every authenticated Nextcloud user must therefore pass the ForwardAuth - # gate, otherwise the editor loads a 403 inside the iframe. Allow both - # standard groups; tightening this back to admins-only would break the - # Nextcloud integration for regular users. - allowed_groups: - - admins - - users - flows: - authentication_slug: default-authentication-flow - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - - slug: garage-webui - name: "Garage S3 Console" - external_host: "https://console.s3.phbe.souveredu.ch" - internal_host: "http://garage-webui:3909" - allowed_groups: - - admins - flows: - authentication_slug: default-authentication-flow - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - -# Outpost bindings. drawio runs on this (application) host, so its -# ForwardAuth can use the embedded outpost (same host:9000 as the -# authentik server). garage-webui runs on the storage host; a cross-host -# ForwardAuth to the embedded outpost arrives with a mangled -# X-Forwarded-Host (an extra reverseproxy hop appends :443 / a comma list) -# and the outpost then fails to match the provider's external_host (404). -# So garage-webui is bound to a dedicated proxy outpost deployed on -# storage (role digitalboard.core.authentik_outpost_proxy), whose -# ForwardAuth endpoint the local storage Traefik reaches over the docker -# network — no extra hop, clean X-Forwarded-Host. -authentik_proxy_outposts: - - name: "authentik Embedded Outpost" - type: proxy - providers: - - drawio - - name: "storage-proxy-outpost" - type: proxy - token: "{{ _authentik.proxy_outpost_token }}" - providers: - - garage-webui - # config is required by the outpost blueprint serializer. The outpost - # runs on the storage host and reaches authentik over the public FQDN - # (resolves to the DMZ reverseproxy the storage subnet can reach). - config: - authentik_host: "https://auth.phbe.souveredu.ch/" - log_level: "info" - -# OIDC clients -authentik_oidc_apps: - - slug: nextcloud - name: Nextcloud - client_id: nextcloud - client_secret: "{{ _authentik.nextcloud_oidc_secret }}" - redirect_uris: - - url: "https://cloud.phbe.souveredu.ch/apps/user_oidc/code" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - scopes: [openid, email, profile, offline_access] - - slug: opnform - name: OpnForm - client_id: opnform - client_secret: "{{ _authentik.opnform_oidc_secret }}" - redirect_uris: - - url: "https://forms.phbe.souveredu.ch/auth/authentik/callback" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - # No separate `groups` scope — authentik's default `profile` mapping - # already emits a `groups` claim built from request.user.groups, so - # OpnForm's admin-group mapping works without an extra scope. - scopes: [openid, email, profile] - - slug: homarr - name: Homarr - client_id: homarr - client_secret: "{{ _authentik.homarr_oidc_secret }}" - redirect_uris: - - url: "https://home.phbe.souveredu.ch/api/auth/callback/oidc" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - scopes: [openid, email, profile] - - slug: bookstack - name: BookStack - client_id: bookstack - client_secret: "{{ _authentik.bookstack_oidc_secret }}" - redirect_uris: - - url: "https://wiki.phbe.souveredu.ch/oidc/callback" - matching_mode: strict - signing_key_name: "authentik Self-signed Certificate" - flows: - authorization_slug: default-provider-authorization-implicit-consent - invalidation_slug: default-provider-invalidation-flow - scopes: [openid, email, profile] - -authentik_groups: - - name: admins - - name: users - - name: opnform-admins - - name: homarr-admins - - name: bookstack-admins - -authentik_local_users: - - username: akadmin - name: "Authentik Admin" - email: "admin@phbe.souveredu.ch" - password: "{{ _authentik.admin_password }}" - is_active: true - groups: - - authentik Admins - - admins diff --git a/inventories/demo-phbern/host_vars/application/authentik_outpost_ldap.yml b/inventories/demo-phbern/host_vars/application/authentik_outpost_ldap.yml deleted file mode 100644 index f3b8931..0000000 --- a/inventories/demo-phbern/host_vars/application/authentik_outpost_ldap.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -# Same token as authentik_ldap_outpost.token above — outpost uses it to -# authenticate against the authentik server it talks to. -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# 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.phbe.souveredu.ch" -authentik_outpost_ldap_token: "{{ _authentik.ldap_outpost_token }}" -# auth.phbe.* presents a valid Let's Encrypt cert via the reverseproxy, so -# verify the chain instead of relying on the role's insecure default. -authentik_outpost_ldap_insecure: "false" diff --git a/inventories/demo-phbern/host_vars/application/bookstack.yml b/inventories/demo-phbern/host_vars/application/bookstack.yml deleted file mode 100644 index db820c0..0000000 --- a/inventories/demo-phbern/host_vars/application/bookstack.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -# Bao secret /data/bookstack expected to contain: -# db_root_password, db_password, admin_password, oidc_client_secret, -# app_key (optional — only set when restoring) -_bookstack: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/bookstack', url=vault_addr) }}" - -bookstack_domain: "wiki.phbe.souveredu.ch" -bookstack_extra_domains: - - "wiki.int.phbe.souveredu.ch" -bookstack_base_url: "https://wiki.phbe.souveredu.ch" - -# Override the role-default certresolver ("le") with the value used -# across this demo (matches traefik_ssl_cert_resolver in group_vars). -bookstack_traefik_certresolver: "dns" - -bookstack_db_root_password: "{{ _bookstack.db_root_password }}" -bookstack_db_password: "{{ _bookstack.db_password }}" -bookstack_admin_password: "{{ _bookstack.admin_password }}" -bookstack_admin_email: "admin@phbe.souveredu.ch" -bookstack_admin_name: "BookStack Admin" - -# OIDC against Authentik. BookStack compares OIDC_ISSUER strictly against -# the `iss` claim in the discovery response, and Authentik emits the -# public auth.phbe.* hostname there, so the issuer must use the public -# FQDN. That FQDN resolves (internal DNS view) to the DMZ reverseproxy, -# which the backend subnet can reach, so the server-to-server calls stay -# in the LAN. -bookstack_oidc_enabled: true -bookstack_oidc_name: "Authentik" -bookstack_oidc_issuer: "https://auth.phbe.souveredu.ch/application/o/bookstack/" -bookstack_oidc_client_id: "bookstack" -bookstack_oidc_client_secret: "{{ _bookstack.oidc_client_secret }}" -bookstack_oidc_additional_scopes: "openid profile email" -bookstack_oidc_user_to_groups: true -bookstack_oidc_groups_claim: "groups" -bookstack_oidc_auto_initiate: false diff --git a/inventories/demo-phbern/host_vars/application/collabora.yml b/inventories/demo-phbern/host_vars/application/collabora.yml deleted file mode 100644 index c42c49e..0000000 --- a/inventories/demo-phbern/host_vars/application/collabora.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -# First entry is the canonical public FQDN. Additional entries cover -# internal *.int.* names so nextcloud's WOPI discovery hits collabora -# in the LAN with a valid internal cert. -collabora_domains: - - "office.phbe.souveredu.ch" - - "office.int.phbe.souveredu.ch" - -# Hosts allowed to issue WOPI calls. Both names are listed so collabora -# accepts the callback from nextcloud regardless of which FQDN it uses. -collabora_allowed_domains: - - "cloud.phbe.souveredu.ch" - - "cloud.int.phbe.souveredu.ch" - -collabora_frame_ancestors: - - "cloud.phbe.souveredu.ch" diff --git a/inventories/demo-phbern/host_vars/application/drawio.yml b/inventories/demo-phbern/host_vars/application/drawio.yml deleted file mode 100644 index fe0f396..0000000 --- a/inventories/demo-phbern/host_vars/application/drawio.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -drawio_domain: "draw.phbe.souveredu.ch" - -# Internal FQDN the DMZ reverseproxy uses as backend host so its TLS -# verify matches a cert SAN (the canonical IP-only route has no SAN -# and breaks with "cannot validate certificate ... no IP SANs"). Same -# split-horizon pattern as cloud.int.* / auth.int.* / office.int.*. -drawio_extra_domains: - - "draw.int.phbe.souveredu.ch" - -# Gate drawio behind the authentik embedded outpost. The allow-list is -# managed on the authentik proxy application (admins + users) so the -# Nextcloud drawio iframe works for every authenticated user. -# ForwardAuth talks to the embedded outpost on the authentik server's -# in-network address. Going via the public FQDN routes through a second -# traefik hop that strips/rewrites X-Forwarded-Host, which breaks -# authentik's provider matching (it returns 404). Plain HTTP to the -# container is the path docs recommend for the embedded outpost. -drawio_authentik_forward_auth: true -drawio_authentik_forward_auth_url: "http://authentik-server-1:9000/outpost.goauthentik.io/auth/traefik" diff --git a/inventories/demo-phbern/host_vars/application/homarr.yml b/inventories/demo-phbern/host_vars/application/homarr.yml deleted file mode 100644 index 6357331..0000000 --- a/inventories/demo-phbern/host_vars/application/homarr.yml +++ /dev/null @@ -1,78 +0,0 @@ ---- -# Bao secret /data/homarr expected to contain: -# secret_encryption_key (64 hex chars), admin_password, oidc_client_secret -_homarr: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/homarr', url=vault_addr) }}" - -homarr_domain: "home.phbe.souveredu.ch" -homarr_extra_domains: - - "home.int.phbe.souveredu.ch" -homarr_base_url: "https://home.phbe.souveredu.ch" - -homarr_secret_encryption_key: "{{ _homarr.secret_encryption_key }}" -homarr_admin_username: "admin" -homarr_admin_email: "admin@phbe.souveredu.ch" -homarr_admin_password: "{{ _homarr.admin_password }}" - -# OIDC against Authentik. credentials provider stays enabled as a -# break-glass account — reach it via /auth/login/credentials when -# AUTH_OIDC_AUTO_LOGIN bypasses the normal /login page. -# -# Issuer must match the `iss` claim authentik emits, which is always the -# public FQDN. Homarr (oauth4webapi) does a strict 1:1 comparison between -# the discovery response's issuer and this URL — an internal FQDN here -# fails with OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED. The public FQDN -# resolves (internal DNS view) to the DMZ reverseproxy, which the backend -# subnet can reach, so discovery/token/userinfo stay in the LAN. -homarr_auth_providers: "credentials,oidc" -homarr_oidc_issuer: "https://auth.phbe.souveredu.ch/application/o/homarr/" -homarr_oidc_client_id: "homarr" -homarr_oidc_client_secret: "{{ _homarr.oidc_client_secret }}" -homarr_oidc_client_name: "Authentik" -homarr_oidc_scopes: "openid profile email groups" -homarr_oidc_groups_attribute: "groups" -homarr_oidc_auto_login: "true" - -# Default board with shortcuts to the other phbern services. Width -# values describe horizontal grid cells (1-10 desktop / 6 tablet / 2 -# mobile, packed left-to-right). -homarr_apps: - - id: nextcloud - name: Nextcloud - description: "Cloud Storage & Collaboration" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png - href: https://cloud.phbe.souveredu.ch - width: 2 - - id: collabora - name: Collabora Office - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/collaboraonline.png - href: https://office.phbe.souveredu.ch - width: 2 - - id: drawio - name: Draw.io - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/drawio.png - href: https://draw.phbe.souveredu.ch - width: 2 - - id: send - name: Send - description: "Encrypted file-share" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/firefox-send.png - href: https://send.phbe.souveredu.ch - width: 2 - - id: opnform - name: OpnForm - description: "Self-hosted forms" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/opnform.png - href: https://forms.phbe.souveredu.ch - width: 2 - - id: bookstack - name: BookStack - description: "Wiki & documentation" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/bookstack.png - href: https://wiki.phbe.souveredu.ch - width: 2 - - id: authentik - name: Authentik - description: "Identity provider" - icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/authentik.png - href: https://auth.phbe.souveredu.ch - width: 2 diff --git a/inventories/demo-phbern/host_vars/application/main.yml b/inventories/demo-phbern/host_vars/application/main.yml deleted file mode 100644 index 6843222..0000000 --- a/inventories/demo-phbern/host_vars/application/main.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -# application host runs: authentik, authentik-ldap-outpost, -# nextcloud, collabora, drawio, send, opnform, homarr, bookstack diff --git a/inventories/demo-phbern/host_vars/application/nextcloud.yml b/inventories/demo-phbern/host_vars/application/nextcloud.yml deleted file mode 100644 index c3d57a8..0000000 --- a/inventories/demo-phbern/host_vars/application/nextcloud.yml +++ /dev/null @@ -1,121 +0,0 @@ ---- -# Bao secret /data/nextcloud expected to contain: -# postgres_password, admin_password -_nextcloud: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/nextcloud', url=vault_addr) }}" -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# No nextcloud_image pin: the role default (nextcloud:fpm) already ships -# the fix for the PHP UserConfig::getValueBool TypeError (#59629). - -# First entry is the canonical public FQDN (used for OVERWRITEHOST and -# OIDC redirects). Additional entries cover internal *.int.* names so -# collabora's WOPI callbacks hit nextcloud on a name with a valid -# internal cert instead of routing through the DMZ. -nextcloud_domains: - - "cloud.phbe.souveredu.ch" - - "cloud.int.phbe.souveredu.ch" -nextcloud_postgres_password: "{{ _nextcloud.postgres_password }}" -nextcloud_admin_user: admin -nextcloud_admin_password: "{{ _nextcloud.admin_password }}" - -nextcloud_enable_notify_push: true -# Use the internal FQDN for the notify_push setup check so curl from the -# nextcloud container hits the local traefik directly instead of -# hairpinning through the DMZ reverseproxy. -nextcloud_notify_push_domain: "cloud.int.phbe.souveredu.ch" - -# Collabora integration -# wopi_url (server-to-server: nextcloud calls collabora for discovery / -# capabilities) goes to the internal FQDN so the call stays in the LAN. -# public_wopi_url is what the browser loads the office iframe from — that -# stays on the public name reachable through the DMZ. -nextcloud_enable_collabora: true -nextcloud_collabora_domain: "office.int.phbe.souveredu.ch" -nextcloud_collabora_public_domain: "office.phbe.souveredu.ch" - -# Draw.io integration -nextcloud_enable_drawio: true -nextcloud_drawio_url: "https://draw.phbe.souveredu.ch" - -nextcloud_apps_to_install: - - groupfolders - - richdocuments - - spreed - - user_ldap - - user_oidc - - whiteboard - - drawio - - files_lock - - notify_push - -# S3 primary storage via Garage — server-to-server, so use the internal FQDN. -# Resolves through the internal DNS to the storage host and presents a valid -# cert from the local traefik on storage. -nextcloud_use_s3_storage: true -nextcloud_s3_key: "{{ lookup('digitalboard.core.garage_credentials', 'nextcloud', host='storage')['key_id'] }}" -nextcloud_s3_secret: "{{ lookup('digitalboard.core.garage_credentials', 'nextcloud', host='storage')['secret_key'] }}" -nextcloud_s3_bucket: "nextcloud" -nextcloud_s3_host: "s3.int.phbe.souveredu.ch" -nextcloud_s3_port: 443 -nextcloud_s3_ssl: true -nextcloud_s3_usepath_style: true - -# OIDC discovery/token/userinfo and the S3 backend both target FQDNs -# that resolve to RFC1918 addresses (auth.phbe.* via the reverseproxy, -# s3.int.* directly), which Nextcloud's DnsPinMiddleware would otherwise -# block as "local server access". -nextcloud_allow_local_remote_servers: true - -# Share the LDAP docker network with the authentik LDAP outpost -nextcloud_extra_networks: - - ldap - -# LDAP backend (Authentik LDAP outpost) -nextcloud_ldap_enabled: true -nextcloud_ldap_config: - ldapHost: "ldap://authentik-outpost-ldap-ldap-1" - ldapPort: "3389" - ldapAgentName: "cn=akadmin,ou=users,dc=phbe,dc=souveredu,dc=ch" - ldapAgentPassword: "{{ _authentik.admin_password }}" - ldapBase: "dc=phbe,dc=souveredu,dc=ch" - ldapBaseUsers: "ou=users,dc=phbe,dc=souveredu,dc=ch" - ldapTLS: "0" - turnOffCertCheck: "1" - ldapUserFilter: "(&(objectClass=user)(cn=*))" - ldapUserFilterObjectclass: "user" - ldapLoginFilter: "(&(objectClass=user)(cn=%uid))" - ldapLoginFilterUsername: "1" - ldapUserDisplayName: "cn" - ldapEmailAttribute: "mail" - ldapExpertUsernameAttr: "cn" - ldapExpertUUIDUserAttr: "uid" - ldapExpertUUIDGroupAttr: "uid" - ldapBaseGroups: "ou=groups,dc=phbe,dc=souveredu,dc=ch" - ldapGroupFilter: "(&(objectClass=group))" - ldapGroupFilterObjectclass: "group" - ldapGroupDisplayName: "cn" - ldapGroupMemberAssocAttr: "member" - ldapAdminGroup: "admins" - ldapCacheTTL: "600" - ldapPagingSize: "500" - ldapExperiencedAdmin: "1" - ldapConfigurationActive: "1" - -# OIDC providers for login (Authentik) -nextcloud_oidc_providers: - - identifier: authentik - display_name: "Login with Authentik" - client_id: nextcloud - client_secret: "{{ _authentik.nextcloud_oidc_secret }}" - # Discovery via the public FQDN. It resolves (internal DNS view) to - # the DMZ reverseproxy, which the backend subnet can now reach, so - # the server-to-server call stays in the LAN while the iss claim and - # all listed endpoints match the public hostname the browser sees - # during login — no host-rewrite or /etc/hosts pin needed. - discovery_url: "https://auth.phbe.souveredu.ch/application/o/nextcloud/.well-known/openid-configuration" - scope: "openid email profile" - unique_uid: true - mapping: - uid: preferred_username - display_name: name - email: email diff --git a/inventories/demo-phbern/host_vars/application/opnform.yml b/inventories/demo-phbern/host_vars/application/opnform.yml deleted file mode 100644 index b1364b4..0000000 --- a/inventories/demo-phbern/host_vars/application/opnform.yml +++ /dev/null @@ -1,50 +0,0 @@ ---- -# Bao secret /data/opnform expected to contain: -# app_key (must start with "base64:"), jwt_secret, front_api_secret, -# db_password, admin_password, oidc_client_secret -_opnform: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/opnform', url=vault_addr) }}" -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -opnform_domain: "forms.phbe.souveredu.ch" -opnform_extra_domains: - - "forms.int.phbe.souveredu.ch" -opnform_base_url: "https://forms.phbe.souveredu.ch" - -opnform_app_key: "{{ _opnform.app_key }}" -opnform_jwt_secret: "{{ _opnform.jwt_secret }}" -opnform_front_api_secret: "{{ _opnform.front_api_secret }}" -opnform_db_password: "{{ _opnform.db_password }}" - -# Bootstrap admin via API on first run so the manual setup page is -# skipped. The admin credentials are also required to seed the OIDC -# IdentityConnection through OpnForm's API (only an authenticated admin -# can create connections). -opnform_admin_name: "OpnForm Admin" -opnform_admin_email: "admin@phbe.souveredu.ch" -opnform_admin_password: "{{ _opnform.admin_password }}" - -# OIDC against Authentik. The public auth.phbe.* FQDN resolves (internal -# DNS view) to the DMZ reverseproxy, which the backend subnet can reach, -# so OpnForm's discovery/token/userinfo calls stay in the LAN while the -# iss claim matches the public hostname browsers see during login. -opnform_oidc_enabled: true -# Issuer must use the public FQDN: OpnForm validates the token's `iss` -# claim against this value, and Authentik emits the public hostname there. -opnform_oidc_issuer: "https://auth.phbe.souveredu.ch/application/o/opnform/" -opnform_oidc_client_id: "opnform" -opnform_oidc_client_secret: "{{ _opnform.oidc_client_secret }}" -opnform_oidc_client_name: "Authentik" -opnform_oidc_slug: "authentik" -opnform_oidc_domain: "phbe.souveredu.ch" -opnform_oidc_admin_group: "opnform-admins" - -# Disable password login entirely — every user goes through Authentik. -# All real users have @phbe.souveredu.ch addresses (matching -# opnform_oidc_domain above), so no password fallback is needed. -opnform_oidc_force_login: true - -# `/` and `/login` are intercepted and jump straight to Authentik. -# Public form deep-links (`/forms/`, `/admin/...`) keep working. -# Break-glass: /login?bypass=1 reaches the email form when the IdP is -# down. -opnform_oidc_sso_entrypoint: true diff --git a/inventories/demo-phbern/host_vars/application/send.yml b/inventories/demo-phbern/host_vars/application/send.yml deleted file mode 100644 index 3009b36..0000000 --- a/inventories/demo-phbern/host_vars/application/send.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# Send: anonymized self-hosted file-share (no login). First entry is the -# canonical public FQDN (used as BASE_URL); the *.int.* entry covers the -# server-to-server hop from the DMZ reverseproxy with a cert SAN that -# matches the backend hostname (same split-horizon pattern as cloud/draw). -send_domains: - - "send.phbe.souveredu.ch" - - "send.int.phbe.souveredu.ch" diff --git a/inventories/demo-phbern/host_vars/application/traefik.yml b/inventories/demo-phbern/host_vars/application/traefik.yml deleted file mode 100644 index 3dec920..0000000 --- a/inventories/demo-phbern/host_vars/application/traefik.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -# Services hosted on `application` that the DMZ reverseproxy should -# forward public traffic to. The DMZ traefik picks this up via -# hostvars[backend].traefik_dmz_exposed_services and renders a router + -# service for each entry into /config/services.yml. -traefik_dmz_exposed_services: - - name: authentik - domain: auth.phbe.souveredu.ch - backend_host: auth.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: nextcloud - domain: cloud.phbe.souveredu.ch - backend_host: cloud.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: collabora - domain: office.phbe.souveredu.ch - backend_host: office.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: drawio - domain: draw.phbe.souveredu.ch - backend_host: draw.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: send - domain: send.phbe.souveredu.ch - backend_host: send.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: opnform - domain: forms.phbe.souveredu.ch - backend_host: forms.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: homarr - domain: home.phbe.souveredu.ch - backend_host: home.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: bookstack - domain: wiki.phbe.souveredu.ch - backend_host: wiki.int.phbe.souveredu.ch - port: 443 - protocol: https diff --git a/inventories/demo-phbern/host_vars/reverseproxy/traefik.yml b/inventories/demo-phbern/host_vars/reverseproxy/traefik.yml deleted file mode 100644 index 48f6d08..0000000 --- a/inventories/demo-phbern/host_vars/reverseproxy/traefik.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -traefik_mode: dmz - -# The DMZ traefik discovers which services to expose by reading -# traefik_dmz_exposed_services from each backend host's host_vars -# (application/traefik.yml, storage/traefik.yml). See the role's -# tasks/main.yml — set_fact "Build service registry from backend -# servers (DMZ mode)". - -# From the DMZ network the public ns1 IP (193.43.183.169) is not -# reachable on port 53, but the internal address (172.16.9.169) is. -# Override the group-level traefik_acme_dns_nameserver from bao so -# lego's RFC2136 updates land at the internal interface. The TSIG -# key/secret are the same; only the transport target changes. -traefik_acme_dns_nameserver: "172.16.9.169" - -# Lego's propagation check normally polls the NS hostnames listed in -# the zone's SOA (ns1.digitalboard.ch.) — which resolves to the -# public IP that's unreachable from this DMZ host. Skip that check; -# lego still polls via the resolver above before asking LE to -# validate. -traefik_acme_disable_ans_checks: true diff --git a/inventories/demo-phbern/host_vars/storage/authentik_outpost_proxy.yml b/inventories/demo-phbern/host_vars/storage/authentik_outpost_proxy.yml deleted file mode 100644 index 1f17fbd..0000000 --- a/inventories/demo-phbern/host_vars/storage/authentik_outpost_proxy.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -# Proxy (ForwardAuth) outpost co-located with garage on storage. The -# garage-webui Traefik router's ForwardAuth middleware points at this -# container over the local `proxy` docker network, so the auth subrequest -# never leaves storage — the embedded outpost on the application host -# can't be used here because the cross-host hop mangles X-Forwarded-Host. -_authentik: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/authentik', url=vault_addr) }}" - -# Outpost pulls its config from authentik over the public FQDN (resolves -# via the internal DNS view to the DMZ reverseproxy the storage subnet can -# reach). Token must match the storage-proxy-outpost registered in -# authentik (see application/authentik.yml authentik_proxy_outposts). -# Verify the authentik TLS chain: auth.phbe.* presents a valid Let's -# Encrypt cert via the reverseproxy and the storage subnet reaches it, so -# there's no reason to disable verification (verified: ssl_verify_result=0). -authentik_outpost_proxy_host: "https://auth.phbe.souveredu.ch" -authentik_outpost_proxy_token: "{{ _authentik.proxy_outpost_token }}" -authentik_outpost_proxy_insecure: "false" -authentik_outpost_proxy_network: "proxy" diff --git a/inventories/demo-phbern/host_vars/storage/garage.yml b/inventories/demo-phbern/host_vars/storage/garage.yml deleted file mode 100644 index 4e7c2d7..0000000 --- a/inventories/demo-phbern/host_vars/storage/garage.yml +++ /dev/null @@ -1,51 +0,0 @@ ---- -# Bao secret /data/garage expected to contain: -# rpc_secret, admin_token, metrics_token, webui_password -_garage: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount + '/data/garage', url=vault_addr) }}" - -# First entry is the canonical public S3 FQDN. Additional entries -# cover internal *.int.* names so server-to-server S3 traffic (e.g. -# nextcloud → garage) stays in the LAN. -garage_s3_domains: - - "s3.phbe.souveredu.ch" - - "s3.int.phbe.souveredu.ch" -# First entry is the canonical public console FQDN. The *.int.* entry -# gives the DMZ a backend_host whose cert SAN matches (storage/traefik.yml), -# so the DMZ->storage hop verifies TLS instead of failing on a raw IP. -garage_webui_domains: - - "console.s3.phbe.souveredu.ch" - - "console.s3.int.phbe.souveredu.ch" -garage_use_ssl: true -garage_webui_enabled: true -# Gate the WebUI behind authentik (admins-only, via policy-binding on the -# authentik proxy app). Replaces the htpasswd Basic-Auth — AUTH_USER_PASS -# is dropped from the compose env when this is true. -# -# ForwardAuth talks to the storage-local proxy outpost over the docker -# network (role digitalboard.core.authentik_outpost_proxy). The embedded -# outpost on the application host can't be used cross-host: the extra -# reverseproxy hop mangles X-Forwarded-Host (appends :443 / a comma list) -# and the outpost then 404s instead of matching console.s3.* against the -# provider's external_host. A co-located outpost keeps the subrequest on -# the local network with a clean X-Forwarded-Host. -garage_webui_authentik_forward_auth: true -garage_webui_authentik_forward_auth_url: "http://authentik-outpost-proxy-proxy-1:9000/outpost.goauthentik.io/auth/traefik" -# Kept for completeness — only used when authentik ForwardAuth is off. -garage_webui_username: "admin" -garage_webui_password: "{{ _garage.webui_password | default('disabled') }}" - -garage_rpc_secret: "{{ _garage.rpc_secret }}" -garage_admin_token: "{{ _garage.admin_token }}" -garage_metrics_token: "{{ _garage.metrics_token }}" - -# Initial cluster bootstrap (single-node) -garage_bootstrap_enabled: true -garage_bootstrap_zone: "bern1" -garage_bootstrap_capacity: "100G" - -# Buckets and keys consumed by nextcloud -garage_s3_keys: - - name: nextcloud - buckets: - - name: nextcloud - permissions: ["read", "write"] diff --git a/inventories/demo-phbern/host_vars/storage/main.yml b/inventories/demo-phbern/host_vars/storage/main.yml deleted file mode 100644 index 75e487d..0000000 --- a/inventories/demo-phbern/host_vars/storage/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -# storage host runs: garage (S3 + WebUI) diff --git a/inventories/demo-phbern/host_vars/storage/traefik.yml b/inventories/demo-phbern/host_vars/storage/traefik.yml deleted file mode 100644 index 96c6110..0000000 --- a/inventories/demo-phbern/host_vars/storage/traefik.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -# Services hosted on `storage` that the DMZ reverseproxy should forward -# public traffic to. See application/traefik.yml for the mechanism. -# The garage-webui ForwardAuth no longer needs an auth FQDN pinned here — -# it talks to the storage-local proxy outpost over the docker network -# (see garage.yml), and the outpost reaches authentik via the public FQDN -# over the reverseproxy (firewall now permits backend -> DMZ). -traefik_dmz_exposed_services: - - name: garage-s3 - domain: s3.phbe.souveredu.ch - backend_host: s3.int.phbe.souveredu.ch - port: 443 - protocol: https - - name: garage-webui - domain: console.s3.phbe.souveredu.ch - # Internal name so the DMZ verifies the storage backend cert against a - # matching SAN (acme cert_mode, no insecureSkipVerify). garage now - # serves this SAN via garage_webui_domains (see garage.yml). - backend_host: console.s3.int.phbe.souveredu.ch - port: 443 - protocol: https diff --git a/inventories/demo-phbern/hosts.yml b/inventories/demo-phbern/hosts.yml index 6a264be..1dafcf5 100644 --- a/inventories/demo-phbern/hosts.yml +++ b/inventories/demo-phbern/hosts.yml @@ -22,7 +22,6 @@ all: backend_servers: hosts: application: - storage: garage_servers: hosts: @@ -32,6 +31,10 @@ all: hosts: application: + opencloud_servers: + hosts: + application: + collabora_servers: hosts: application: @@ -42,32 +45,4 @@ all: authentik_servers: hosts: - application: - - authentik_outpost_ldap_servers: - hosts: - application: - - # Proxy (ForwardAuth) outpost co-located with garage on storage, so - # the garage-webui ForwardAuth subrequest stays on the local docker - # network instead of crossing an extra reverseproxy hop (which mangles - # X-Forwarded-Host and breaks the embedded outpost's app matching). - authentik_outpost_proxy_servers: - hosts: - storage: - - send_servers: - hosts: - application: - - opnform_servers: - hosts: - application: - - homarr_servers: - hosts: - application: - - bookstack_servers: - hosts: - application: + application: \ No newline at end of file diff --git a/playbooks/site.yml b/playbooks/site.yml index 1676774..2e70255 100644 --- a/playbooks/site.yml +++ b/playbooks/site.yml @@ -70,12 +70,6 @@ roles: - digitalboard.core.authentik_outpost_ldap -- name: Deploy authentik proxy outpost - hosts: authentik_outpost_proxy_servers - become: yes - roles: - - digitalboard.core.authentik_outpost_proxy - - name: Deploy nextcloud service hosts: nextcloud_servers become: yes diff --git a/scripts/bao-seed.sh b/scripts/bao-seed.sh index dc59711..80945b7 100755 --- a/scripts/bao-seed.sh +++ b/scripts/bao-seed.sh @@ -154,27 +154,12 @@ ensure_key authentik secret_key gen_hex64 ensure_key authentik postgres_password gen_pass ensure_key authentik admin_password gen_pass ensure_key authentik ldap_outpost_token gen_hex32 -ensure_key authentik proxy_outpost_token gen_hex32 ensure_key authentik nextcloud_oidc_secret gen_hex32 ensure_key authentik opnform_oidc_secret gen_hex32 ensure_key authentik homarr_oidc_secret gen_hex32 ensure_key authentik bookstack_oidc_secret gen_hex32 write_secret authentik -echo "-> nextcloud" -read_secret nextcloud -ensure_key nextcloud postgres_password gen_long_pass -ensure_key nextcloud admin_password gen_pass -write_secret nextcloud - -echo "-> garage" -read_secret garage -ensure_key garage rpc_secret gen_hex32 -ensure_key garage admin_token gen_hex32 -ensure_key garage metrics_token gen_hex32 -ensure_key garage webui_password gen_pass -write_secret garage - echo "-> opnform" read_secret opnform ensure_key opnform app_key gen_app_key