Exchange Check (RFC 8693 token exchange, subject-token read, Authorization injection, 401/403/5xx) #24

Merged
ginjiruu merged 1 commit from feat/exchange-check into master 2026-09-15 02:34:12 +00:00
Owner

Closes #15

Implements the Exchange Flow Check (machine tokens). A service account presents its Kanidm API token; the Bridge exchanges it for OAuth2 tokens via the RFC 8693 grant and hands the exchanged token to the backend.

What it does

internal/serve/exchange.go (new) — the real :8084 gRPC ext_authz Check, replacing the build-surface stub:

  • Resolve the App by Host — no App resolved → 403; secretReady=false / no exchange section / no token endpoint → 5xx (ADR 0011 fail-closed).
  • Read the presented subject token from the configured header/prefix/tokenType (defaults Authorization / Bearer / access_token, matching the TokenExchangeSubjectToken API defaults); missing or malformed (wrong prefix / empty token) → 401. The token is opaque — forwarded, not validated (the token endpoint validates it).
  • Perform the RFC 8693 grant with no client secret (Kanidm rejects it), audience = the App's own clientID, the policy's scopes, subject_token_type from config.
  • Inject the exchanged access_token as Authorization: Bearer <token> (overwrite) and headers_to_remove the incoming subject-token header (the subject token is consumed, not forwarded).
  • Fail-closed: an IdP RFC 8693 rejection (HTTP error status, error field, or no access_token) or an unreachable token endpoint → 5xx. A failed exchange never mis-authorizes.

internal/serve/serve.goexchangeService gains an httpClient; the Exchange stub Check and checkStub are removed (the callback stub remains until its ticket).

Notes

  • The per-replica token cache (ADR 0009's second half) is ticket #18 and is deliberately out of scope here — this Check exchanges per request.
  • Tests (internal/serve/exchange_test.go) cover all 5 acceptance criteria against a token-endpoint double that records the received form (asserting grant_type, client_id, subject_token, subject_token_type, audience, scope, and that client_secret is absent), plus the 401/403/5xx gates and the configured header/prefix/type path.

Review

Two-axis review (Standards + Spec): no hard standard violations; the principal smells are the repo's accepted per-flow parallelism (mirroring oidc.go). All 5 acceptance criteria + the ADR 0011 status model are met; no scope creep.

Verification

  • GOTOOLCHAIN=go1.26.0 make lint — 0 issues.
  • GOTOOLCHAIN=go1.26.0 make test — all packages green (serve 89.3% coverage).
Closes #15 Implements the Exchange Flow `Check` (machine tokens). A service account presents its Kanidm API token; the Bridge exchanges it for OAuth2 tokens via the RFC 8693 grant and hands the exchanged token to the backend. ## What it does `internal/serve/exchange.go` (new) — the real `:8084` gRPC `ext_authz` `Check`, replacing the build-surface stub: - **Resolve the App by Host** — no App resolved → `403`; `secretReady=false` / no exchange section / no token endpoint → `5xx` (ADR 0011 fail-closed). - **Read the presented subject token** from the configured `header`/`prefix`/`tokenType` (defaults `Authorization` / `Bearer ` / `access_token`, matching the `TokenExchangeSubjectToken` API defaults); missing or malformed (wrong prefix / empty token) → `401`. The token is opaque — forwarded, not validated (the token endpoint validates it). - **Perform the RFC 8693 grant** with **no client secret** (Kanidm rejects it), `audience` = the App's own `clientID`, the policy's scopes, `subject_token_type` from config. - **Inject the exchanged `access_token`** as `Authorization: Bearer <token>` (overwrite) and `headers_to_remove` the incoming subject-token header (the subject token is consumed, not forwarded). - **Fail-closed**: an IdP RFC 8693 rejection (HTTP error status, `error` field, or no `access_token`) or an unreachable token endpoint → `5xx`. A failed exchange never mis-authorizes. `internal/serve/serve.go` — `exchangeService` gains an `httpClient`; the Exchange stub `Check` and `checkStub` are removed (the callback stub remains until its ticket). ## Notes - The per-replica token cache (ADR 0009's second half) is **ticket #18** and is deliberately out of scope here — this Check exchanges per request. - Tests (`internal/serve/exchange_test.go`) cover all 5 acceptance criteria against a token-endpoint double that records the received form (asserting `grant_type`, `client_id`, `subject_token`, `subject_token_type`, `audience`, `scope`, and that `client_secret` is **absent**), plus the 401/403/5xx gates and the configured header/prefix/type path. ## Review Two-axis review (Standards + Spec): no hard standard violations; the principal smells are the repo's accepted per-flow parallelism (mirroring `oidc.go`). All 5 acceptance criteria + the ADR 0011 status model are met; no scope creep. ## Verification - `GOTOOLCHAIN=go1.26.0 make lint` — 0 issues. - `GOTOOLCHAIN=go1.26.0 make test` — all packages green (serve 89.3% coverage).
add exchange check: rfc 8693 grant, subject-token read, authorization injection, 401/403/5xx
Some checks failed
Lint / Run on Ubuntu (push) Has been cancelled
E2E Tests / Run on Ubuntu (push) Has been cancelled
Tests / Run on Ubuntu (push) Has been cancelled
Lint / Run on Ubuntu (pull_request) Has been cancelled
E2E Tests / Run on Ubuntu (pull_request) Has been cancelled
Tests / Run on Ubuntu (pull_request) Has been cancelled
9b16bd6b18
The Exchange Flow's Check (issue #15): resolve the App by Host (403 / 5xx), read the presented service-account token from the configured header/prefix/tokenType (401 when missing or malformed), perform the RFC 8693 grant (no client secret; audience = the App's own clientID; the policy's scopes), and inject the exchanged access_token as Authorization while consuming the subject-token header (headers_to_remove). An IdP rejection or an unreachable token endpoint is 5xx (fail-closed).
ginjiruu merged commit 61599e56dd into master 2026-09-15 02:34:12 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lab/authz-bridge!24
No description provided.