Single sign-on (SAML)
OrbitalReg is a SAML 2.0 Service Provider (SP). Any SAML 2.0 IdP works; this section provides tested, step-by-step guides for the four most common self-hosted / enterprise IdPs, plus the Azure Entra ID default:
- authentik
- Okta
- Keycloak
- AD FS
- Azure Entra ID — the chart default; see the inline comments in
charts/orbitalreg/values-trial-saml.yaml.
Each guide covers provider creation, attribute/property mappings, the POST binding requirement, request-signing verification certificates, a ready-to-paste Helm values snippet (also shipped as charts/orbitalreg/values-<idp>.yaml.example), and a troubleshooting table built from real rollout findings.
How the SP side works
- Endpoints. The SP mounts at
/saml/on yourrootURL:/saml/metadata(SP metadata XML) and/saml/acs(Assertion Consumer Service, POST only). - Metadata-driven. At startup the API fetches
saml.idpMetadataURLand takes the IdP's SSO endpoints and signing certificate from it. If the fetch fails, the pod logssaml init failedand crashes — fix the URL/TLS/DNS first. - Signed AuthnRequests. OrbitalReg signs its login requests (
SignRequest: true) with the SP keypair (chart-generated self-signed by default, or your own viasaml.existingSecret/saml.certPEM+saml.keyPEM). Some IdPs (authentik) must be given the SP public certificate to verify these requests. - Identity = email. The NameID / email claim identifies the user. Sessions are HMAC-signed cookies (8 h TTL); groups are baked into the session at login, so mapping changes require a full re-login.
Claims and the IdP presets
The SP reads up to four claims from the assertion: email (identity), display name, groups (multi-valued, mapped to roles), and an optional role claim. Which attribute names it looks for is controlled by saml.idpPreset — each preset matches that IdP's default property mappings / attribute statements, so you don't have to clone another vendor's claim URIs into your IdP:
| Preset | Display name | Groups | Role | |
|---|---|---|---|---|
entra (default) | xmlsoap …/emailaddress URI | xmlsoap …/name URI | MS 2008/06 …/groups URI | MS 2008/06 …/role URI |
authentik | xmlsoap …/emailaddress URI | xmlsoap …/name URI | xmlsoap …/claims/Group URI | — |
okta | email | name | groups | — |
keycloak | urn:oid:1.2.840.113549.1.9.1 | urn:oid:2.16.840.1.113730.3.1.241 | member | Role |
adfs | xmlsoap …/emailaddress URI | xmlsoap …/name URI | xmlsoap …/claims/Group URI | MS 2008/06 …/role URI |
The full URIs are listed on each IdP's guide page. If your IdP emits custom attribute names, override any single field with saml.claimEmail / claimName / claimGroups / claimRole (env: ORBITALREG_SAML_CLAIM_*) — a non-empty override beats the preset for that field only.
From groups to roles
Group (and role-claim) values from the assertion are compared against auth.adminGroups / auth.developerGroups / auth.readerGroups. No match → default reader, which sees nothing until a project binding exists (see Tenancy model). Two practical consequences:
- The strings must match exactly what your IdP emits — display names for authentik/Keycloak/Okta, object-ID GUIDs for Entra.
- The lists also accept a login email address. That is the universal fallback: put your own email into
auth.adminGroups, redeploy, re-login — you areorg_admineven while group mapping is still broken, and can debug in peace.
Verify with https://<domain>/auth/whoami — check the groups and roles arrays after a complete logout + login.
Troubleshooting essentials (all IdPs)
| Symptom | Cause | Fix |
|---|---|---|
Pod crashes, saml init failed in log | SP cannot fetch idpMetadataURL (TLS, DNS, wrong path) | curl the URL from the pod network; use the IdP's copy-URL button |
| "response code null" after clicking login | IdP metadata contains internal SSO URLs (metadata was fetched via an internal address) | Point idpMetadataURL at the IdP's public URL |
| HTTP 405 after IdP login | IdP sends the SAML response via Redirect (GET) binding — the ACS accepts only POST | Set the SP/ACS binding to POST on the IdP side |
404 on /saml/acs | ACS URL has a trailing slash or wrong path — the SP compares exactly | ACS URL exactly https://<domain>/saml/acs |
405 when opening /saml/acs in a browser | Normal — GET on a POST-only endpoint | Not an error |
Login works but roles: reader | Group values don't match auth.*Groups, or groups claim missing | Check /auth/whoami; see the per-IdP guide's mapping section; email fallback above |
groups empty in whoami | Mapping not attached to the provider, wrong attribute name/preset, or stale session | Fix mapping/preset; log out fully and back in |
Operational monitoring for a down IdP is covered by the OrbitalRegSAMLDown runbook.