Files
computer-playbook/roles/web-app-keycloak/meta/services.yml
kevinveenbirkenbachandClaude Opus 5.5 c8c3f96bf9 feat(web-app-keycloak): serve /admin only to declared source networks
The Keycloak vhost proxied every path to Keycloak, so the admin console
and the admin REST API under /admin/realms answered any client on the
internet (observed on auth.veen.world and auth.infinito.nexus), and
sys-svc-proxy had no way to limit a path by source address.

services.keycloak.admin.allowed_networks now takes the networks the
operator declares in the inventory. When the list is non-empty, the
vhost gets its own `location ^~ /admin`, rendered through the shared
html.conf.j2 like `/`, with one `allow` per entry and `deny all`. The
`^~` modifier keeps a regex location from taking an admin path over.
The OIDC endpoints under /realms/ and the assets under /resources/ stay
public. The role ships the list empty and adds no network of its own,
so an inventory that declares nothing renders today's vhost unchanged.
kc_admin_networks rejects anything but a list of addresses or CIDR
blocks without host bits, so a typo fails the render instead of the
nginx reload.

The deploy's own admin calls do not cross the vhost: every kcadm.sh
login, including web-app-opentalk's and web-app-mattermost's, runs
inside the Keycloak container against a loopback URL, and a unit test
now pins that. The README names the callers that do cross it (the
OpenTalk controller's user search, onion visitors, the Playwright
suites) and so need an entry. The TODO.md item is dropped.

Closes #1121

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:06:21 +01:00

129 lines
3.7 KiB
YAML

---
# nocheck: seaweedfs-required — object storage not implemented for this role
# nocheck: dynamic-flag
# nocheck: logout
logout:
enabled: false
shared: false
# nocheck: dynamic-flag cluster-wide LDAP federation keys on inventory groups[]; host-scoped group_names is cross-host cache-poisoned for this destructive flag
ldap:
bond: 1
enabled: "{{ (groups['svc-db-openldap'] | default([]) | length) > 0 }}"
shared: "{{ (groups['svc-db-openldap'] | default([]) | length) > 0 }}"
connection_pooling: false
sync:
period:
changes: 900
full: 86400
dashboard:
bond: 1
enabled: "{{ 'web-app-dashboard' in group_names }}"
shared: "{{ 'web-app-dashboard' in group_names }}"
iframe: false
matomo:
bond: 1
enabled: "{{ 'web-app-matomo' in group_names }}"
shared: "{{ 'web-app-matomo' in group_names }}"
email:
bond: 1
enabled: "{{ MAIL_PROVIDER in group_names }}"
shared: "{{ MAIL_PROVIDER in group_names }}"
javascript:
bond: 1
enabled: "{{ 'web-svc-logout' in group_names }}"
shared: "{{ 'web-svc-logout' in group_names }}"
# nocheck: playwright-service-flag — self-provider
keycloak:
healthcheck:
flavor: http_status
path: health/ready
port_key: management
# nocheck: healthcheck-timing-override the management endpoint answers from the JVM, which does not reply inside the 5s default while the realm is still importing
timeout: 15s
retries: 5
modes:
compose:
enabled: true
swarm:
enabled: true
bond: 1
enabled: true # nocheck: dynamic-flag
shared: true
provides: sso
image: quay.io/keycloak/keycloak
version: "26.8.1"
name: keycloak
min_storage: 468MB
cpus: "2.0"
mem_reservation: 2g
mem_limit: 4g
pids_limit: 1024
realm:
import_enabled: true
totp_enabled: true
users:
emails_verified: true
scopes:
nextcloud: nextcloud
moodle: moodle
accounts:
bootstrap:
username: administrator
system:
username: "{{ SOFTWARE_NAME | replace('.', '_') | lower }}"
admin:
allowed_networks: []
ports:
local:
http: 8032
internal:
http: 8080
management: 9000
proxy:
service_key: keycloak
run_after:
- web-app-matomo
- web-app-stalwart
- web-app-mailu
lifecycle: beta
# Exception: sso-proxy sidecar; upstream artefact names (oauth2-proxy image, oauth2-proxy-keycloak.cfg) stay literal, project identifiers use the sso_proxy namespace
# nocheck: dynamic-flag — per-consumer fan-out
# nocheck: playwright-service-flag — sidecar shape, no probe-able surface flag
sso_proxy:
image: quay.io/oauth2-proxy/oauth2-proxy
version: "v7.15.5"
min_storage: 220MB
configuration_file: oauth2-proxy-keycloak.cfg
ports:
internal:
http: 4180
cpus: "0.25"
mem_reservation: "64m"
mem_limit: "128m"
pids_limit: 256
# nocheck: playwright-service-flag — DB engine, covered by role-local integration tests
postgres:
bond: 1
enabled: true # nocheck: dynamic-flag
shared: "{{ 'svc-db-postgres' in group_names }}"
# nocheck: playwright-service-flag — css consumer, no Playwright surface (provider: web-svc-css)
css:
bond: 1
enabled: "{{ 'web-svc-css' in group_names }}"
shared: "{{ 'web-svc-css' in group_names }}"
# nocheck: dynamic-flag
# nocheck: playwright-service-flag — captcha backend, infrastructural
recaptcha:
enabled: "{{ RECAPTCHA_ENABLED | bool }}"
prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
shared: "{{ 'web-app-prometheus' in group_names }}"
# nocheck: playwright-service-flag
# nocheck: variants-coverage — sso-variant-tor requires the onion on in every variant, so the false polarity has no variant left to live in
tor:
bond: 0.5
enabled: "{{ 'svc-net-tor' in group_names }}"
shared: true # nocheck: dynamic-flag