Identity#
Who can sign in, and what each person may do (plan2 D21): the two ways an install signs people in, the roles and their permissions, the bundled identity provider (Keycloak), and the break-glass login for when the provider is down.
How people sign in#
One setting, ODS_AUTH in the core stack’s .env (the apps stack reads the same file on a release deploy), chooses between two modes:
Mode |
Who signs in |
Roles |
|---|---|---|
|
The install’s shared login ( |
Everyone behind the shared login is an operator: nothing can tell people apart |
|
Each person, with an OpenID Connect provider: the bundled Keycloak, or one you already run (Entra ID, Okta, Google Workspace, …). oauth2-proxy sits in front of the portal and Dagster and sends anyone not signed in to the provider |
From the person’s groups at the provider |
Either way, the platform trusts only the proxy about who someone is. In oidc mode oauth2-proxy passes the signed-in person to the portal in X-Auth-Request-* headers, and Traefik removes those headers from every request that arrives, before anything else, so nobody can claim to be someone by sending them.
Roles and permissions#
The platform checks permissions, never roles. Five roles come with it, each granted by a group:
Role |
Group |
Permissions |
So they can |
|---|---|---|---|
viewer |
|
|
browse the portal and catalog, read settings |
analyst |
|
+ |
also launch a round-trip proof |
engineer |
|
+ |
also use Dagster’s own UI |
operator |
|
+ |
everything, including rebuild paces |
auditor |
|
|
read the catalog and, as they’re built, the records for audit |
Someone in several groups has every permission of each. Someone in none can sign in and is shown what they’re missing. Controls a person may not use are hidden, and a page that needs a permission they lack says which one.
Dagster has no permissions of its own: whoever can open its UI can launch and stop anything. So only dagster:use reaches it, and the auditor doesn’t get it (Dagster can’t be made read-only).
Using your own groups#
The roles and groups are in apps/portal/roles.yml. To change them for an install, copy that file to the portal’s config directory (shared/portal/roles.yml on a release deploy; onprem/apps/portal/roles.yml beside a checkout) and edit the copy. It replaces the platform’s file whole. The usual change is to point a role at a group your directory already has:
roles:
engineer:
groups: [ods-engineer, data-engineering] # any of these grants the role
permissions: [catalog:read, proof:run, dagster:use]
An unknown role or permission stops the portal at start, with the file and the name, rather than quietly granting or refusing.
Signing in with Keycloak#
The install signs people in with oidc mode: oauth2-proxy sits in front of the portal and Dagster and sends anyone not signed in to an OpenID Connect provider. The provider can be one you already run (see another provider), or the Keycloak that comes with the platform as the optional identity profile.
Keycloak runs in its own keycloak database on the stack’s Postgres, is capped at 1 GB of memory (ODS_KEYCLOAK_MEMORY; it idles around 600 MB), and is served at /auth on the install’s own address, so a LAN install needs no domain for it. On its first start it imports the ods realm (onprem/core/keycloak/ods-realm.json):
the five role groups,
ods-viewer,ods-analyst,ods-engineer,ods-operatorandods-auditor(what each role may do is inapps/portal/roles.yml);the client oauth2-proxy signs in with, which only sends people back to the install’s own address and puts a person’s groups in a
groupsclaim;a password policy (at least 12 characters, not the username or email), and lockout after repeated failures;
no self-registration: people are added by an operator, or come from a directory.
To turn it on, in the core stack’s .env:
Run
onprem/core/scripts/init_traefik.sh. It generates the secrets the mode needs when they’re missing, without printing them:ODS_OIDC_CLIENT_SECRET(shared by oauth2-proxy and the realm),ODS_OIDC_COOKIE_SECRETandODS_KEYCLOAK_ADMIN_PASSWORD.Set
ODS_PUBLIC_URLto the address people type, as their browser sees it, with scheme and port, for examplehttps://192.0.2.10:8443. The host must be one the install’s certificate names (init_traefik.shmakes it for the host’s names and addresses).Set
ODS_AUTH=oidc, addoidc,identitytoCOMPOSE_PROFILES, and start the stack. The apps stack’s portal needsODS_AUTH=oidctoo.
Leave ODS_OIDC_ISSUER and ODS_OIDC_CLIENT_ID unset: they default to the bundled realm.
Sessions: a person stays signed in while they use the platform. Every ODS_OIDC_COOKIE_REFRESH (5 minutes unless set), oauth2-proxy renews the session with Keycloak, so a change of groups applies within that time, and a disabled account, or a Keycloak session ended by an administrator or by 30 minutes without use, is sent back to sign in. Sign out (in the portal’s top bar) ends both the platform’s session and Keycloak’s.
Note
The realm is imported once, on the first start, and an existing realm is never overwritten. Changing ODS_PUBLIC_URL or ODS_OIDC_CLIENT_SECRET later means changing them in Keycloak’s admin console as well: Clients → ods → Valid redirect URIs and Web origins, or Credentials.
The admin console#
Keycloak’s admin console is at <ODS_PUBLIC_URL>/auth/admin/. The first sign-in uses admin (or ODS_KEYCLOAK_ADMIN_USER) and ODS_KEYCLOAK_ADMIN_PASSWORD from .env. That account is temporary, and Keycloak says so: in the master realm, create a permanent administrator for yourself, add it to the admin role, sign in as it, and delete the temporary one. Then remove ODS_KEYCLOAK_ADMIN_PASSWORD from .env: it only ever creates that first account, and an unused secret is better gone (init_traefik.sh generates it again if it’s run once more). People who use the platform belong in the ods realm, never in master.
Administrators and the people who use the platform sign in on different pages: the console’s is the master realm’s (its address has /auth/realms/master/), the platform’s is the ods realm’s (/auth/realms/ods/). They look alike. A master account typed into the platform’s page is refused as an unknown user, and the other way round.
Adding people#
In the admin console, switch to the ods realm, then:
Users → Add user: a username, their email (tick Email verified if you know it’s theirs), and their name.
Join groups: the group of their role, for example
ods-analyst. Someone in no role group can sign in but sees a refusal page.Credentials → Set password: a first password, with Temporary on, so they choose their own when they first sign in.
A person’s role changes when their groups do, from their next sign-in.
Two-factor sign-in#
Two-factor sign-in with an authenticator app is available and off by default. Anyone who sets it up (from their account page, <ODS_PUBLIC_URL>/auth/realms/ods/account/) is asked for a code at every sign-in. To require it:
for one person: their user page → Required user actions → Configure OTP;
for everyone added from now on: Authentication → Required actions → Configure OTP → Set as default action.
Connecting a directory you already have#
People can come from an existing directory instead of being added by hand. Either way, their roles come from groups: either put them in the ods-<role> groups, or point roles.yml at groups your directory already has (copy it to the portal’s config directory and change groups:).
LDAP or Active Directory#
User federation → Add LDAP providers:
Vendor: Active Directory (or your server’s), and its Connection URL (
ldaps://where you can);Bind DN and Bind credentials: a service account that can read users;
Users DN: where the people are;
Edit mode:
READ_ONLY, so the directory stays the authority;then, under Mappers, Add mapper of type
group-ldap-mapperwith the LDAP Groups DN of the groups to bring in. The groups appear in the realm, and a person’s directory groups go into theirgroupsclaim.
Passwords stay in the directory: Keycloak checks them there at each sign-in.
Entra ID, Okta or Google#
Keycloak can send people on to another provider to sign in (“brokering”), keeping the platform’s roles and groups in one place. Identity providers:
Entra ID or Okta: OpenID Connect v1.0, with the provider’s discovery address (Entra:
https://login.microsoftonline.com/<tenant id>/v2.0/.well-known/openid-configuration; Okta:https://<your org>.okta.com/.well-known/openid-configuration), and the client id and secret of an app registration made there.Google: the built-in Google provider, with an OAuth client id and secret from Google Cloud.
Register <ODS_PUBLIC_URL>/auth/realms/ods/broker/<alias>/endpoint as the redirect address at the provider (<alias> is the name the provider has in Keycloak). Then give brokered people their role, under the provider’s Mappers: Hardcoded group (everyone from this provider gets one role), or Advanced claim to group (a claim the provider sends, for example Entra’s groups, mapped to an ods-<role> group). Entra sends group ids, not names, unless the app registration is set to send names.
Another provider#
Keycloak is optional. oauth2-proxy can sign people in with any OpenID Connect provider directly: Entra ID, Okta, Google Workspace, Authentik, or a Keycloak you already run. Leave out the identity profile and set:
ODS_OIDC_ISSUER: the provider’s issuer address;ODS_OIDC_CLIENT_IDandODS_OIDC_CLIENT_SECRET: the client registered there, with<ODS_PUBLIC_URL>/oauth2/callbackas its redirect address;ODS_OIDC_GROUPS_CLAIM, if the provider puts groups in a claim other thangroups;ODS_OIDC_LOGOUT_URL: the provider’s logout address, with{id_token}where the session’s ID token goes, so signing out ends the provider’s session too (for examplehttps://idp.example.com/logout?id_token_hint={id_token}); set it empty if the provider has none;and in
roles.yml, the provider’s group names (or ids) for each role.
The provider must send the person’s groups: without them, everyone signs in with no role. It should also issue refresh tokens (often the offline_access scope, or a setting on the client; add a scope with ODS_OIDC_SCOPE): without them, sessions aren’t renewed, so a change of groups or a disabled account applies only when the person next signs in.
Break-glass#
When the identity provider is down, or misconfigured, nobody can sign in through it. The break-glass login is the operator’s way in regardless: the install’s shared login (ODS_ADMIN_USER and ODS_ADMIN_PASSWORD, from init_traefik.sh) on a port of its own, ODS_BREAKGLASS_PORT (8444 unless set), at the same address: https://<host>:8444/. It doesn’t touch oauth2-proxy or the provider, and it works in either sign-in mode.
Who gets in: whoever has the shared login, as an operator (shown as admin (break-glass)). Keep the password where operators can find it in an emergency, and no one else.
From where: only the networks in
ODS_BREAKGLASS_ALLOW(comma-separated, e.g.127.0.0.0/8,192.168.1.0/24). Unset, that’s the host itself and private networks: loopback,10.0.0.0/8,172.16.0.0/12and192.168.0.0/16. The host’s own requests arrive through Docker’s gateway, which is in172.16.0.0/12, so keep that range (or Docker’s network) if you narrow the list and still want to use it from the host. A request from anywhere else is refused before the login is asked for.What it reaches: the portal and Dagster. Keycloak’s admin console has its own login at
/auth/admin/on the usual port.The record: every request on this port, refused or not, is in Traefik’s access log, as JSON, with the login’s name (
ClientUsername), the address it came from, the path and the answer:/var/log/traefik/access.logon thetraefik-logsvolume. To read it:docker compose exec traefik tail /var/log/traefik/access.log. The usual HTTPS port is not logged there. Settings changed through the portal also record who changed them on the run (ods/requested_by): the person’s email when they signed in with the provider, admin (break-glass) through this login.
Don’t publish the break-glass port to the internet. If the install sits behind a router that forwards ports, forward only ODS_HTTPS_PORT.
When the provider is down#
Signed-in people keep working until their next session renewal (
ODS_OIDC_COOKIE_REFRESH), then are sent to sign in.Signing in shows Sign-in is unavailable (for the bundled Keycloak, which is served by the install itself) instead of the provider’s page.
Operators use the break-glass login, and look at the provider:
docker compose ps keycloakanddocker compose logs keycloak.
Turning on oidc for an install#
A checklist, for an install in basic mode today:
Run
init_traefik.sh(on a release deploy withODS_ENV_FILEandODS_TRAEFIK_SECRETSpointing atshared/) for the new secrets.In
.env:ODS_PUBLIC_URL,ODS_AUTH=oidc, andoidc,identityadded toCOMPOSE_PROFILES(oidconly, with another provider and its settings).Deploy. Keycloak takes about a minute on its first start; until oauth2-proxy has found it, pages answer with an error (Traefik can’t reach the sign-in yet). Use break-glass meanwhile if needed.
In Keycloak’s admin console: your permanent administrator (see the admin console), then the people and their groups, or a directory.
Sign in as each kind of person once. Check break-glass from the host.
To go back, set ODS_AUTH=basic and deploy: the shared login applies again, and Keycloak’s data stays in its database for next time.
Known limits#
Domain mode (
auth.<ODS_DOMAIN>) comes with the platform’s domain mode; for now Keycloak is served at/authon the install’s address.A deploy restarts Keycloak. Each release runs from its own directory, so Compose recreates Keycloak on every deploy, even one that doesn’t change it. People signed in to the platform stay signed in (Keycloak keeps sessions in its database), but the admin console signs you out: avoid deploying while someone is in the middle of administering Keycloak.