Skip to content

Actor JWT contract and OIDC discovery for external relying parties #1756

Description

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

Activity

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

Metadata

Metadata

Labels

area/apiUser-facing API changesarea/identityarea/securitySecurity related issue/prkind/featureAn enhancement / feature request or implementationprio/P0Highest priority / required for next milestone

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions