mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
tasks/00_core.yml built webserver_extra_configuration by concatenating the helper iframes, the admin allowlist and the logout panel template lookups with `~`. With the shipped empty `services.keycloak.admin.allowed_networks` the allowlist template rendered no text, Ansible's template lookup returned None for it, and `~` wrote the word "None" into the auth vhost. OpenResty then refused `unknown directive "Nonelocation"` in front of the logout panel location and every deploy with Keycloak went red. The role now resolves the declared networks once in KEYCLOAK_ADMIN_NETWORKS and the task appends the allowlist template only when that list is not empty, the same guard the logout panel uses. The template renders the restricted /admin location unconditionally from KEYCLOAK_ADMIN_NETWORKS. With a list that did not contain the deploy host, the deploy still went red: Keycloak answers `/` with a redirect to `/admin/`, so the CSP health check and the front proxy probe, which request `/` from the host, got 403 and hlth-csp failed. While the allowlist is active, `location = /` now redirects to the account console of the platform realm instead, and the README no longer claims the deploy needs no entry for any reason. test_admin_allowlist.py rendered the template with plain Jinja, which yields '' for an empty template, so it could not see the None. It now evaluates the task's own webserver_extra_configuration expression through Ansible's Templar and real template lookup, which reproduces "HELPER_IFRAMES\nNoneLOGOUT_PANEL" on the old task, and covers the root redirect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
178 lines
9.8 KiB
Markdown
178 lines
9.8 KiB
Markdown
# Keycloak
|
||
|
||
## Description
|
||
|
||
Step into a secure future with [Keycloak](https://www.keycloak.org/)! This open‐source identity and access management solution offers powerful single sign-on (SSO), multi-factor authentication, and user federation capabilities. With support for industry standards such as SAML and OpenID Connect, Keycloak helps you protect and streamline access to your applications.
|
||
|
||
## Overview
|
||
|
||
This role deploys Keycloak in a Docker environment, integrating it with a PostgreSQL database and enabling operation behind a reverse proxy such as NGINX. It manages container orchestration and configuration via Docker Compose and environment variable templates, ensuring a secure and scalable identity management solution.
|
||
|
||
## Cosmos
|
||
|
||
The diagram places Keycloak in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph deps [Dependencies]
|
||
dep_svc_db_openldap["svc-db-openldap 🐳🐝"]
|
||
dep_svc_db_postgres["svc-db-postgres 🐳🐝"]
|
||
dep_svc_net_tor["svc-net-tor 🐳🐝"]
|
||
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
|
||
dep_web_app_matomo["web-app-matomo 🐳🐝"]
|
||
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
|
||
dep_web_app_stalwart["web-app-stalwart 🐳🐝"]
|
||
dep_web_svc_css["web-svc-css 💻"]
|
||
dep_web_svc_logout["web-svc-logout 🐳🐝"]
|
||
end
|
||
subgraph role [web-app-keycloak 🐳🐝]
|
||
svc_logout["logout ❌"]
|
||
svc_ldap["ldap"]
|
||
svc_dashboard["dashboard"]
|
||
svc_matomo["matomo"]
|
||
svc_email["email"]
|
||
svc_javascript["javascript"]
|
||
svc_keycloak["keycloak"]
|
||
svc_sso_proxy["sso_proxy"]
|
||
svc_postgres["postgres"]
|
||
svc_css["css"]
|
||
svc_recaptcha["recaptcha"]
|
||
svc_prometheus["prometheus"]
|
||
svc_tor["tor"]
|
||
end
|
||
subgraph dependents [Dependents]
|
||
dpt_web_app_akaunting["web-app-akaunting 🐳🐝"]
|
||
dpt_web_app_baserow["web-app-baserow 🐳🐝"]
|
||
dpt_web_app_bigbluebutton["web-app-bigbluebutton 🐳🐝"]
|
||
dpt_web_app_bluesky["web-app-bluesky 🐳🐝"]
|
||
dpt_web_app_bookwyrm["web-app-bookwyrm 🐳🐝"]
|
||
dpt_web_app_bridgy_fed["web-app-bridgy-fed 🐳🐝"]
|
||
dpt_web_app_checkmk["web-app-checkmk 🐳🐝"]
|
||
dpt_web_app_chess["web-app-chess 🐳🐝"]
|
||
dpt_web_app_confluence["web-app-confluence 🐳🐝"]
|
||
dpt_web_app_dashboard["web-app-dashboard 🐳🐝"]
|
||
dpt_web_app_decidim["web-app-decidim 🐳🐝"]
|
||
dpt_web_app_discourse["web-app-discourse 🐳🐝"]
|
||
dpt_more["..."]
|
||
end
|
||
dep_svc_db_openldap -. "0..1" .-> svc_ldap
|
||
dep_svc_db_postgres -. "0..1" .-> svc_postgres
|
||
dep_svc_net_tor -. "0..1" .-> svc_tor
|
||
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
|
||
dep_web_app_matomo -. "0..1" .-> svc_matomo
|
||
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
|
||
dep_web_app_stalwart -. "0..1" .-> svc_email
|
||
dep_web_svc_css -. "0..1" .-> svc_css
|
||
dep_web_svc_logout -. "0..1" .-> svc_javascript
|
||
svc_keycloak -- "1:1" --> dpt_more
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_akaunting
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_baserow
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_bigbluebutton
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_bluesky
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_bookwyrm
|
||
svc_keycloak -- "0..0" --> dpt_web_app_bridgy_fed
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_checkmk
|
||
svc_keycloak -- "0..0" --> dpt_web_app_chess
|
||
svc_keycloak -- "0..0" --> dpt_web_app_confluence
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_dashboard
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_decidim
|
||
svc_keycloak -. "0..1" .-> dpt_web_app_discourse
|
||
linkStyle 15,17,18 stroke:red;
|
||
```
|
||
|
||
Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (enabled only in matching deployments); red `0..0` edges are turned off in this role. Node markers show the role's deploy modes (💻 host, 🐳 compose, 🐝 swarm); ❌ marks a service that is explicitly turned off, and ⚙️ an Ansible role dependency declared in `meta/main.yml`.
|
||
|
||
## Features
|
||
|
||
- **Comprehensive Identity Management:** Manage users, roles, and permissions across your applications with robust SSO and user federation.
|
||
- **Advanced Security Options:** Benefit from multi-factor authentication, configurable password policies, and secure session management.
|
||
- **Standards Support:** Seamlessly integrate with SAML, OpenID Connect, and OAuth2 to support various authentication flows.
|
||
- **Scalable and Customizable:** Easily tailor settings and scale your Keycloak instance to meet growing demands.
|
||
|
||
## Quick Setup
|
||
|
||
### Development
|
||
|
||
Clone, set up the workstation, and deploy Keycloak onto the local stack:
|
||
|
||
```bash
|
||
git clone https://github.com/infinito-nexus/core.git
|
||
cd core
|
||
make onboard
|
||
make compose-deploy mode=reinstall apps=web-app-keycloak full_cycle=false
|
||
```
|
||
|
||
### Production
|
||
|
||
Run the published image to provision the inventory and deploy Keycloak to a managed server (the mounted volume persists the inventory):
|
||
|
||
```bash
|
||
APP=web-app-keycloak
|
||
HOST=<your-server>
|
||
DOMAIN=<your-domain>
|
||
TLS_MODE=self_signed
|
||
SSH_PUBLIC_KEY="<your-ssh-public-key>"
|
||
|
||
docker run --rm -it \
|
||
-v "$PWD/inventories:/etc/infinito.nexus/inventories" \
|
||
-e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
|
||
ghcr.io/infinito-nexus/core/debian bash -c '
|
||
INVENTORY=/etc/infinito.nexus/inventories/production
|
||
infinito administration inventory provision "$INVENTORY" \
|
||
--inventory-file "$INVENTORY/devices.yml" \
|
||
--host "$HOST" \
|
||
--include "$APP" \
|
||
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
|
||
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
|
||
--password-file "$INVENTORY/.password" \
|
||
--diff -vv'
|
||
```
|
||
|
||
## Developer Notes
|
||
|
||
For the OIDC variable tree, claim rules, and the policy that app-specific protocol mappers belong in per-client scope files (not in the shared `clients/default.json.j2`), see [oidc.md](../../docs/contributing/design/iam/oidc.md).
|
||
|
||
## Admin Console Access
|
||
|
||
`services.keycloak.admin.allowed_networks` limits every path under `/admin`, which carries the admin console and the admin REST API under `/admin/realms`, to the listed source networks. Each entry is an IPv4 or IPv6 address or a CIDR block. The vhost renders one `allow` per entry followed by `deny all`, so any other client gets `403`. The list ships empty, and an empty list leaves `/admin` open. Declare it in the inventory:
|
||
|
||
```yaml
|
||
applications:
|
||
web-app-keycloak:
|
||
services:
|
||
keycloak:
|
||
admin:
|
||
allowed_networks:
|
||
- 203.0.113.0/24
|
||
- 2001:db8::/48
|
||
```
|
||
|
||
The OIDC endpoints under `/realms/` and the static assets under `/resources/` stay public.
|
||
|
||
The deploy's `kcadm.sh` logins need no entry: every one, including the ones `web-app-opentalk` and `web-app-mattermost` make, runs inside the Keycloak container against `KEYCLOAK_SERVER_INTERNAL_URL`. While the list is set, `/` redirects to the account console of the platform realm instead of to `/admin/`, so the CSP health check and the proxy probe, which request `/` from the host itself, need no entry either. Clients that call `/admin` through the public URL need their source network in the list:
|
||
|
||
- The OpenTalk controller's user search calls the admin REST API from its container, so the vhost sees the container's Docker network address. `NETWORK_INTERNAL_CIDRS` covers the loopback and private ranges those addresses come from.
|
||
- Onion visitors reach the vhost from the Tor daemon's address.
|
||
- The Playwright suites of several roles read and change realm groups through the admin REST API.
|
||
|
||
## Further Resources
|
||
|
||
- [Keycloak Official Website](https://www.keycloak.org/)
|
||
- [Official Keycloak Documentation](https://www.keycloak.org/documentation.html)
|
||
- [Keycloak GitHub Repository](https://github.com/keycloak/keycloak)
|
||
- [Setting up Keycloak behind a Reverse Proxy](https://www.keycloak.org/server/reverseproxy)
|
||
- [Wikipedia](https://en.wikipedia.org/wiki/Keycloak)
|
||
- [Youtube Tutorial](https://www.youtube.com/watch?v=fvxQ8bW0vO8)
|
||
|
||
## Persona contract opt-outs
|
||
|
||
This role declares `PERSONA_ADMINISTRATOR_BLOCKED` and `PERSONA_BIBER_BLOCKED` in `templates/playwright.env.j2`. Keycloak provides SSO rather than consuming it: `meta/services.yml` carries no `sso` block (the `keycloak` service declares `provides: sso`) and pins `logout.enabled: false`, so the canonical URL has neither an oauth2-proxy gate nor an in-app OIDC login link for the shared persona helpers to click or log out of. The administrator persona is additionally out of reach because Keycloak's only admin surface is the master-realm console, which accepts the permanent super-admin account (`KEYCLOAK_PERMANENT_ADMIN_USERNAME` / `KEYCLOAK_PERMANENT_ADMIN_PASSWORD` in `vars/main.yml`, backed by the role-local `credentials.administrator_password`) and not the normal-realm `ADMIN_PASSWORD` the helper types.
|
||
|
||
Both journeys are covered bespoke in `files/playwright/playwright.spec.js`: the master-realm super administrator drives the admin console, and the normal-realm administrator and biber each drive the realm account console including sign-out. The path back to the generic personas is an app-shaped surface behind the shared auth chain, which the identity provider by definition does not have.
|
||
|
||
## Credits
|
||
|
||
Implemented by **[Kevin Veen-Birkenbach](https://www.veen.world)**.
|
||
Part of the [Infinito.Nexus Project](https://s.infinito.nexus/code) and maintained by [Kevin Veen-Birkenbach](https://www.veen.world).
|
||
Licensed under the [Infinito.Nexus Community License (Non-Commercial)](https://s.infinito.nexus/license).
|