Repository navigation
atecontroller publishes actor JWT verification keys for ate-idp-server to serve - #2134
Closed
Max Thompson (thompsonmax) wants to merge 6 commits into
Closed
Max Thompson (thompsonmax) wants to merge 6 commits into
Max Thompson (thompsonmax) wants to merge 6 commits into
Conversation
Max Thompson (thompsonmax)
requested review from
Eitan Yarmush (EItanya),
Shruti Nair (SHRUTI6991) and
Taahir Ahmed (ahmedtd)
October 2, 2026 20:07
Max Thompson (thompsonmax)
marked this pull request as ready for review
October 2, 2026 20:07
Max Thompson (thompsonmax)
force-pushed
the
actor-jwks-configmap
branch
2 times, most recently
from
October 5, 2026 17:20
dcf01d8 to
d759c5c
Compare
Add oidcdiscovery.Thumbprint, the RFC 7638 SHA-256 thumbprint of an RSA or P-256 public key, and replace GenerateECDSAP256Authority with GenerateAuthority, which generates an RS256 or ES256 key and defaults its ID to the thumbprint. A thumbprint ID cannot collide across operators or be reused for a different key. kubectl ate admin make-jwt-pool gains --alg, defaulting to RS256, and --key-id now defaults to the thumbprint. ate-setup creates the same kind of pool. RS256 works with relying parties that do not support ES256. Existing pools are untouched, so keys named "1" keep working.
New actor JWT pools default to ES256 again, matching every existing pool. make-jwt-pool --alg defaults to ES256, and ate-setup reads the algorithm from ACTOR_JWT_ALGORITHM (ES256 or RS256, default ES256), so an install whose relying parties do not support ES256 can choose RS256 without creating the pool by hand. Key IDs still default to the thumbprint.
Use go-jose's JSONWebKey.Thumbprint instead of building the RFC 7638 canonical form by hand. Thumbprint still accepts only RSA and P-256 keys, the types the pool generates. go-jose moves from an indirect to a direct dependency; it was already vendored.
kubectl-ate's make-jwt-pool and ate-setup each generated an authority, made it active, and marshaled the pool. GeneratePool does that and returns the serialized pool and the authority's ID, so both callers only build their Secret.
Add JWKS, which serializes verification keys into the JWK set relying parties fetch from the issuer, sorted by key ID. It encodes with go-jose's JSONWebKey, as the Kubernetes service account issuer does, and adds the checks go-jose leaves to the caller: the set is not empty, key IDs are present and unique, and each algorithm fits its key (ES256 for P-256 EC, RS256, RS384, or RS512 for RSA), so a malformed pool cannot publish a set that verifiers would reject. localjwtauthority.VerificationKey gains Algorithm so callers can publish it.
ate-idp-server serves the actor JWT key set but must never see the signing keys, so something with access to the actor-id-jwt-pool Secret has to copy the public half out. ActorJWKSReconciler watches that Secret and server-side-applies the JWK set of every authority in the pool, active or not, into the actor-id-jwks ConfigMap under jwks.json. The ConfigMap is owned by the Secret, so garbage collection removes the published keys along with the pool. An unreadable or empty pool fails the reconcile and leaves the last good key set in place, since relying parties cache whatever ate-idp-server serves. Deleting or editing the ConfigMap by hand is reverted. atecontroller gains ConfigMap write access through a Role scoped to the system namespace. Its Secret cache now covers the whole system namespace: a cache takes one field selector per namespace, and two controllers each need a different Secret.
Max Thompson (thompsonmax)
force-pushed
the
actor-jwks-configmap
branch
from
October 5, 2026 17:42
d759c5c to
b933fd9
Compare
2 tasks
Collaborator
Author
|
Closing in favor of having ate-idp-server read the pool Secret directly with RefreshingPool (#2099), which removes the need for this controller and the ConfigMap. |
Max Thompson (thompsonmax)
added a commit
to thompsonmax/substrate
that referenced
this pull request
Oct 16, 2026
…tor JWTs (agent-substrate#2099) Part of agent-substrate#1756. - Adds `cmd/ate-idp-server`, which serves `/.well-known/openid-configuration` and `/openid/v1/jwks` over HTTPS with a servicedns credential bundle, so relying parties can verify actor JWTs. - Reads the actor JWT authority pool from `--actor-id-jwt-pool` with `localjwtauthority.RefreshingPool`, the reader ate-api-server signs with, and builds each document from it per request. - Builds both documents at startup and exits if it can't. After that, a request whose document can't be built gets a 500 rather than an empty or stale set. - Requires `--issuer` rather than deriving a default, so it can't disagree with ateapi, and accepts only `https://<host>`, so the routes are fixed. The check is the new `actoridjwt.ValidateIssuer`; the discovery document moves out of `internal/oidcdiscovery` into the server, its only user. - Makes the EC case in `localjwtauthority.JWKS` explicit: P-256 with fixed 32-byte coordinates and an error for any other curve, rather than reading the coordinate size off the encoded point. - Records in `docs/metrics/substrate.yaml` that it emits no metrics. For reviewers: the pool holds the signing keys, so ate-idp-server will mount the same Secret ate-api-server does, in place of the controller and ConfigMap in agent-substrate#2134. ate-api-server and ate-setup still accept an issuer with a path until the installer deploys ate-idp-server. It serves `/healthz` but no `/readyz`. Nothing deploys it yet. Testing: unit tests for routing, the host-only issuer check, the discovery document, startup failures, key changes, 500s on a broken set, the P-256 encoding, and required flags, plus a local run over TLS against a generated pool. E2E can't cover it until it's deployed. - [ ] Tests pass - [ ] Appropriate changes to documentation are included in the PR
Timo Derstappen (teemow)
pushed a commit
to giantswarm/substrate
that referenced
this pull request
Oct 18, 2026
…tor JWTs (agent-substrate#2099) Part of agent-substrate#1756. - Adds `cmd/ate-idp-server`, which serves `/.well-known/openid-configuration` and `/openid/v1/jwks` over HTTPS with a servicedns credential bundle, so relying parties can verify actor JWTs. - Reads the actor JWT authority pool from `--actor-id-jwt-pool` with `localjwtauthority.RefreshingPool`, the reader ate-api-server signs with, and builds each document from it per request. - Builds both documents at startup and exits if it can't. After that, a request whose document can't be built gets a 500 rather than an empty or stale set. - Requires `--issuer` rather than deriving a default, so it can't disagree with ateapi, and accepts only `https://<host>`, so the routes are fixed. The check is the new `actoridjwt.ValidateIssuer`; the discovery document moves out of `internal/oidcdiscovery` into the server, its only user. - Makes the EC case in `localjwtauthority.JWKS` explicit: P-256 with fixed 32-byte coordinates and an error for any other curve, rather than reading the coordinate size off the encoded point. - Records in `docs/metrics/substrate.yaml` that it emits no metrics. For reviewers: the pool holds the signing keys, so ate-idp-server will mount the same Secret ate-api-server does, in place of the controller and ConfigMap in agent-substrate#2134. ate-api-server and ate-setup still accept an issuer with a path until the installer deploys ate-idp-server. It serves `/healthz` but no `/readyz`. Nothing deploys it yet. Testing: unit tests for routing, the host-only issuer check, the discovery document, startup failures, key changes, 500s on a broken set, the P-256 encoding, and required flags, plus a local run over TLS against a generated pool. E2E can't cover it until it's deployed. - [ ] Tests pass - [ ] Appropriate changes to documentation are included in the PR (cherry picked from commit 0877906) Signed-off-by: Timo Derstappen <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1756. Stacked on #2132, which is stacked on #1961; only the last commit is new.
ActorJWKSReconcilerto atecontroller. It watches theactor-id-jwt-poolSecret and server-side-applies the public key of every authority, active or not, into theactor-id-jwksConfigMap asjwks.json, the file ate-idp-server (Add ate-idp-server to serve the discovery document and key set for actor JWTs #2099) reads. ate-idp-server never sees the signing keys.ate-system.For reviewers: atecontroller's Secret cache now covers all of
ate-systemrather than onlyegress-mitm-ca-pool, because a cache takes one field selector per namespace. Its ClusterRole already reads Secrets cluster-wide.Testing: fake-client tests for publishing, rotation, reverting edits, a missing pool, and bad pools. Not run against a cluster; the e2e PR covers that once the manifests land.