Skip to content

Credential Injection - Support RFC 8693 token exchange of the Actor JWT #1661

Description

Summary

Let an EgressPolicy hostname rule ask the egress gateway to inject a credential obtained by exchanging the actor's JWT at an OAuth 2.0 token-exchange endpoint (RFC 8693). The gateway mints an actor JWT bound to the exchange endpoint's audience, presents it as the subject_token, and injects the returned access_token on the TLS-terminated MITM leg. The actor never sees either token.

Builds on #1660 (actor JWT injection).

Motivation

  • Most third-party APIs do not understand Substrate actor JWTs. They understand their own access tokens. Token exchange is the standard bridge: the actor proves who it is with a Substrate-signed JWT, and a trusted STS hands back a credential scoped to that identity.
  • This removes the last reason to put long-lived cloud credentials in a Kubernetes Secret for the static-injection path. The credential is short-lived, minted per actor, and derived from verified identity rather than provisioned.
  • It answers the "federation" and "OAuth token exchange" use cases raised in Carrying actor JWTs to an Egress PEP - use cases #586 and gives the in-cluster gateway question in How should an in-cluster user-owned gateway identify an actor? #1228 a concrete path.

Current state

Proposal

API

Add an exchange-backed effect alongside inject_static_headers and inject_actor_jwt. Exchange endpoints are configured per install and referenced by name, so a policy never carries endpoint URLs or CA material:

message EgressRuleEffects {
  repeated CredentialHeaderInjection inject_static_headers = 1;
  repeated ActorJWTHeaderInjection inject_actor_jwt = 2;

  // Injects a credential obtained by RFC 8693 exchange of the actor JWT.
  // +k8s:optional
  // +k8s:maxItems=16
  // +k8s:listType=map
  // +k8s:listMapKey=header
  repeated ExchangedTokenHeaderInjection inject_exchanged_token = 3;
}

message ExchangedTokenHeaderInjection {
  // +k8s:required
  string header = 1;              // unique across all three effect lists
  // +k8s:optional
  string prefix = 2;              // e.g. "Bearer "
  // +k8s:required
  string exchange_provider = 3;   // name of a gateway-configured exchange endpoint
  // Audience the actor JWT is minted for and sent as the RFC 8693 `audience`.
  // For GCP: //iam.googleapis.com/projects/N/locations/global/workloadIdentityPools/P/providers/X
  // +k8s:required
  string audience = 4;
  // +k8s:optional
  repeated string scopes = 5;     // RFC 8693 `scope`
}

Gateway configuration (flags or a small config file on atenet-egress) defines each named provider: token endpoint URL, optional CA bundle, subject_token_type (default urn:ietf:params:oauth:token-type:jwt), requested_token_type (default urn:ietf:params:oauth:token-type:access_token), and timeout.

Gateway flow

On the egress_tls_mitm leg, when the first matching rule carries inject_exchanged_token:

  1. Mint an actor JWT with MintActorJWT(actor, uid, [audience]), reusing the UID resolution and token cache from Credential Injection - Support Actor JWT #1660. The JWT's only audience is the exchange audience, so it cannot be replayed against any other relying party.
  2. POST to the named provider's token endpoint with grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token=, subject_token_type, audience, scope, and requested_token_type. No actor_token; this is impersonation, not delegation.
  3. Validate the response (access_token present, issued_token_type as requested) and sanitize the token bytes before they become a header value.
  4. Cache the exchanged token per (actor UID, exchange provider, audience, scopes) until expires_in minus a safety margin, with singleflight so a burst of requests performs one exchange.
  5. Set header to prefix + access_token with OVERWRITE_IF_EXISTS_OR_ADD.
  6. Fail closed on mint or exchange failure. The policy promised a credential.

Latency: the first request for an actor pays mint plus an external round trip, and it must fit inside the ext_proc message timeout (5s today, with a 4s policy-fetch budget). Options: a dedicated exchange timeout, warming the cache on the CONNECT leg when the policy has exchange effects, or raising the MITM route's ext_proc timeout. To be decided in the PR.

Where the exchange runs

Recommended: an in-gateway broker in cmd/atenet/internal/router/egress, keyed by named provider config. It needs no new deployment, no extra hop, and matches the "broker within Substrate" direction from the #1360 review.

Alternative: a CredentialProvider plugin that receives the actor JWT in SecretRequestContext and performs the exchange itself. This keeps the gateway simpler and aligns with the intent that the plugin uses the actor JWT to authenticate externally, at the cost of another deployment and hop. The proposal above does not preclude adding this later.

Prerequisites in ateapi

  • Publish OIDC discovery and JWKS for the actor-JWT issuer, with a configurable issuer URL. GCP Workload Identity Federation accepts an uploaded JWK set, so the issuer need not be reachable from Google, but the iss string must still be a valid URL that matches the provider configuration exactly. AWS and Azure federation fetch keys from the issuer, so those targets need a reachable endpoint. The JWKS must include inactive authorities so rotation does not break verification. localjwtauthority.Pool.VerificationKeys already returns the whole pool.
  • Caller authorization for MintActorJWT (tracked in Credential Injection - Support Actor JWT #1660).

Security notes

  • The actor JWT's sub is atespaces::actors:. On the STS side, attribute mapping and conditions (for GCP, google.subject from assertion.sub, with a 127-character limit) decide which actors may federate. Document a recommended mapping so operators can bind IAM to a single actor or an atespace prefix.
  • Exchanged tokens live in the shared gateway process, the same blast radius as static injection. Never log token material; log provider name, audience, actor, and status only.
  • The token endpoint must be TLS-verified against system roots or a configured CA. No plaintext endpoints.

Out of scope

  • Delegation (actor_token) and requesting token types other than access tokens.
  • Service-account impersonation as a second hop (GCP generateAccessToken).
  • Conditional or sentinel-triggered injection.
  • The agentgateway dataplane.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area/identityarea/networkarea/securitySecurity related issue/prkind/featureAn enhancement / feature request or implementationprio/P0Highest priority / required for next milestone

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions