Skip to content

Credential Injection - Support Actor JWT #1660

Description

Summary

Let an EgressPolicy hostname rule ask the egress gateway to inject a Substrate-issued actor JWT into a request header. The gateway mints the token through the existing Control.MintActorJWT RPC with the audience(s) the policy names, and attaches it on the TLS-terminated MITM leg. The actor never holds the token.

Motivation

Current state

  • Control.MintActorJWT(actor, actor_uid, audience[]) exists in cmd/ateapi/internal/controlapi/actor.go.
  • EgressRuleEffects has one field, inject_static_headers. PR egress: add credential injector #1360 implements it in cmd/atenet/internal/router/egress/credentials.go (Handler.applyEffects) by calling a CredentialProvider, and only on the egress_tls_mitm filter chain.
  • On the MITM leg the handler knows the actor only from the dev.ate.actor.identity filter state, a SPIFFE URI carrying atespace and name but not the UID.

Proposal

API

Add another effect alongside inject_static_headers:

message EgressRuleEffects {
  repeated CredentialHeaderInjection inject_static_headers = 1;

  // Injects a Substrate-issued actor JWT bound to the listed audiences.
  // +k8s:optional
  // +k8s:maxItems=16
  // +k8s:listType=map
  // +k8s:listMapKey=header
  repeated ActorJWTHeaderInjection inject_actor_jwt = 2;
}

message ActorJWTHeaderInjection {
  // +k8s:required
  string header = 1;            // case-insensitive, unique across both effect lists
  // +k8s:optional
  string prefix = 2;            // prepended verbatim, e.g. "Bearer "
  // +k8s:required
  // +k8s:minItems=1
  // +k8s:maxItems=16
  repeated string audiences = 3; // passed to MintActorJWT as-is
}

Header uniqueness must be enforced across inject_static_headers and inject_actor_jwt together. The audiences limits mirror MintActorJWTRequest.audience.

Gateway

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

  1. Resolve the actor UID. Either look it up with GetActor and cache it next to the compiled policy, or carry the UID from the CONNECT leg (where the certificate's ActorIdentity extension was already verified) into filter state.
  2. Call MintActorJWT(actor, uid, audiences) over the gateway's existing pod-identity mTLS connection to ateapi. No new dependency: the handler already holds a ControlClient.
  3. Cache tokens per (actor UID, audience set) using the same singleflight and TTL pattern as policycache.go, refreshing well inside the 15-minute lifetime.
  4. Set header to prefix + jwt with OVERWRITE_IF_EXISTS_OR_ADD, so a value the actor set itself never survives.
  5. Fail forwarding request if minting JWT fails.

Behavior on the cleartext leg should match whatever #1360 settles on for static injection, so the two effects have one rule.

ateapi

  • Restrict who may call MintActorJWT. Until OpenFGA authorization (OpenFGA-based authorization - basic #1563, OpenFGA-based authorization - atelet/ateom authorizer #1564) exists, an interim allowlist of caller identities (atelet, atenet-egress) by SPIFFE SAN keeps any mTLS peer from minting for any actor.
  • Make the issuer configurable and publish OIDC discovery plus JWKS (localjwtauthority.Pool.VerificationKeys already exists for this). Without it no relying party can verify the token. This can be a separate prerequisite issue, but it gates any real consumer.

Out of scope

  • RFC 8693 exchange of the actor JWT for another credential (follow-up issue).
  • Conditional/sentinel-triggered injection (we likely want to eventually support this over blind injection, but will leave this out of scope for now)

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