Repository navigation
Add ate-idp-server to serve the discovery document and key set for actor JWTs - #2099
Merged
Taahir Ahmed (ahmedtd) merged 5 commits intoOct 16, 2026
Merged
Conversation
Max Thompson (thompsonmax)
force-pushed
the
ate-idp-server
branch
2 times, most recently
from
October 2, 2026 17:47
4cd8539 to
55d3488
Compare
Max Thompson (thompsonmax)
requested review from
Eitan Yarmush (EItanya),
Shruti Nair (SHRUTI6991) and
Taahir Ahmed (ahmedtd)
October 2, 2026 17:50
Max Thompson (thompsonmax)
marked this pull request as ready for review
October 2, 2026 17:50
2 tasks done
Max Thompson (thompsonmax)
force-pushed
the
ate-idp-server
branch
from
October 5, 2026 22:59
55d3488 to
f8da78f
Compare
2 tasks
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio. |
Max Thompson (thompsonmax)
force-pushed
the
ate-idp-server
branch
3 times, most recently
from
October 7, 2026 20:55
8536af6 to
c5dd81b
Compare
Lior Lieberman (LiorLieberman)
pushed a commit
to LiorLieberman/agent-substrate
that referenced
this pull request
Oct 8, 2026
…ubstrate#2132) Part of agent-substrate#1756. - Adds `localjwtauthority.JWKS`, which turns a pool's verification keys into the JWK set relying parties fetch, sorted by key ID. It encodes the keys in-tree with the RFC 7518 parameters, padding EC coordinates to the curve size. - Rejects an empty set, missing or duplicate key IDs, and an algorithm that doesn't fit its key, so a bad pool can't publish a set verifiers would reject. - `localjwtauthority.VerificationKey` gains `Algorithm`. - `Thumbprint` moves from `oidcdiscovery` to `localjwtauthority`, since key IDs belong to the pool. It is now the base64url SHA-256 of the key's PKIX encoding, as the Kubernetes service account issuer derives `kid`, instead of go-jose's RFC 7638 thumbprint. Pools store each key's ID, so existing keys keep theirs. go-jose is no longer imported; it stays in go.mod as an indirect dependency. Nothing calls `JWKS` yet; ate-idp-server (agent-substrate#2099) will. Testing: the RFC 7517 and RFC 7638 example keys, thumbprints checked against openssl, EC coordinate padding, a round trip that decodes keys back to the originals, and the rejection cases. E2E can't cover it until it's deployed. - [ ] Tests pass - [ ] Appropriate changes to documentation are included in the PR
Max Thompson (thompsonmax)
force-pushed
the
ate-idp-server
branch
from
October 8, 2026 22:32
c5dd81b to
228aab1
Compare
Max Thompson (thompsonmax)
force-pushed
the
ate-idp-server
branch
from
October 13, 2026 20:24
125da15 to
80fedfa
Compare
This was referenced Oct 13, 2026
Max Thompson (thompsonmax)
force-pushed
the
ate-idp-server
branch
4 times, most recently
from
October 15, 2026 16:49
38d035e to
308bedd
Compare
Taahir Ahmed (ahmedtd)
previously approved these changes
Oct 15, 2026
Taahir Ahmed (ahmedtd)
enabled auto-merge
October 15, 2026 22:06
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 15, 2026
Add a small read-only HTTPS server for the OpenID discovery document and JWK set that relying parties use to verify actor JWTs. It serves the key set from --jwks-file as is, builds the discovery document from the keys' algorithms with oidcdiscovery.DiscoveryDocument, and reloads when the file changes, keeping the last good set if a new one does not parse. Routes sit under the issuer's path and match on the cleaned path. --issuer is required. The installer passes the same value it gives ateapi, so the two cannot derive different issuers. Until a key set loads, /readyz and both documents return 503, so a relying party never caches an empty set. TLS uses the servicedns credential bundle through credbundle, which picks up rotated certificates. The server emits no metrics, which docs/metrics/substrate.yaml records.
ate-idp-server read a JWK set that a controller was to derive from the authority pool and copy into a ConfigMap. It now mounts the pool itself and reads it with localjwtauthority.RefreshingPool, the same reader ate-api-server signs with, so publishing a key needs no second component and no internal ConfigMap. --actor-id-jwt-pool replaces --jwks-file and --reload-interval; the pool is reread at most once a minute. Each request builds the key set and discovery document from the pool's verification keys. RefreshingPool fails every call after a bad reread, so the server keeps the last documents it built and serves those; until it has built any, it reports not ready and answers with 503.
toJWK now switches on the curve and slices the two 32-byte P-256 coordinates out of the uncompressed point, returning an error for any other curve, instead of deriving the coordinate size from the point's length. It also stops aliasing base64.RawURLEncoding.EncodeToString. The coordinates still come from PublicKey.Bytes rather than the X and Y fields, which are deprecated.
The server registers GET patterns on a ServeMux for the discovery
document, the JWK set and /healthz, so the mux answers HEAD, 405 with
Allow, and 404 in place of hand-written checks. The patterns use the
issuer's escaped path, so a character such as { in it stays literal.
New builds both documents and fails if it cannot, so ate-idp-server
exits at startup on a pool it cannot publish. After that, each request
builds its document from the pool and answers 500 if that fails, instead
of serving the last good documents. That leaves /readyz nothing to
report, so it is removed.
ate-idp-server now requires an issuer of the form https://<host>, with no path, and serves /.well-known/openid-configuration and /openid/v1/jwks at fixed routes. Supporting a path meant building routes from the escaped issuer path and handling dot segments; an operator who fronts the server with a load balancer still chooses the host. The host-only check is actoridjwt.ValidateIssuer so that ate-api-server and ate-setup can share it. The discovery document moves out of internal/oidcdiscovery into the server, its only user; ValidateIssuer stays there for ate-api-server and ate-setup until they switch. The server also stops special-casing HEAD, since net/http discards the body and sets Content-Length, and sets only Content-Type, because net/http would infer text/plain and OpenID Connect Discovery requires application/json. Relying parties refetch the key set when they see an unknown key ID, so Cache-Control goes, as does the CORS header.
Max Thompson (thompsonmax)
force-pushed
the
ate-idp-server
branch
from
October 16, 2026 16:31
308bedd to
2898875
Compare
Taahir Ahmed (ahmedtd)
approved these changes
Oct 16, 2026
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]>
Merged
1 of 2 tasks
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.
cmd/ate-idp-server, which serves/.well-known/openid-configurationand/openid/v1/jwksover HTTPS with a servicedns credential bundle, so relying parties can verify actor JWTs.--actor-id-jwt-poolwithlocaljwtauthority.RefreshingPool, the reader ate-api-server signs with, and builds each document from it per request.--issuerrather than deriving a default, so it can't disagree with ateapi, and accepts onlyhttps://<host>, so the routes are fixed. The check is the newactoridjwt.ValidateIssuer; the discovery document moves out ofinternal/oidcdiscoveryinto the server, its only user.localjwtauthority.JWKSexplicit: P-256 with fixed 32-byte coordinates and an error for any other curve, rather than reading the coordinate size off the encoded point.docs/metrics/substrate.yamlthat 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 #2134. ate-api-server and ate-setup still accept an issuer with a path until the installer deploys ate-idp-server. It serves
/healthzbut 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.