Identity & Access Management — Keycloak¶
Keycloak is the identity backbone of the CDM platform. In the two-stack architecture, two Keycloak instances are involved: one in the Provider-Stack and one per Tenant-Stack.
Realm Structure¶
Provider-Stack Keycloak¶
Manages a single cdm realm — all roles and users are consolidated here:
| Realm | Purpose | OIDC Clients |
|---|---|---|
cdm |
Platform services, all users, cross-tenant SSO | grafana, iot-bridge, portal, pgadmin, rabbitmq-management, dashboard |
cdm realm roles:
| Role | Description |
|---|---|
cdm-admin |
Platform administrator |
cdm-operator |
Fleet operator |
cdm-viewer |
Read-only access |
platform-admin |
Full administrative access to CDM platform and all tenants |
platform-operator |
Day-to-day operations; read-only on tenants |
pgadmin-users |
Grants access to the pgAdmin UI via OIDC |
matrix-viewer |
Composite role: view-users, query-users, view-realm (realm-management) |
Tenant-Stack Keycloak (Phase 2)¶
Each tenant operates its own Keycloak instance with a single tenant realm that mirrors
the cdm realm role structure. The tenant realm is registered as an Identity Provider
in the Provider cdm realm, enabling platform admins to access the Tenant-Stack with their
cdm realm credentials.
| Realm | Purpose | OIDC Clients |
|---|---|---|
<tenant-id> |
Tenant-specific services | thingsboard, hawkbit, grafana, portal, pgadmin |
Keycloak Federation Flow¶
graph LR
subgraph provider["Provider-Stack Keycloak"]
CDM["cdm realm<br>(all users + roles)"]
end
subgraph t1["Tenant-Stack A Keycloak"]
TR1["tenant-acme realm"]
end
subgraph t2["Tenant-Stack B Keycloak"]
TR2["tenant-beta realm"]
end
TR1 -->|"Identity Provider federation"| CDM
TR2 -->|"Identity Provider federation"| CDM
When a tenant JOIN request is approved, the IoT Bridge API automatically:
1. Registers the Tenant Keycloak realm as an OIDC Identity Provider in the cdm realm.
2. Configures role mappers so platform-admin maps to cdm-admin in the tenant realm.
OIDC Client Configuration (cdm realm)¶
| Client ID | Service | Type | Secret env var |
|---|---|---|---|
grafana |
Grafana (Provider Dashboards) | confidential | GRAFANA_OIDC_SECRET |
iot-bridge |
IoT Bridge API | confidential + service-account | BRIDGE_OIDC_SECRET |
portal |
CDM Provider Portal | confidential | PORTAL_OIDC_SECRET |
pgadmin |
pgAdmin OIDC proxy | confidential | PGADMIN_OIDC_SECRET |
rabbitmq-management |
RabbitMQ Management UI | confidential, standard flow | RABBITMQ_MANAGEMENT_OIDC_SECRET |
dashboard |
Landing page Silent SSO | public | — |
All redirectUris and webOrigins are set to * to support dynamic Codespaces hostnames.
The rabbitmq-management client has five custom client scopes that map directly to
RabbitMQ permissions:
| Scope | RabbitMQ permission |
|---|---|
rabbitmq.tag:administrator |
Management UI admin tag |
rabbitmq.read:*/* |
Read all resources |
rabbitmq.write:*/* |
Write all resources |
rabbitmq.configure:*/* |
Configure all resources |
rabbitmq.tag:monitoring |
Read-only monitoring tag |
All four non-monitoring scopes are assigned as default scopes on rabbitmq-management so
that platform-admin users receive full administrator access to RabbitMQ on SSO login.
Grafana — realm-roles mapper: The
grafanaclient has anoidc-usermodel-realm-role-mapperthat injects all realm roles as a flat array into therolesclaim. Grafana maps this to its internal role viaGF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH.
Role-Based Access¶
| Role | Grafana (Provider) | IoT Bridge API |
|---|---|---|
cdm-admin |
Admin | Full access (incl. tenant onboarding) |
cdm-operator |
Editor | Read + trigger deployments |
cdm-viewer |
Viewer | Read-only |
platform-admin |
Admin | Full access |
platform-operator |
Editor | Read-only on tenants |
All roles live in the single
cdmrealm — separation is purely role-based, not realm-based.
For the complete cross-service access matrix including pgAdmin, RabbitMQ, and Keycloak Admin Console, see Access Matrix.
Updating Client Secrets¶
- Log in to http://localhost:8888/auth/admin/cdm/console/ → Clients.
- For each client, go to Credentials → copy or regenerate the Secret.
- Update
provider-stack/.env:GRAFANA_OIDC_SECRET=<from Keycloak cdm realm → grafana> BRIDGE_OIDC_SECRET=<from Keycloak cdm realm → iot-bridge> PORTAL_OIDC_SECRET=<from Keycloak cdm realm → portal> PGADMIN_OIDC_SECRET=<from Keycloak cdm realm → pgadmin> RABBITMQ_MANAGEMENT_OIDC_SECRET=<from Keycloak cdm realm → rabbitmq-management> - Restart affected services:
Security Considerations¶
Default Admin Password
Change KC_ADMIN_PASSWORD in provider-stack/.env before exposing Keycloak to any network.
Brute-Force Protection
Enable Keycloak’s built-in brute-force detection: Realm Settings → Security Defenses → Brute Force Detection.
HTTPS in Production
Caddy handles HTTPS automatically via ACME. Ensure KC_HOSTNAME in .env points to the
external FQDN so Keycloak builds correct redirect URIs.