feat: publish OIDC discovery metadata so clouds can federate to ZeroID (ADR 0028 D8) - #313
Conversation
…D (ADR 0028 D8)
AWS IAM OIDC identity providers, Azure federated identity credentials and
GCP workload identity pools all discover an external issuer by fetching
/.well-known/openid-configuration. All three were written against OpenID
Connect Discovery, not RFC 8414, so ZeroID publishing only
/.well-known/oauth-authorization-server left it undiscoverable to every one
of them — even though the keyset they would verify against was already
public at /.well-known/jwks.json.
That gap was the whole of "make our identity useful in someone else's
cloud": the blocker was a missing discovery document, not missing crypto.
The new endpoint serves the RFC 8414 document plus the two members OIDC
Discovery §3 makes REQUIRED that RFC 8414 §2 does not define:
- subject_types_supported: ["public"]. ZeroID `sub` is the stable identity
ID, identical for every verifier. A pairwise subject would break the
cloud role trust policies that pin a literal `sub`.
- id_token_signing_alg_values_supported: derived from the live keyset
rather than hardcoded. A verifier that trusts an alg we no longer sign
with fails at token-verification time in someone else's cloud, which is
the most expensive place to debug it.
Superset rather than a second document because two hand-maintained
documents describing one AS is how they drift; buildASMetadata is extracted
for both and returns a fresh map per call, so OIDC-only members cannot leak
backwards into the RFC 8414 document.
ZeroID is NOT an OpenID Provider and issues no id_token. The structural
guard is response_types_supported, which carries at most "code" and never
an id_token response type, so no relying party can request one whatever it
discovers here. TestOIDCDiscovery_NeverAdvertisesIDToken asserts that
absence and should be deleted by the PR that makes the claim true, not
before.
Both non-obvious invariants were mutation-tested: hardcoding a wrong alg
list and caching the base map each produce a failing test.
There was a problem hiding this comment.
🔮 Oracle Review
🎯 Start Here
internal/handler/wellknown.go (~31 min) — Security changes in wellknown.go
📋 PR Summary
What this PR does: Implements OpenID Connect Discovery metadata endpoint (/.well-known/openid-configuration) to enable AWS, Azure, and GCP cloud federation with ZeroID, extending the existing RFC 8414 authorization server metadata as a superset.
Key changes:
- Added GET /.well-known/openid-configuration endpoint with OIDC-required members (subject_types_supported, id_token_signing_alg_values_supported)
- Extracted buildASMetadata function to share logic between RFC 8414 and OIDC Discovery endpoints, preventing OIDC-only members from leaking
- Derives supported signing algorithms dynamically from live JWKS keyset (ES256/RS256 based on loaded keys)
- Added mutation-verified tests ensuring OIDC members don't leak and signing algorithms match published JWKS
- Compliance suite updated with OIDC Discovery 1.0 specification tests following COMPLIANCE.md conventions
Areas affected: internal/handler/wellknown.go, OIDC Discovery endpoint, Cloud federation integration
Testing notes: Full integration suite and go test ./... passed; mutation testing verified non-obvious invariants; compliance tests added per COMPLIANCE.md conventions with one MUST per test clause
🔍 Code Review
This is a well-architected implementation that thoughtfully extends the existing RFC 8414 metadata rather than duplicating logic, using a superset approach that prevents document drift. The author went beyond standard testing with mutation testing to verify critical invariants and properly scoped the work as unblocking provider registration rather than delivering full end-to-end flow.
What's good:
- ✨ Excellent use of mutation testing to verify invariants (algorithm mismatch and member leakage) rather than relying solely on assertions
- ✨ Thoughtful architectural choice to build as a superset of RFC 8414, extracting shared logic to prevent drift between two hand-maintained documents
- ✨ Clear scope boundaries with explicit test (TestOIDCDiscovery_NeverAdvertisesIDToken) guarding against unintended ID token advertisement, documented in COMPLIANCE.md
Generated by Oracle - Highflame's AI Code Reviewer
Section 10.4 asserted "ZeroID does not publish an /.well-known/openid-configuration document" — a statement this PR's own change makes false, in the one section of the normative spec that is specifically about ZeroID as a federation issuer to AWS / Azure / GCP. Found auditing the PR rather than writing it: the endpoint was added, the README and COMPLIANCE.md were updated, and the RFC-style spec that an independent implementer would read was left contradicting the code. Fixes the claim, adds §11.4 specifying the document (both OIDC-required members, why subject_types_supported is `public` rather than `pairwise`, and a MUST NOT against reading it as advertising an id_token), and updates the §14.5 / §14.6 registry cross-references.
Why
AWS IAM OIDC identity providers, Azure federated identity credentials and GCP workload identity pools all discover an external issuer by fetching
/.well-known/openid-configuration. All three were written against OpenID Connect Discovery, not RFC 8414 — so ZeroID publishing only/.well-known/oauth-authorization-serverleft it undiscoverable to every one of them, even though the keyset they would verify against was already public at/.well-known/jwks.json.That gap was the whole of "make our identity useful in someone else's cloud" (ADR 0028 D8). The blocker was a missing discovery document, not missing crypto.
This is also the shared top rung of the governance ladder: it is how a model-one agent reaches level 4 (customer configures their cloud to trust identities we issue) and how model two works outside our own walls. One piece of work, both models.
What
GET /.well-known/openid-configuration— the RFC 8414 document plus the two members OIDC Discovery §3 makes REQUIRED that RFC 8414 §2 does not define:subject_types_supported["public"]subis the identity's WIMSE URI (credential.go:384-388), identical for every verifier. A pairwise subject would break the cloud role trust policies that pin a literalsub.id_token_signing_alg_values_supported["ES256","RS256"]with an RSA key loaded,["ES256"]without.Built as a superset of the existing document rather than a second one — two hand-maintained documents describing one AS is how they drift.
buildASMetadatais extracted for both and returns a fresh map per call, so OIDC-only members cannot leak backwards into the RFC 8414 response.ZeroID is NOT an OpenID Provider
It issues no
id_token, and this document must not become evidence otherwise. The structural guard isresponse_types_supported, inherited from the base document: it carries at most"code"and never anid_tokenresponse type, so no relying party can request one whatever it discovers here.TestOIDCDiscovery_NeverAdvertisesIDTokenasserts that absence. It is the test that should fail — and be deleted — by the PR that makesid_tokenissuance real. Not before. Documented in the COMPLIANCE.md scope note and normatively in spec §11.4.Verification
go test ./...green;go vet+golangci-lintclean on the changed packages. Re-verified after themainmerge that brought in feat: coalesce concurrent CIMD document fetches for the same client_id #312.TestOIDCDiscovery_SigningAlgsMatchPublishedJWKSfails (and confirmed the suite exercises both keys rather than passing vacuously);TestOIDCDiscovery_OIDCOnlyMembersStayOutOfASMetadatafails on both leaked members.jwk.Setis internally RWMutex-protected, andKey(i)returning!okcorrectly handles a keyset that shrank mid-iteration.TestOIDCDiscovery1_0_S<section>_<assertion>, one MUST per test, clause quoted in the first comment). Coverage matrix updated.Audit findings (second commit)
A post-hoc audit of this PR found a defect the original commit missed: spec §10.4 asserted "ZeroID does not publish an
/.well-known/openid-configurationdocument" — in the one section of the normative spec specifically about ZeroID as a federation issuer to AWS/Azure/GCP. The endpoint was added and the README and COMPLIANCE.md updated, but the RFC-style document an independent implementer would read was left contradicting the code. Fixed, with a new §11.4 specifying the endpoint and updated §14.5/§14.6 registry cross-references.Scope
Correction to an earlier version of this description, which said end-to-end federation had to wait on an audience story. That was too pessimistic. Per spec §10.4, issued tokens default
audto the ZeroID issuer URL, and a relying party that enforces an audience must be configured to accept that URL as the expected audience — Anthropic Workload Identity Federation is already a verified relying party on exactly this path, with AWS/Azure/GCP following the same configuration shape. So this PR makes ZeroID discoverable, which is the piece AWS and Azure structurally could not work without; #199/#258 (caller-chosenaud) would make it more ergonomic, not newly possible.Out of scope by construction: OIDC §5 UserInfo, §2 WebFinger issuer discovery, and every ID Token clause.
Follow-ups
capabilities.yamlentry — "ZeroID publishes OIDC discovery + JWKS" is a platform capability others will build against./feature-prepwas not run; a spec PR should accompany or precede merge.🤖 Generated with Claude Code