feat(infra): Keycloak with mock DigiD/eHerkenning/eIDAS/medewerker realms (closes #3)
Some checks failed
CI / lint (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / unit (pull_request) Has been cancelled
CI / compose-smoke (pull_request) Has been cancelled

Add infra/keycloak/docker-compose.yml (Keycloak 26.1, dev mode, --import-realm)
with four realms imported at boot (infra/keycloak/realms/*.json): digid,
eherkenning, eidas, medewerker. Each has a public big-portal OIDC client
(standard flow + direct access grants) and test users; protocol mappers inject
the identifying claims (bsn / kvk / eidas_id) and medewerker realm roles.

Add `make keycloak-up/keycloak-smoke/keycloak-down`; keycloak-smoke runs
infra/keycloak/check_realms.py (password-grant login per realm, asserts the
claim). Document credentials in docs/synthetic-data.md and a runbook.

Verified: `make keycloak-smoke` green — all four realms log in and return the
expected claims (bsn 123456782, kvk 12345678, eidas_id FR/NL..., role behandelaar).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 16:16:38 +02:00
parent 195a76aaf2
commit 30d8aecf8c
9 changed files with 352 additions and 1 deletions

37
docs/runbooks/keycloak.md Normal file
View File

@@ -0,0 +1,37 @@
# Keycloak runbook
Keycloak (`infra/keycloak/docker-compose.yml`) runs in dev mode with four realms
imported at boot from `infra/keycloak/realms/`: **digid**, **eherkenning**, **eidas**,
**medewerker**. It mocks the Dutch identity brokers so portals can do real OIDC logins
locally. Host port **:8180**.
## Quick test (`make`)
```bash
make keycloak-up # start Keycloak + import realms (~30-60s first boot)
make keycloak-smoke # start + verify every realm logs in and returns its claim
make keycloak-down # stop + wipe
```
`make keycloak-smoke` runs `infra/keycloak/check_realms.py`, which does a password-grant
login per realm and asserts the identifying claim:
| Realm | User | Claim asserted |
|---|---|---|
| digid | jan-burger | `bsn` |
| eherkenning | acme-ondernemer | `kvk` |
| eidas | pierre-dupont | `eidas_id` |
| medewerker | merel-behandelaar | role `behandelaar` |
All test users / credentials are in [../synthetic-data.md](../synthetic-data.md).
## Notes
- **Admin console:** <http://localhost:8180/> — `admin` / `admin` (dev only).
- **Client `big-portal`** is public with `standardFlowEnabled` (browser redirect login)
*and* `directAccessGrantsEnabled` (password grant, used by the smoke test).
- **Dev store:** in-memory H2 via `start-dev`; realms re-import on each boot, so changes
made in the admin UI don't persist. Edit the realm JSONs to make durable changes.
- **Image** pinned to `quay.io/keycloak/keycloak:26.1`.
- Claims are injected by OIDC protocol mappers on `big-portal` (user attribute → token
claim); `medewerker` roles come through `realm_access.roles`.

35
docs/synthetic-data.md Normal file
View File

@@ -0,0 +1,35 @@
# Synthetic data
All credentials here are **dev-only** synthetic test data — never real personal data,
never used outside local development.
## Keycloak realms (S-02)
Keycloak runs at <http://localhost:8180> (admin console: **admin / admin**). Four realms
are imported at boot from `infra/keycloak/realms/`. Each has a public OIDC client
**`big-portal`** (standard flow + direct access grants enabled, redirect URIs `*` for dev).
All test users share the password **`test123`**.
| Realm | Mimics | User | Identifying claim |
|---|---|---|---|
| `digid` | DigiD (burgers) | `jan-burger` | `bsn` = `123456782` |
| `eherkenning` | eHerkenning (bedrijven) | `acme-ondernemer` | `kvk` = `12345678` |
| `eidas` | eIDAS (EU) | `pierre-dupont` | `eidas_id` = `FR/NL/AB-1234-5678` |
| `medewerker` | Internal staff | `merel-behandelaar` | role `behandelaar` |
| `medewerker` | Internal staff | `tom-teamlead` | roles `behandelaar`, `teamlead` |
The identifying claims are injected via OIDC protocol mappers on `big-portal`
(user-attribute → token claim); `medewerker` roles appear in `realm_access.roles`.
## Get a token (for testing)
```bash
curl -s -X POST \
http://localhost:8180/realms/digid/protocol/openid-connect/token \
-d grant_type=password -d client_id=big-portal \
-d username=jan-burger -d password=test123 -d scope=openid | jq -r .access_token
```
Decode the JWT payload to see the `bsn` claim. `make keycloak-smoke` checks every realm
automatically.