Repository navigation
ADR 0026 - Token Exchange: OP as STS (RFC 8693, v2) #945
Description
Umbrella for ADR 0026 §12 "Token Exchange (RFC 8693, v2)". Part of #939.
Goal
Use the RFC 8693 urn:ietf:params:oauth:grant-type:token-exchange grant on POST /v4/oauth2/{domain_id}/token to make the OP a token service (STS) for two strategic uses, and keep the already-shipped Keystone-native path as a frozen, migration-only legacy:
| Use | Subject token | Status |
|---|---|---|
| Ingress from external IdPs | external IdP JWT, run through the mapping engine | #1018 |
| On-behalf-of delegation / audience narrowing | the user's token as a service received it (OP JWT, and Fernet/JWS during migration) | ADR 0036 §6 (Proposed, not built) |
| Legacy migration bridge | Fernet/JWS token carrying an application-credential chain | built; frozen, sunset with Fernet (ADR 0026 §13 Stage 5) |
Rationale: ADR 0026 §13 Stage 3 moves machine identities from application credentials to OAuth2Clients, Stage 5 sunsets Fernet, and ADR 0036 supersedes trusts. Investing in new features for Fernet/app-cred/Trust/EC2 subjects would harden something scheduled to disappear. Fixes needed by every subject kind, and defects in the shipped path, are still in scope.
Current state (what is built)
- Dispatch at
crates/keystone/src/api/v4/oauth2/token.rs:329, handlerhandle_token_exchange_grant(token.rs:961): per-client rate limit, client authentication, requiresGrantType::TokenExchangein the client'sgrant_types, validatessubject_tokenviaTokenApi::validate_to_context, signs a native JWT, emits a CADFauthenticateevent. crates/core/src/oauth2_client/token_exchange.rs: derivesDelegationContext::AppCred { project_id }from the authentication chain (security.md I1/I2), enforces same-domain (CrossDomainSubjectToken),keystone_ruleset_version = 0sentinel.- Application credentials only. Trust and EC2 subjects are rejected (
UnsupportedDelegation); plain password/token chains are rejected (NotDelegated). This is intentionally not extended (see oauth2: Decide scope of token-exchange subject kinds and revocation #1357). - Tests: 5 handler unit tests (
token.rs~2054-2357), 4 core unit tests, 3 raft integration tests (tests/integration/src/oauth2_token_exchange.rs, incl. the cross-domain regression guard).
Work items
Fixes to shipped code and groundwork shared by every subject kind:
- Exchanged token outlives its subject.
exp = now + access_lifetime, never capped by the subject or app-cred expiry → oauth2: Cap token-exchange lifetime to subject and app-cred expiry #1353 - RFC 8693 conformance.
subject_token_type/requested_token_typeignored (token.rs:85-97), noissued_token_type(REQUIRED, §2.2.1),scopehard-coded,audience/resource/actor_tokensilently ignored → oauth2: RFC 8693 request/response conformance for token exchange #1354 - Shared subject-token validation profile.
verify.rs:202-205hard-codesopenstack-apis:{domain_id}; OP-issued JWTs cannot be subjects. Prerequisite for Implement token exchange using the mapping engine #1018 and ADR 0036 §6 → oauth2: Shared subject-token validation profile for token exchange #1355
Strategic uses:
- External-IdP subject tokens via the mapping engine → Implement token exchange using the mapping engine #1018 (exit criteria and CI demonstration are defined on that issue; see its comment for the surfaces shared with ADR 0036 §6.4)
- On-behalf-of exchange → ADR 0036 §6 (implementation issues to be opened once that ADR is accepted)
Demonstration in CI (external-IdP exchange):
- Keycloak JWT exchange in k3s (functional + negative suite) → test(oauth2): Keycloak JWT token exchange in k3s #1366
- GitHub Actions OIDC JWT exchange (real workflow token, same-repo runs) → test(oauth2): GitHub Actions OIDC token exchange test #1367
- GitHub tests fail instead of silently skipping when the JWT is required → test(federation): Fail instead of skipping without GitHub JWT #1368
Scope, docs and records:
- Decisions: legacy vs. goal for Fernet/app-cred subjects, Trust/EC2 (won't do), revocation /
root_jtifor exchanged tokens, ordering of Implement token exchange using the mapping engine #1018 vs. ADR 0036 §6 → oauth2: Decide scope of token-exchange subject kinds and revocation #1357 - ADR amendments for ADR 0026 §12, §13 Stage 4 and the §1 "Circular Token Exchange Trap" note → docs(adr): Amend ADR 0026 §12 with token-exchange scope #1365
- Docs, discovery and live tests for the shipped grant, presented as a migration bridge → oauth2: Document and live-test the token-exchange grant (legacy bridge) #1356
Related
- ADR 0026 §12/§13 (
doc/src/adr/0026-oauth2-oidc-provider.md), ADR 0036 §6 on-behalf-of exchange, security modeldoc/src/contributor/security-model.md. - OP readiness audit:
doc/src/contributor/oauth2-production-readiness.md. - Revocation machinery: oauth2: implement RFC 7009 token revocation endpoint POST /v4/oauth2/{domain_id}/revoke #1258 (
/revoke), oauth2-session: revoke refresh families on user disable/delete, domain disable/delete, and password change #1259 (lifecycle hooks). Bearer JWT acceptance on Keystone's own API: auth: accept OP-issued OpenStack access tokens (Authorization: Bearer JWT) on Keystone's own v3/v4 API #1266. Discovery: oauth2 discovery: wrong device-grant identifier and missing RFC 8414/8628/7636 metadata #1268.
Done when
All sub-issues are closed, ADR 0026 §12/§13 and ADR 0036 §6 agree on supported subject kinds and revocation semantics, the external-IdP exchange is demonstrated against Keycloak and GitHub in CI, and the shipped grant is documented as legacy and covered by a live API test.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Activity
feat(adr0026): Phase 6a
feat(adr0026): Phase 6a
feat(adr0026): Phase 6a (#1017)
[-]ADR 0026 - Token Exchange & Delegation (RFC 8693, v2)[/-][+]ADR 0026 - Token Exchange: OP as STS (RFC 8693, v2)[/+]