Files
computer-playbook/docs/contributing/design/iam/oidc.md

114 lines
5.7 KiB
Markdown

# OIDC 🪪
This page describes how application roles consume the platform's OIDC provider, [Keycloak](../../../../roles/web-app-keycloak/).
The variable tree lives in [11_oidc.yml](../../../../group_vars/all/11_oidc.yml) under `defaults_oidc`.
For the cross-cutting IAM principles, including the paired identifier rule shared with LDAP, see [common.md](common.md).
For LDAP-side details see [ldap.md](ldap.md).
## Variable Tree 🌳
The `OIDC` tree is a lazy group_var: it resolves in the consuming role's
context, so the issuer family-aligns to that consumer (clearnet page →
clearnet issuer, onion page → onion issuer). Inventory overrides therefore
live under `OIDC_OVERRIDE:` — an inventory-level `OIDC:` block would shadow
the lazy resolution and is rejected by a constructor assert.
The keys consumers care about:
| Key | Meaning |
|---|---|
| `OIDC.URL` | Base URL of the Keycloak instance |
| `OIDC.CLIENT.ID` | Client identifier, defaults to `SOFTWARE_DOMAIN` |
| `OIDC.CLIENT.SECRET` | Client secret, MUST come from the inventory (`OIDC_OVERRIDE.CLIENT.SECRET`) |
| `OIDC.CLIENT.REALM` | Realm the client is registered in |
| `OIDC.CLIENT.ISSUER_URL` | Issuer URL, equal to discovery `issuer` |
| `OIDC.CLIENT.DISCOVERY_DOCUMENT` | `.well-known/openid-configuration` URL |
| `OIDC.CLIENT.AUTHORIZE_URL` | Authorization endpoint |
| `OIDC.CLIENT.TOKEN_URL` | Token endpoint |
| `OIDC.CLIENT.USER_INFO_URL` | Userinfo endpoint |
| `OIDC.CLIENT.LOGOUT_URL` | RP-initiated logout endpoint |
| `OIDC.CLIENT.CERTS` | JWKS endpoint, equal to discovery `jwks_uri` |
| `OIDC.CLIENT.ACCOUNT.URL` | User self-service console root |
| `OIDC.CLIENT.ACCOUNT.PROFILE_URL` | Personal-info section deep link |
| `OIDC.CLIENT.ACCOUNT.SECURITY_URL` | Login and security section deep link |
| `OIDC.CLIENT.CHANGE_CREDENTIALS` | Direct link to the credentials editor |
| `OIDC.CLIENT.RESET_CREDENTIALS` | Public password-reset entry point |
| `OIDC.BUTTON_TEXT` | Default label for SSO login buttons |
When a role needs an OIDC endpoint, it MUST template the corresponding `OIDC.CLIENT.*` value rather than build the URL from `OIDC.URL` itself.
The URL builders in [11_oidc.yml](../../../../group_vars/all/11_oidc.yml) already handle realm and protocol concerns.
## Claim Attributes 🏷️
`OIDC.ATTRIBUTES` lists the claim names that downstream applications read out of the userinfo response and ID token:
| Key | Default value | Meaning |
|---|---|---|
| `OIDC.ATTRIBUTES.USERNAME` | `preferred_username` | Stable user identifier |
| `OIDC.ATTRIBUTES.GIVEN_NAME` | `givenName` | First name |
| `OIDC.ATTRIBUTES.FAMILY_NAME` | `surname` | Last name |
| `OIDC.ATTRIBUTES.EMAIL` | `email` | Email address |
Rules:
- Application roles MUST identify users via `OIDC.ATTRIBUTES.USERNAME`. They
MUST NOT introduce a private claim name (for example `user_id`, `uid`, or
`oauth_uid`) when `preferred_username` already satisfies the requirement.
- Roles MUST NOT hardcode the literal value `preferred_username`. Always
template `{{ OIDC.ATTRIBUTES.USERNAME }}`.
- The standard OIDC `sub` claim is permitted as an alternative when the
application explicitly requires a UUID-shaped identifier and stores its own
mapping table. In that case the role MUST document the deviation in its
README.
- The OIDC username claim is paired with the LDAP login attribute by design.
See [ldap.md](ldap.md) for the LDAP-side rule.
## Keycloak Client Templates 📦
Keycloak client definitions are imported from
[clients](../../../../roles/web-app-keycloak/templates/import/clients/). The
default template in
[default.json.j2](../../../../roles/web-app-keycloak/templates/import/clients/default.json.j2)
is shared by every application client.
Rules:
- The shared `default.json.j2` MUST stay generic. App-specific protocol
mappers, claims, or scopes MUST NOT be added there because the change
affects every other client.
- An app that needs an extra mapper MUST add it through a per-client scope.
See
[nextcloud.json.j2](../../../../roles/web-app-keycloak/templates/import/scopes/nextcloud.json.j2)
for the precedent: a dedicated scope holds the Nextcloud-specific UID and
quota mappers and is attached only to the Nextcloud client.
- New scope files MUST be referenced from the consuming app via its
`KEYCLOAK_RBAC_GROUP_CLAIM` style configuration or via the
`optionalClientScopes` list in the client template.
## Consumer Pattern 🧩
A typical OIDC-consuming role wires the values into its environment template, for example [env.j2](../../../../roles/web-app-mastodon/templates/env.j2):
```jinja
OIDC_ISSUER={{ OIDC.CLIENT.ISSUER_URL }}
OIDC_DISCOVERY=true
OIDC_SCOPE=openid,profile,email
OIDC_UID_FIELD={{ OIDC.ATTRIBUTES.USERNAME }}
OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }}
OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}
```
Every application role SHOULD follow the same shape: discovery on, the `OIDC.CLIENT.*` endpoints templated, the user identifier read from `OIDC.ATTRIBUTES.USERNAME`, and the scopes limited to what the app actually consumes.
## Related Files 📚
| File | Purpose |
|---|---|
| [11_oidc.yml](../../../../group_vars/all/11_oidc.yml) | OIDC SPOT, defines `OIDC.*` |
| [web-app-keycloak](../../../../roles/web-app-keycloak/) | OIDC provider role |
| [default.json.j2](../../../../roles/web-app-keycloak/templates/import/clients/default.json.j2) | Shared client template, MUST stay app-agnostic |
| [scopes](../../../../roles/web-app-keycloak/templates/import/scopes/) | Per-app client scopes for extra mappers |
| [realm.json.j2](../../../../roles/web-app-keycloak/templates/import/realm.json.j2) | Realm definition, including the `preferred_username` mapper |
| [ldap.md](ldap.md) | LDAP-side counterpart, paired user identifier |
| [common.md](common.md) | Cross-cutting IAM principles and integration checklist |