Problem
On managed cloud clusters (GKE, EKS, AKS) where the kube-controller-manager signing cert is inaccessible, Pinniped falls back to the impersonation proxy + its own signer CA. The current flow requires:
- Client presents external JWT to
TokenCredentialRequest
- Pinniped validates the JWT via
JWTAuthenticator (JWKS fetch, signature, claims)
- Pinniped issues a short-lived X.509 cert signed by its own CA
- Client uses that cert for mTLS to the impersonation proxy
- Proxy impersonates the user to the upstream API server
The cert and key are both transmitted together in the TokenCredentialRequest response. The client never generates the key pair — the private key is handed over the wire. This eliminates the proof-of-possession guarantee that mTLS is supposed to provide, making the security posture functionally equivalent to a bearer token.
What currently exists
The impersonation proxy does have bearer token handling (withBearerTokenPreservation + tokenPassthroughRoundTripper), but this passes tokens through unchanged to the upstream API server — only for service account tokens with UIDs that cannot be impersonated via headers. There is no path that validates a bearer token via the JWTAuthenticator and then impersonates the resulting user upstream. The JWTAuthenticator and the impersonation proxy are completely separate code paths inside Concierge with no bridge between them.
Proposal
Add a bearer token authentication mode to the impersonation proxy. When enabled, the proxy would:
- Accept
Authorization: Bearer <jwt> on incoming requests
- Validate the JWT using the configured
JWTAuthenticator (JWKS already cached, claim extraction already implemented)
- Derive the username via the existing
usernameExpression CEL logic
- Forward to the upstream API server with
Impersonate-User header — identical to what the proxy already does after mTLS auth
All the necessary infrastructure already exists inside Concierge — the JWTAuthenticator controller, JWKS caching, and claim extraction. This would bridge those two existing code paths.
Suggested config
impersonationProxy:
mode: auto
authentication:
- mTLS # existing default
- bearerToken # new opt-in mode
Explicit opt-in, preserving existing default behavior.
Why this matters
- Eliminates the signer CA, cert issuance, and secret rotation infrastructure that only exists to serve an mTLS handshake that provides no meaningful security advantage over the JWT it replaced (given the key is transmitted alongside the cert)
- Makes the proxy usable from any HTTP client with a JWT —
curl, kubectl with a simple token credential plugin, service-to-service calls
- Reduces operational complexity for single-cluster internal deployments where the IdP is trusted infrastructure
- The JWTAuthenticator already caches JWKS, so per-request validation overhead is a cache lookup + signature verify — not an external call on every request
Problem
On managed cloud clusters (GKE, EKS, AKS) where the kube-controller-manager signing cert is inaccessible, Pinniped falls back to the impersonation proxy + its own signer CA. The current flow requires:
TokenCredentialRequestJWTAuthenticator(JWKS fetch, signature, claims)The cert and key are both transmitted together in the
TokenCredentialRequestresponse. The client never generates the key pair — the private key is handed over the wire. This eliminates the proof-of-possession guarantee that mTLS is supposed to provide, making the security posture functionally equivalent to a bearer token.What currently exists
The impersonation proxy does have bearer token handling (
withBearerTokenPreservation+tokenPassthroughRoundTripper), but this passes tokens through unchanged to the upstream API server — only for service account tokens with UIDs that cannot be impersonated via headers. There is no path that validates a bearer token via theJWTAuthenticatorand then impersonates the resulting user upstream. TheJWTAuthenticatorand the impersonation proxy are completely separate code paths inside Concierge with no bridge between them.Proposal
Add a bearer token authentication mode to the impersonation proxy. When enabled, the proxy would:
Authorization: Bearer <jwt>on incoming requestsJWTAuthenticator(JWKS already cached, claim extraction already implemented)usernameExpressionCEL logicImpersonate-Userheader — identical to what the proxy already does after mTLS authAll the necessary infrastructure already exists inside Concierge — the
JWTAuthenticatorcontroller, JWKS caching, and claim extraction. This would bridge those two existing code paths.Suggested config
Explicit opt-in, preserving existing default behavior.
Why this matters
curl,kubectlwith a simple token credential plugin, service-to-service calls