Skip to content

Feature: Bearer token (JWT) authentication mode for the impersonation proxy #3184

Description

@sigtrapd

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:

  1. Client presents external JWT to TokenCredentialRequest
  2. Pinniped validates the JWT via JWTAuthenticator (JWKS fetch, signature, claims)
  3. Pinniped issues a short-lived X.509 cert signed by its own CA
  4. Client uses that cert for mTLS to the impersonation proxy
  5. 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:

  1. Accept Authorization: Bearer <jwt> on incoming requests
  2. Validate the JWT using the configured JWTAuthenticator (JWKS already cached, claim extraction already implemented)
  3. Derive the username via the existing usernameExpression CEL logic
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions