Summary
ateapi mints actor JWTs that nothing outside the cluster can verify. The issuer is a hardcoded string, no OpenID Connect discovery document or JWK set is served, and there is no way to rotate the signing key. Finalize the token contract and serve discovery and JWKS so that the egress gateway can inject the token (#1660) and exchange it (#1661), and so operators can federate actors into cloud IAM.
Design: Actor JWT Schema + OIDC (shared with ate-dev@).
Motivation
Current state
Control.MintActorJWT signs with the pool from --actor-id-jwt-pool. iss is the literal https://api.ate-system.svc; the header carries alg and kid but no typ.
- Both installers create an ES256 pool with
kid "1". make-jwt-pool can only create a single-authority Secret; there is no add, activate, or remove.
Pool.VerificationKeys exists but has no caller. ateapi exposes gRPC on 443 and plaintext metrics on 9090; neither serves HTTP discovery or JWKS.
- The RPC logging interceptor writes the full
MintActorJWTResponse, token included, at Info.
Proposal
The design doc has the detail. In short:
- Token: keep
sub as atespaces:<atespace>:actors:<name>, aud always an array, ate.dev claims stable. Add typ: JWT. New authorities default to RS256 with an RFC 7638 thumbprint as kid; existing "1" keys are grandfathered. A single --actor-jwt-issuer value feeds iss, the discovery issuer, and jwks_uri.
- API:
MintActorJWTRequest accepts a requested lifetime clamped to [5m, 1h], with zero meaning the current 15m default; the response returns expires_at.
- Discovery:
/.well-known/openid-configuration and /openid/v1/jwks, shaped like the Kubernetes service account issuer, with no authorization_endpoint. The JWKS carries every authority in the pool, so rotation is add, publish, activate, wait, remove.
- Serving: a small read-only
ate-oidc-server behind an operator-supplied Ingress, serving public key material reconciled from the pool Secret into a ConfigMap. ateapi gains no public listener.
Work
- Contract:
typ header, algorithm on verification keys, issuer flag, requested duration and expires_at, redact actor_jwt from RPC logs.
- Keys: RS256 generation and thumbprint kids as the default in
kubectl ate admin make-jwt-pool and ate-setup; add, activate, and remove subcommands for rotation.
- Serving: shared discovery and JWKS package, ConfigMap reconciler,
ate-oidc-server binary and manifests, issuer plumbing through both installers, and an e2e that verifies a minted token against the served JWKS.
- Docs: fix
docs/api-guide.md section 7 and docs/authentication.md, which describe an ActorIdentity service and checks that do not exist; add the contract, federation recipes for GCP, AWS, and Azure, and a
rotation runbook.
Out of scope
Summary
ateapi mints actor JWTs that nothing outside the cluster can verify. The issuer is a hardcoded string, no OpenID Connect discovery document or JWK set is served, and there is no way to rotate the signing key. Finalize the token contract and serve discovery and JWKS so that the egress gateway can inject the token (#1660) and exchange it (#1661), and so operators can federate actors into cloud IAM.
Design: Actor JWT Schema + OIDC (shared with ate-dev@).
Motivation
Current state
Control.MintActorJWTsigns with the pool from--actor-id-jwt-pool.issis the literalhttps://api.ate-system.svc; the header carriesalgandkidbut notyp.kid"1".make-jwt-poolcan only create a single-authority Secret; there is no add, activate, or remove.Pool.VerificationKeysexists but has no caller. ateapi exposes gRPC on 443 and plaintext metrics on 9090; neither serves HTTP discovery or JWKS.MintActorJWTResponse, token included, at Info.Proposal
The design doc has the detail. In short:
subasatespaces:<atespace>:actors:<name>,audalways an array,ate.devclaims stable. Addtyp: JWT. New authorities default to RS256 with an RFC 7638 thumbprint askid; existing"1"keys are grandfathered. A single--actor-jwt-issuervalue feedsiss, the discoveryissuer, andjwks_uri.MintActorJWTRequestaccepts a requested lifetime clamped to [5m, 1h], with zero meaning the current 15m default; the response returnsexpires_at./.well-known/openid-configurationand/openid/v1/jwks, shaped like the Kubernetes service account issuer, with noauthorization_endpoint. The JWKS carries every authority in the pool, so rotation is add, publish, activate, wait, remove.ate-oidc-serverbehind an operator-supplied Ingress, serving public key material reconciled from the pool Secret into a ConfigMap. ateapi gains no public listener.Work
typheader, algorithm on verification keys, issuer flag, requested duration andexpires_at, redactactor_jwtfrom RPC logs.kubectl ate admin make-jwt-poolandate-setup; add, activate, and remove subcommands for rotation.ate-oidc-serverbinary and manifests, issuer plumbing through both installers, and an e2e that verifies a minted token against the served JWKS.docs/api-guide.mdsection 7 anddocs/authentication.md, which describe anActorIdentityservice and checks that do not exist; add the contract, federation recipes for GCP, AWS, and Azure, and arotation runbook.
Out of scope
MintActorJWT(OpenFGA-based authorization - basic #1563, OpenFGA-based authorization - atelet/ateom authorizer #1564).