test(e2e): cover workload identity against a real JWKS fetch - #782
Merged
Conversation
The RFC 7523 client_assertion path had no test where Authorizer actually FETCHES the signing keys. Every in-package test substitutes the fetch seam, which removes the one thing that decides whether the feature works in production: the address. mock-oauth stands in for the cluster — it already generates an RS256 keypair, serves /jwks and has discovery, so it only needed an endpoint minting a JWT shaped like a projected ServiceAccount token. Four cases: a workload authenticates, an assertion is single-use, an unlisted subject is refused, and a wrong-audience assertion is refused. Reaching a compose-network mirror needs the private-address escape hatch, so clientauth becomes the third caller of SafeHTTPClientAllowPrivate, gated on Config.Env == E2EEnv exactly as the SSO broker and webhook delivery are. That function's doc asks for careful review before a third caller; newSafeClient's comment carries the reasoning, including why the alternatives are worse — relaxing the guard's ranges weakens a production control for a test, and hosting the mirror on a range the guard happens to permit couples the suite to a block-list gap (there were two; #781 closes them) and loses the coverage silently once that gap is fixed. The gate is asserted by OUTCOME rather than by reading the flag: a private address must be refused under production, under an empty env, and with a nil Config, and must be genuinely reachable under e2e. An inverted condition or a drifting default fails there. Also corrects a false claim found while building this. The replay comment said K8s SA tokens carry no jti, and the (iss,sub,iat,exp) fallback was justified by that. Verified against a live cluster: a projected token's claims are aud, exp, iat, iss, jti, kubernetes.io, nbf, sub — it does carry one. The fallback is still correct to keep, for issuers that omit jti as RFC 7523 permits, but not for the stated reason. The mock mints a jti for the same reason: real tokens have one, and without it two mints in the same second collide on the fallback key.
lakhansamani
force-pushed
the
test/e2e-workload-identity
branch
from
August 17, 2026 06:20
8e071ed to
c54874a
Compare
lakhansamani
added a commit
that referenced
this pull request
Aug 19, 2026
* docs(changelog): cover #773-#783 Unreleased linked 50 PRs and none of #773-#783, so every change made after rc.22 - including four security fixes - was missing from the CHANGELOG a user reads at 2.4.0. Refs #773, #774, #775, #776, #777, #778, #779, #781, #782, #783 * chore: bump web/app to authorizer-react 2.2.0 authorizer-react 2.2.0 is published on authorizer-js 4.0.0; drop the -rc.7 pin. Also stamps the CHANGELOG's Unreleased section as 2.4.0. * test(e2e): make the authorizer host ports overridable The seven authorizer services published fixed host ports, so the suite could not run on a machine already using 8080-8086 - it failed at "address already in use" before any test ran. The mock services already take this shape. Playwright reaches every service by compose DNS, so the host mapping is for humans only and the defaults are unchanged.
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.
Requested alongside #781. Closes the coverage gap that PR's finding exposed.
The gap
The RFC 7523
client_assertionpath had no test where Authorizer actually fetches the signing keys. Every in-package test substitutes the fetch seam — which removes the single thing that decides whether the feature works in production: the address.mock-oauthstands in for the cluster. It already generates an RS256 keypair, serves/jwksand has discovery, so it only needed an endpoint minting a JWT shaped like a projected ServiceAccount token. Four cases:allowed_subjectsis refusedaudassertion is refusedBecoming the third caller of
SafeHTTPClientAllowPrivateThe mirror is a compose-network service on a private address, so this needs the escape hatch — gated on
Config.Env == E2EEnv, exactly as the SSO broker and webhook delivery already are. That function's doc comment asks for careful review before a third caller, so:Why one is needed at all. Workload identity is Authorizer fetching a key document the operator points it at. Without this, the only end-to-end coverage of that fetch is no coverage.
Why the alternatives are worse. Relaxing the guard's ranges weakens a production control for a test. Hosting the mirror on a range the guard happens to permit couples the suite to a gap in the block list — there were two, and #781 closes them — so that approach loses its coverage the moment the gap is fixed correctly.
What is not relaxed. Scheme allow-list, one-shot DNS resolution and dial pinning (rebinding defence), redirect refusal, size cap, TLS verification. This widens which addresses are reachable and nothing else — the same trade, under the same flag, as the two existing callers.
The gate is asserted by outcome, not by reading the flag. A private address must be refused under
production, under an empty env, and with a nil Config; and must be genuinely reachable (a real request, not just client construction) undere2e. An inverted condition, a drifting default, or a nil Config arriving from a new call site all fail there.A false claim found while building it
The replay comment said K8s ServiceAccount tokens carry no
jti, and justified the(iss,sub,iat,exp)fallback with it. Verified against a live cluster:They do carry one. The fallback is still correct to keep — RFC 7523 permits omitting
jti, and without the fallback such an assertion would have no single-use key at all — but not for the stated reason. Corrected in the code comment and the test comment; the docs repo copy is corrected in authorizerdev/docs#92.The mock mints a
jtifor the same reason: real tokens have one, and without it two mints in the same second collide on the fallback key — which is exactly how the single-use test failed before I added it.Verification