scaffold: hub-and-spoke layout, 4-network topology, 13 stack stubs
Initial structure for R&D infrastructure:
- stacks/ — 13 reusable, runnable stack stubs (kebab-case)
cloud-and-edge: node-red, influxdb, grafana, keycloak, portainer,
nginx-proxy, mqtt, postfix
cloud-only: wireguard-server, gitea, jenkins, sql (postgres stub)
edge-only: wireguard-client
- cloud/ — single central hub composition with 4 networks
(edge, app, data internal, mgmt) and include: stubs
- sites/ — per-plant edge folders (template README only for now)
- docs/architecture.md — hub-and-spoke + ingress + segmentation rationale
Network model: only nginx-proxy (80/443/8883) and wireguard-server
(51820/udp) publish ports on the cloud host. Edge nginx publishes
80/443 on plant-LAN interface only. MQTT cloud-side via nginx stream
proxy; MQTT edge-side internal-only; Postfix outbound-only.
OT layer (OPCUA, PLCs) is out of scope for this repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:37:59 +02:00
# keycloak
2026-05-21 13:54:57 +02:00
Identity provider for SSO across all R&D services. **Cloud-only ** for now (edges get their own realms once we cover the edge layer).
scaffold: hub-and-spoke layout, 4-network topology, 13 stack stubs
Initial structure for R&D infrastructure:
- stacks/ — 13 reusable, runnable stack stubs (kebab-case)
cloud-and-edge: node-red, influxdb, grafana, keycloak, portainer,
nginx-proxy, mqtt, postfix
cloud-only: wireguard-server, gitea, jenkins, sql (postgres stub)
edge-only: wireguard-client
- cloud/ — single central hub composition with 4 networks
(edge, app, data internal, mgmt) and include: stubs
- sites/ — per-plant edge folders (template README only for now)
- docs/architecture.md — hub-and-spoke + ingress + segmentation rationale
Network model: only nginx-proxy (80/443/8883) and wireguard-server
(51820/udp) publish ports on the cloud host. Edge nginx publishes
80/443 on plant-LAN interface only. MQTT cloud-side via nginx stream
proxy; MQTT edge-side internal-only; Postfix outbound-only.
OT layer (OPCUA, PLCs) is out of scope for this repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:37:59 +02:00
2026-05-21 13:54:57 +02:00
- **Public hostname**: `auth.wbd-rd.nl` (reverse-proxied via nginx-proxy at HTTPS → backend HTTP 8080)
- **Networks**: `app` (OIDC endpoints for relying-party apps) + `mgmt` (admin console) + `data` (postgres backend)
- **Backend**: postgres `keycloak` database in `sql` stack — provisioned automatically by `sql/config/init.d/01-databases.sh` on first start
- **Volume**: `keycloak-data` (themes, providers, realm exports)
## First-run bootstrap
`KC_BOOTSTRAP_ADMIN_USERNAME` + `KC_BOOTSTRAP_ADMIN_PASSWORD` create the master-realm admin on first start. **Change the password immediately after first login ** via the admin console.
After deployment:
```bash
# 1. Bring it up (sql must be running first)
cd /mnt/d/gitea/RnD/infra/cloud
docker compose up -d sql # if not already up
docker compose up -d keycloak
# 2. Watch logs until you see "Keycloak <version> on JVM started"
docker compose logs -f keycloak
# 3. Browse https://auth.wbd-rd.nl/ → admin console
# (until cert is bootstrapped, https://<cloud-host>:9443 portainer can show logs)
```
## Realm + clients (TODO — design before deploy day 2)
**Recommended structure**: one realm `wbd` containing all R&D apps as separate clients.
| Client ID | App | Redirect URI | Flow |
|---|---|---|---|
| grafana | Grafana | `https://dash.wbd-rd.nl/login/generic_oauth` | code |
| gitea | Gitea | `https://git.wbd-rd.nl/user/oauth2/keycloak/callback` | code |
| node-red | Node-RED | `https://flow.wbd-rd.nl/auth/strategy/callback/` | code |
| jenkins | Jenkins | `https://ci.wbd-rd.nl/securityRealm/finishLogin` | code |
| jupyterhub | JupyterHub | `https://hub.wbd-rd.nl/hub/oauth_callback` | code |
| mlflow | MLflow (via oauth2-proxy) | `https://ml.wbd-rd.nl/oauth2/callback` | code |
| portainer-ce | Portainer (via oauth2-proxy) | `https://ops.wbd-rd.nl/oauth2/callback` | code |
Apps **without native OIDC ** (mlflow, portainer-CE) sit behind an `oauth2-proxy` sidecar that nginx `auth_request` s to. That's a TODO stack we'll add when we wire up mlflow / portainer SSO.
## Realm-as-code
Drop exported realm JSON into `config/realms/` . On first start, Keycloak imports anything in `/opt/keycloak/data/import/` if `KC_IMPORT_REALM_DIR` is set or if you run `kc.sh import` manually. Recommended workflow:
1. Configure the realm by hand once in the UI
2. `docker compose exec keycloak /opt/keycloak/bin/kc.sh export --dir /opt/keycloak/data/import --realm wbd`
3. Commit the exported file under `stacks/keycloak/config/realms/`
4. Subsequent fresh deploys auto-import
## TODO
- Realm bootstrap script (provision `wbd` realm + clients above)
- Theme: WBD branding (logo, colors)
- User federation (LDAP from corporate AD, if applicable)
- 2FA policy
- Session / token lifetimes per client
- oauth2-proxy stack for apps without native OIDC
- Realm export → `config/realms/wbd.json` committed