* fix(oidc): ignore groups without a matching policy at login OIDC login failed with "OIDC policy mapping did not resolve to current policies" whenever any mapped group lacked a policy of the same name. Directory-backed providers such as Active Directory always emit groups like `DOMAIN\Domain Users` that can never be mapped, so group-based authorization was impossible there. Keep only policy names that resolve to an existing policy and are valid in session claims, and reject the login only when none remain. The signed `policy` claim still lists only resolved names, so request-time evaluation and site replication receivers keep their invariant. Refs #8163 * fix(oidc): only ignore unmapped groups-claim values Limit the relaxed resolution to policy names derived solely from the groups claim. Names from ROLE_POLICY, a dedicated CLAIM_NAME claim, or an explicit IAM policy mapping must still all resolve. Reject the login when a mapped name refers to an existing policy whose name is not allowed in session claims: dropping it could remove an explicit Deny and broaden access. Claim-unsafe names are only checked against the in-memory policy cache and never passed to storage-backed policy loading. Refs #8163 * docs(oidc): document ignorable roles claim values and cover wiring Roles claim values are merged into the canonical groups, so values without a matching policy are ignored like groups-claim values. This covers built-in provider roles such as Keycloak's `offline_access`. Document that in the field and method docs and in the provider requirements, and pin it with a test. Assert that the OIDC authorization carries the group claim policies so a regression in the wiring cannot silently disable the relaxation. Refs #8163 * fix(oidc): ignore group policies only after confirmed absence merge_policies drops names whose load fails, so a storage or decode error for an uncached group policy was indistinguishable from a missing one. The group name was then ignored and the session signed without it, which could remove an existing Deny that request-time checks cannot restore. Add IamSys::policy_exists, which consults the cache and then storage and reports false only for NoSuchPolicy while returning every other error. The OIDC binding now ignores a group-derived name only after a confirmed absence and rejects the login when a lookup fails. Refs #8163 --------- Co-authored-by: cxymds <cxymds@gmail.com> Co-authored-by: Chris <anzhengchao@gmail.com>
8.0 KiB
OIDC provider requirements
Use this when: evaluating whether an identity provider (or a vendor's "OIDC-like" OAuth product) can back RustFS console SSO, or debugging why a provider fails discovery, login, or policy mapping.
Source of truth: crates/iam/src/oidc.rs (OidcProviderConfig, discover_provider, trusted_aud, map_claims_to_policies); crates/config/src/constants/oidc.rs (OIDC_DEFAULT_*, ENV_IDENTITY_OPENID_*); rustfs/src/admin/handlers/oidc.rs (derive_callback_uri_with_provider_config, browser_redirect_url).
RustFS is a standard OpenID Connect relying party using the authorization-code flow with PKCE. It reads every claim it uses from the ID token; it never calls the UserInfo endpoint. Provider-side setup steps for Keycloak and Authing, and the RustFS-side configuration keys, are in oidc-console-integration.md.
Requirements
The table below describes Console login. For endpoint-free STS issuers, use the workload contract below.
| # | Requirement | Details | Code anchor |
|---|---|---|---|
| 1 | Discovery document | RUSTFS_IDENTITY_OPENID_CONFIG_URL names the provider (issuer base or full discovery URL); RustFS fetches {issuer}/.well-known/openid-configuration and needs issuer, authorization_endpoint, token_endpoint, jwks_uri, and the standard *_supported arrays. When RUSTFS_IDENTITY_OPENID_ISSUER is set, the document's issuer must equal it exactly; otherwise RustFS tries the issuer candidates derived from the config URL. |
crates/iam/src/oidc.rs discover_provider, discover_provider_from_config_url |
| 2 | Outbound reachability | Discovery, JWKS, and token requests go through the shared egress policy. A provider on a private or loopback address needs its exact origin in RUSTFS_OUTBOUND_ALLOW_ORIGINS; otherwise startup logs OIDC provider discovery blocked by outbound policy. |
crates/iam/src/oidc.rs OIDC_DISCOVERY_BLOCKED_BY_OUTBOUND_POLICY; Outbound connection policy |
| 3 | Signed ID token, verifiable via JWKS | The ID token signature is verified against jwks_uri; the key set is refreshed after OIDC_JWKS_REFRESH_INTERVAL and once more on a verification failure. A token response without id_token fails login — an OAuth-only access token is not sufficient. |
crates/iam/src/oidc.rs OIDC_JWKS_REFRESH_INTERVAL, the no id_token in token response error |
| 4 | Authorization-code flow with PKCE (S256) | The authorization request carries response_type=code, scope, redirect_uri, state, nonce, code_challenge, code_challenge_method=S256; the token request carries the code_verifier. Providers that ignore or reject PKCE, state, or nonce are not supported. |
crates/iam/src/oidc.rs PkceCodeChallenge::new_random_sha256; crates/iam/src/oidc_state.rs pkce_verifier |
| 5 | Scopes | Default openid,profile,email (OIDC_DEFAULT_SCOPES); override with RUSTFS_IDENTITY_OPENID_SCOPES (comma-separated). The provider must accept every configured scope. |
crates/config/src/constants/oidc.rs OIDC_DEFAULT_SCOPES; crates/iam/src/oidc.rs OidcProviderConfig::scopes |
| 6 | Callback returns state |
The callback must carry both code and the original state; state locates the in-flight session that holds the nonce and PKCE verifier. A callback with only code fails. |
rustfs/src/admin/handlers/oidc.rs OIDC_CALLBACK_SUFFIX; crates/iam/src/oidc_state.rs |
| 7 | ID token claims | iss, aud, exp, and nonce are verified; sub identifies the user. aud must contain the client id or one of RUSTFS_IDENTITY_OPENID_OTHER_AUDIENCES. |
crates/iam/src/oidc.rs trusted_aud, OidcClaims |
| 8 | Authorization claims in the ID token | Policy mapping reads the claim named by RUSTFS_IDENTITY_OPENID_CLAIM_NAME (default groups, OIDC_DEFAULT_CLAIM_NAME) together with RUSTFS_IDENTITY_OPENID_GROUPS_CLAIM and the optional RUSTFS_IDENTITY_OPENID_ROLES_CLAIM, as a string or array of strings, optionally prefixed by RUSTFS_IDENTITY_OPENID_CLAIM_PREFIX. Values must equal RustFS policy names (for example consoleAdmin, readwrite, readonly) made only of [A-Za-z0-9_:.-]. Groups-claim and roles-claim values with no policy of that name, such as the Active Directory group DOMAIN\Domain Users or Keycloak's offline_access role, are ignored once storage confirms that no such policy exists. Login is rejected when a policy lookup fails, when no value resolves, when a value names an existing policy whose name is outside that character set (dropping it could remove a Deny), or when a name from RUSTFS_IDENTITY_OPENID_ROLE_POLICY, a separate RUSTFS_IDENTITY_OPENID_CLAIM_NAME claim, or an explicit IAM policy mapping does not exist. Email and username come from RUSTFS_IDENTITY_OPENID_EMAIL_CLAIM (default email) and RUSTFS_IDENTITY_OPENID_USERNAME_CLAIM (default preferred_username). A provider that returns only a user id can authenticate but cannot express RustFS authorization. |
crates/iam/src/oidc.rs map_claims_to_policies, extract_canonical_group_values; OidcSys::group_claim_policy_names; rustfs/src/admin/service/federated_identity.rs select_oidc_policies |
| 9 | Registered redirect URI | The provider must accept the callback {public-origin}/rustfs/admin/v3/oidc/callback/{provider_id}. RustFS picks the origin in this order: the provider's redirect_uri (RUSTFS_IDENTITY_OPENID_REDIRECT_URI), then RUSTFS_BROWSER_REDIRECT_URL, then the request's own scheme and host — the last only when RUSTFS_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC is enabled. |
rustfs/src/admin/handlers/oidc.rs derive_callback_uri_with_provider_config, browser_redirect_url |
| 10 | Logout endpoint (optional) | When discovery advertises end_session_endpoint, RustFS builds an RP-initiated logout URL with id_token_hint, client_id, and post_logout_redirect_uri. Without it, logout falls back to the console login page. |
crates/iam/src/oidc.rs build_logout_url (reads end_session_endpoint from ProviderMetadataWithLogout) |
STS workload providers
For workload identity tokens exchanged through AssumeRoleWithWebIdentity (for example Kubernetes service-account tokens), set RUSTFS_IDENTITY_OPENID_HIDE_FROM_UI=on (hide_from_ui=on in persisted configuration, or hide_from_ui: true in the admin JSON API) when discovery omits authorization_endpoint. The default is off; without this setting, RustFS applies the Console discovery contract and rejects the missing endpoint.
Endpoint-free workload discovery requires issuer, jwks_uri, and id_token_signing_alg_values_supported. token_endpoint is optional; browser authorization endpoints, response_types_supported, and subject_types_supported are not required for this path. An explicitly null, empty, or malformed authorization_endpoint is rejected rather than treated as absent. Configuration validation returns authorization_endpoint: null for an accepted workload-only provider.
Set RUSTFS_IDENTITY_OPENID_CONFIG_URL to the issuer/discovery URL and RUSTFS_IDENTITY_OPENID_CLIENT_ID to the intended token audience. Configure policy claims or RUSTFS_IDENTITY_OPENID_ROLE_POLICY to grant the required RustFS permissions. Signature, issuer, audience, and expiration verification remain enforced, and private endpoints still require the outbound allowlist. JWKS requests accept both application/json and application/jwk-set+json.
Workload-only providers cannot perform Console authorization-code login. Hiding a provider with complete discovery metadata only hides its listing; its existing direct Console login remains available.
Deployment notes
- Behind a load balancer, authorize and callback requests must reach the same RustFS node while the
stateis in flight, or setRUSTFS_BROWSER_REDIRECT_URLso the callback URL is stable; the callback error text names both remedies. - Provider-specific setup (client registration, claim mappers, redirect-URL priority) is covered by the console integration guide for the provider in use; this page states only what any provider must offer.