Spec: Shared in-memory state model (Registry + Snapshot) for the ext_authz bridge #1
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lab/authz-bridge#1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem Statement
An App team wants to protect its backends (API servers, web servers, dashboards) behind any Gateway API implementation that supports the GEP-1494
ExternalAuthfilter, using per-App authentication and no gateway-specific config. The gateway'sExternalAuthfilter calls out to the Bridge overext_authz; for the Bridge to answer those callouts it must know, per request: which App the request belongs to, that App's resolved IdP configuration (token/authorization endpoints, signing keys), its client-secret reference, and its per-Flow policy behaviour.Today there is no in-cluster mechanism to turn the App team's CRDs (
OauthClient,OidcPolicy,TokenExchangePolicy) into that resolved, validated, ready-to-use, per-App state, and to make it consistently available to every replica that serves traffic — without each replica redundantly re-resolving IdP config, re-reading Secrets, or the operator hand-wiring gateway config.Solution
The shared in-memory state model, built and distributed by the operator's controller:
OauthClient(clientID, Kanidm issuer URL, client-secret reference,callbackURLs,hostnames) and one policy per Flow it runs (OidcPolicy,TokenExchangePolicy). A Flow is active iff its policy object exists.kubectl get secret) that shows exactly what is being served, 1× IdP load, secrets staying in the per-App Secrets, and last-good state surviving failover.User Stories
OauthClientwith its clientID, Kanidm issuer URL, client-secret reference, callback URLs, and hostnames, so that I protect my App's backends without any gateway-specific config.OidcPolicyto enable the human/browser OIDC Flow for my App, so that users can sign in and I can inject their identity into my backend.TokenExchangePolicyto enable the machine Exchange Flow (RFC 8693) for my App, so that service accounts can exchange their API token for tokens without user interaction.hostnamesto be the single key that maps an incoming request to my App, so that the bridge resolves "which App is this request for" from the request'sHostheader.clientRefpoints to a non-existent Client to be excluded (structurally invalid), so that broken config never silently serves a half-configured App.secretReady=false(served as 5xx) rather than removing it, so that a broken config surfaces as "unavailable," not a false "forbidden."secretReady=trueand resume normal operation, so that a transient Secret outage self-heals.kubectl get secreton the managed Registry to see exactly which resolved Apps every replica is serving, so that I can debug what the bridge is doing without reading process memory.access_tokentype) to the token endpoint rather than validated in-bridge, so that the bridge does not hold the HMAC secret and Kanidm remains the validator.scopesto default toopenid, so that a minimal policy still requests an identity token.audience) to be implicitly the Client's own clientID, so that machine tokens land in the App's own audience with no extra config.Implementation Decisions
Modules built/modified
Leader/replica split
Registry (managed Secret) schema
namespace,clientID,hostnames(also the hostname-index keys)tokenEndpoint(one resolved endpoint used by both Flows),jwks(raw JWKS JSON),secretRef {namespace, name, key},secretReadyOidcPolicy):authorizationEndpoint,callbackURLs, and the policy config (scopes, session, injection, logout)TokenExchangePolicy):clientRef,scopes, and the subject-token source config (header, prefix, token type)Structural validation gate (Registry membership)
clientRefresolves to an existingOauthClientin the same namespace;clientSecretRefis well-formed (has a name).secretReadyproperty.Failure model (runtime degradation)
secretReady=false, CRD Degraded, served as 5xx (fail-closed, "unavailable"). On recreation →secretReady=true, normal operation. Never removed.Secret handling (reference-and-watch)
secretRef; every replica runs an informer over the referenced per-App Secrets. The per-replica "bytes present" (→ the 5xx flag) is derived from the local watch, not the leader's flag.JWKS refresh
Snapshot (visibility mechanic — Way A)
IdP resolve (discovery + JWKS)
issuerURL→token_endpoint,authorization_endpoint, and the JWKS. The token endpoint is shared across Flows; the authorization endpoint is OIDC-only.Exchange subject token
access_tokentype) to the token endpoint; the bridge does not validate it (no HMAC secret held); read from thesubjectTokenconfig already in the CRD; no client secret sent. (The state model carries the config + subject-token source; the actual RFC 8693 grant POST is the gRPC-server follow-on.)CRD status
metav1.Conditionstatus —Available(in the Registry,secretReady) orDegraded(structurally invalid, orsecretReady=false) — withobservedGeneration(the existing CRD status already carries aConditionsfield).Testing Decisions
What makes a good test
Modules tested
config/crd/bases), with the IdP stood in by anhttptestserver.httptesttest double): the discovery-doc / JWKS fetch and the keep-last-good-on-failure refresh, against a stub IdP that can be made to fail. The refresh timer is exercised through Seam 1; the keep-last-good decision has a focused unit test.Prior art
suite_test.go(envtest + Ginkgo/Gomega) is the Seam 1 substrate; the stub*_controller_test.goare the placeholders to fill.Seam count: one integration seam (Seam 1), with the snapshot builder and schema as pure unit tests and the IdP as a test double.
Out of Scope
ext_authzserver and itsCheckhandling: OIDC 302/redirect + code-for-token +id_tokenvalidation + header injection; the Exchange RFC 8693 token call (the actual grant POST, theaudience/resource/requested_token_typehandling, the response). These consume the Snapshot.:8082).ReferenceGrantmanifests.ext_authzproto dependency (not yet ingo.mod).secretReady→ 5xx surfacing by the gRPC server (the state model provides the flag + semantics; the server acts on it).Further Notes
docs/references/kanidm-service-account-token-exchange.md; read it before working on the Exchange Flow.Closing: the spec's build scope is complete. All four tickets are delivered and merged to master:
The spec #11 follow-on amendment (resolve + carry
end_session_endpoint, ticket #13) is also merged — PR #22.Build scope complete (PRs #7-#10 merged, plus the #11 amendment PR #22); spec delivered.