RBAC.md (alerts:read → monitoring:read): The example team-setup table told operators to issue API tokens with an "alerts:read" scope. That scope does not exist in pkg/auth/scopes.go; defined scopes are monitoring:read, settings:read, etc. /api/alerts/ is gated by RequireAuth (no specific scope required), so an integrator issuing a token would naturally pick the closest real scope — monitoring:read — and that is what the doc should have shown. OIDC.md (OIDC_GROUP_ROLE_MAPPINGS, OIDC_CA_BUNDLE): Both env vars were documented but zero code reads them. OIDC config is per-provider in internal/config/sso.go and OIDCProviderConfig in internal/config/oidc.go: groupRoleMappings is a map field; caBundle is a path field. Replace both env-var snippets with the actual UI/API path so operators following the secure-install flow don't silently get no group mapping or no custom CA trust. Same drift pattern as the earlier rc.1 → rc.5 PULSE_RELAY_* aspiration-without-implementation. WEBHOOKS.md (missing helpers): notifications.go's templateFuncMap registers jsonString and pathescape on every webhook template, but the helper list only documented title / upper / lower / printf / urlquery / urlencode / urlpath. Add both, with a short note that jsonString is the safe way to embed arbitrary string values inside a JSON payload — Pulse's shipped templates use it everywhere a value goes inside JSON, and operators writing custom templates were missing the canonical escape primitive. KUBERNETES.md (helm path + markdown fence): - "deployment.strategy.type=Recreate" was the wrong helm path. The chart's strategy block is at the top level (deploy/helm/pulse/values.yaml line 9), so `strategy.type=Recreate` is what operators must actually --set. Following the broken path produced no override and left RWO PVC deployments on the default RollingUpdate, the exact Multi-Attach failure mode the note was trying to warn against. - Trailing ```text on the helm-template code block closed the fence but tagged it as a language, breaking markdown rendering in some readers. Reduced to plain ```. All four are doc-only changes; no code reads the names they document.
6 KiB
🔐 OIDC Single Sign-On
Enable Single Sign-On (SSO) with providers like Authentik, Keycloak, Okta, and Azure AD.
🚀 Quick Start
- Configure Provider: Create an OIDC application in your IdP.
- Redirect URI:
https://<your-pulse-domain>/api/oidc/<provider-id>/callback - Scopes:
openid,profile,email
- Redirect URI:
- Enable in Pulse: Go to Settings → Security → Single Sign-On.
- Enter Details:
- Issuer URL: The base URL of your IdP (e.g.,
https://auth.example.com/application/o/pulse/). - Client ID & Secret: From your IdP.
- Issuer URL: The base URL of your IdP (e.g.,
- Save: The login page will now show your configured SSO provider button(s).
Tip: To hide the username/password form and only show the SSO button, set
PULSE_AUTH_HIDE_LOCAL_LOGIN=truein your environment. You can still access the local login by appending?show_local=trueto the URL (e.g.,https://your-pulse-instance/?show_local=true).
⚙️ Configuration
| Setting | Description |
|---|---|
| Issuer URL | The OIDC provider's issuer URL. Must match the iss claim in tokens. |
| Client ID | The application ID from your provider. |
| Client Secret | The application secret. |
| Redirect URL | Auto-detected. Override only if running behind a complex proxy setup. |
| Scopes | Space-separated scopes. Default: openid profile email. |
| Claim Mapping | Map email, username, and groups to specific token claims. |
Note
: Setting
OIDC_*environment variables locks those fields in the UI. See CONFIGURATION.md for the full list of overrides.
Access Control
Restrict access to specific users or groups:
- Allowed Groups: Only users in these groups can login. Requires the
groupsscope/claim. - Allowed Domains: Restrict to specific email domains (e.g.,
example.com). - Allowed Emails: Allow specific email addresses.
Group-to-Role Mapping (Pro and Above)
Automatically assign Pulse roles based on OIDC group membership. When a user logs in, Pulse checks their groups claim and assigns the corresponding roles.
Configuration: Group-role mappings are configured per SSO provider through the UI (or the SSO provider API for automated setup) — there is no environment-variable override. Go to Settings → Security → Single Sign-On, edit the provider, and populate Group Role Mappings with entries like:
oidc-admins→adminoidc-operators→operatoroidc-viewers→viewer
The mappings persist on the provider record as a groupRoleMappings JSON
field. Provider-level config (including this field) can be PUT through the
SSO provider API for automated setup.
How it works:
- On each login, Pulse reads the user's groups from the configured groups claim.
- For each group that matches a mapping, the corresponding role is assigned.
- Multiple groups can map to multiple roles (user gets all matching roles).
- Role assignments are updated on every login to reflect current group membership.
- Role changes are logged to the audit log for compliance tracking.
Example:
If a user has groups ["oidc-admins", "developers"] and you have mappings:
oidc-admins→admindevelopers→operator
The user will be assigned both admin and operator roles.
Note
: Ensure your IdP includes the
groupsscope and that the groups claim is properly configured. Some providers usegroups, others userolesor custom claims.
Long-Lived Sessions with offline_access
For persistent sessions that don't require frequent re-authentication:
- Add
offline_accessscope: Includeoffline_accessin your OIDC scopes (e.g.,openid profile email offline_access). - Configure your IdP: Ensure your identity provider issues refresh tokens when
offline_accessis requested.
How it works:
- When you login with
offline_access, Pulse stores the refresh token alongside your session. - When your access token expires, Pulse automatically refreshes it using the stored refresh token.
- Your session remains valid as long as the refresh token is valid (typically 30-90 days depending on your IdP).
- If the IdP revokes access (user disabled, token revoked), Pulse detects this on the next refresh attempt and logs you out.
Security considerations:
- Refresh tokens are stored encrypted at rest.
- If the IdP configuration changes, existing sessions with mismatched issuers are automatically invalidated.
- Failed refresh attempts immediately invalidate the session.
📚 Provider Examples
Authentik
- Type: OAuth2/OpenID (Confidential)
- Redirect URI:
https://pulse.example.com/api/oidc/<provider-id>/callback - Signing Key: Must use RS256 (create a certificate/key pair if needed).
- Issuer URL:
https://auth.example.com/application/o/pulse/
Keycloak
- Client ID:
pulse - Access Type: Confidential
- Valid Redirect URIs:
https://pulse.example.com/api/oidc/<provider-id>/callback - Issuer URL:
https://keycloak.example.com/realms/myrealm
Azure AD
- Redirect URI:
https://pulse.example.com/api/oidc/<provider-id>/callback(Web) - Issuer URL:
https://login.microsoftonline.com/<tenant-id>/v2.0 - Note: Enable "ID tokens" in Authentication settings.
🔧 Troubleshooting
| Issue | Solution |
|---|---|
invalid_id_token |
Issuer URL mismatch. Check logs (LOG_LEVEL=debug) to see the expected vs. received issuer. |
unexpected signature algorithm "HS256" |
Your IdP is signing with HS256. Configure it to use RS256. |
| Redirect Loop | Check X-Forwarded-Proto header (must be https) and cookie settings. |
| Self-Signed Certs | Set the CA Bundle field on the SSO provider to a host path readable by Pulse (e.g. /etc/ssl/certs/oidc-ca.pem mounted into the container). The field is stored on the provider record as oidc.caBundle; there is no OIDC_CA_BUNDLE env var. |
Debugging
Enable debug logs to trace the OIDC flow:
export LOG_LEVEL=debug
# Restart Pulse
Logs will show discovery, token exchange, and claim parsing details.