Skip to content

Multi-service authorization extension: token primitives, control plane, outbound tokens, gateway, and topology (#12–#15)#21

Open
JedimEmO wants to merge 10 commits into
masterfrom
feature/authz-extension
Open

Multi-service authorization extension: token primitives, control plane, outbound tokens, gateway, and topology (#12–#15)#21
JedimEmO wants to merge 10 commits into
masterfrom
feature/authz-extension

Conversation

@JedimEmO

Copy link
Copy Markdown
Owner

Summary

Implements the service-coms/gateway extension from #12, #13, #14, and #15, in the layering agreed during the issue review. #16 (binary data-plane transport) is deliberately not included.

Eight new crates across three families, plus an end-to-end example and book documentation:

crates/authorization/

  • ras-authorization-token — the shared foundation: one RasClaims model for all RAS token families (ras_web_session multi-audience, ras_internal_access / ras_gateway_access single-audience), ES256/EdDSA signing (HS256 for embedded shared-secret mode only), kid-based KeyRing rotation with emergency removal, JWKS publication, and a strict TokenValidator (asymmetric-only algorithm allowlist by default, key/header algorithm cross-check against key-type confusion, issuer/audience/token-type pinning, clock-skew-aware exp/nbf).
  • ras-authorization-core (Add RAS-native authorization control plane and internal service token issuer #13, embedded scope) — RAS-native control plane: service registry with unique audiences, audience-scoped grants and roles ("permission P at audience A"), permission-manifest import with unknown-permission rejection (explicit grant_custom escape hatch), pluggable ServiceIdentityVerifier (constant-time static-secret dev verifier shipped; production workload-identity adapters plug into the same trait), fail-closed TokenIssuer with topology-policy edge enforcement, append-only audit events that never contain secrets, embedded axum authority routes (POST /auth/token, GET /auth/jwks.json), and RasTokenAuthProvider so existing generated services validate RAS tokens through ras-auth-core unchanged.
  • ras-authorization-gateway (Add optional RAS auth gateway for multi-service browser frontends #14) — optional token-narrowing reverse proxy: local web-session validation via JWKS, deterministic longest-prefix segment-aligned routing (duplicates fail validation, unmatched fail closed), single-audience derived tokens containing only the target audience's session permissions (never invented, never widened, never outliving the session), inbound Authorization/Cookie/Host/hop-by-hop stripping, streaming bodies, derived-token cache keyed by session/subject/audience/authz-version, generated-profile consumption with startup upstream validation. WebSocket upgrades fail closed (501) in v1 — documented limitation.

crates/integration/ (#12)

  • ras-integration-coreTokenSource abstraction with token families, bounds-checked caching TokenManager (family+integration+subject+audience+canonical-scopes+config-version cache keys, early refresh, per-key concurrent-refresh dedup), GrantStore trait with in-memory impl, SecretString redaction throughout, and capability-scoped AuthorizedHttpClients over ras-transport-core that validate outbound hosts before minting tokens and never auto-replay requests.
  • ras-integration-oauth2 — refresh-token flow with grant scope subset checks and transactional rotation persistence, client-credentials flow with audience forwarding, typed ConsentRequired on missing/revoked grants, and a PKCE S256 ConsentFlow with opaque single-use expiring state bound to user/integration/redirect/scopes/verifier.
  • ras-integration-rasRasInternalTokenSource: obtains internal tokens from the authority (in-process EmbeddedAuthority or HTTP HttpAuthority), holds no keys, never mints locally; v1 is service-as-service only with other principal modes failing closed.

crates/topology/ (#15)

  • ras-topology-core + ras-topology-macroras_topology! declares the logical service graph with typed references to manifest functions and generated permission constants. Compile-time: duplicate ids, undeclared references. Build-time: audience uniqueness, per-gateway route conflicts, public-gateway-exposes-private-service (explicit expose_private required), edge permissions checked against target manifests. Emits byte-deterministic artifacts with content-derived ids: authorization policy JSON (loaded by the authority to refuse issuance outside the declared graph), gateway profile TOML (loaded by the gateway with deployment-provided upstream bindings), and Mermaid diagrams.

Example + docs

  • examples/authorization-demo — two real rest_service! services wired end-to-end: topology artifacts constrain the authority and configure the gateway; billing calls invoice with an authority-issued internal token through the generated client; the gateway narrows a web session per route; generated WITH_PERMISSIONS enforcement still applies per operation.
  • Four new mdBook chapters (Service-To-Service Auth, Outbound Integrations, The Auth Gateway, Topology), READMEs and rustdoc on all crates, CHANGELOG entry.

Testing

  • ~120 new tests covering the issue test plans: rotation + emergency key removal, alg:none/algorithm-confusion rejection, cache-key collision resistance across token families and principal modes, concurrent-refresh dedup, refresh-rotation persistence-failure surfacing, consent-state single-use/expiry/binding, all fail-closed issuance paths, narrowing invariants, exposure validation, deterministic artifacts, and cross-crate artifact consumption.
  • Six end-to-end scenarios in the demo, including the negative paths (missing audience permissions → 403, no session → 401, session token at a backend directly → 401, per-operation permission rejection, undeclared topology edges denied).
  • Full workspace: 130 test suites green, clippy clean (zero warnings), cargo fmt --check clean, cargo deny passing, mdBook builds.

Deliberate v1 scope cuts (from the issue review)

  • Add internal binary data-plane transport for low-latency service traffic #16 binary transport: parked entirely.
  • No tenant/context scoping axis; no admin REST API (in-memory management API only); user-delegated internal tokens fail closed (the request/cache model already distinguishes principal modes, so delegation is additive later).
  • Gateway WebSocket proxying deferred (fails closed) pending a narrowing-at-upgrade-time design.
  • ras-identity-session is not yet migrated onto ras-authorization-token signing — that is the planned breaking follow-up, named in the Service-To-Service Auth chapter. Existing session behavior is untouched by this PR.

New crates are all 0.1.0; no existing crate APIs changed (the only touch outside new code is workspace manifest membership/deps, docs, and the topology builder consuming generated ServicePermissions).

Closes #12. Closes #13. Closes #14. Closes #15.

🤖 Generated with Claude Code

JedimEmO and others added 10 commits June 10, 2026 22:35
Foundation crate for the service-coms extension (#12-#15): one claims
model (RasClaims) for web sessions, internal service tokens, and
gateway-derived tokens, distinguished by typ; ES256/EdDSA/HS256 signing
with kid-based KeyRing rotation and JWKS publication (asymmetric only);
strict TokenValidator with algorithm allowlist (asymmetric by default),
key/header algorithm cross-check, issuer/audience/token-type pinning,
and clock-skew-aware exp/nbf handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements the #12 core: TokenSource abstraction with token families,
TokenManager with fail-closed scope/audience bounds, family+subject+
audience+scopes+config-version cache keys, early refresh with skew,
per-key concurrent-refresh dedup, and subject invalidation; GrantStore
trait with in-memory impl; SecretString redaction throughout; and
capability-scoped AuthorizedHttpClient over ras-transport-core that
validates outbound hosts before minting tokens and never auto-replays
requests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t flow

OAuth2/OIDC TokenSource for #12: refresh-token flow with grant-scope
subset checks and transactional rotation persistence, client-credentials
flow with audience forwarding, typed ConsentRequired on missing/revoked
grants, and a PKCE S256 ConsentFlow with opaque single-use expiring
state bound to user/integration/redirect/scopes. All provider traffic
flows through HttpTransport so tests run a fake provider in-process.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed mode)

Implements #13's embedded scope: Principal model with audience-scoped
grants (permission P at audience A), in-memory AuthorizationStore with
roles/bindings/direct grants, manifest import with unknown-permission
rejection (explicit grant_custom escape hatch), pluggable
ServiceIdentityVerifier with a constant-time static-secret dev verifier,
and a fail-closed TokenIssuer minting short-lived single-audience
internal JWTs stamped with authz_version. Supports topology
ServiceGraphPolicy edge constraints, JWKS publication, key rotation with
emergency removal, append-only audit events (no secrets/tokens), an
embedded axum authority router (POST /auth/token, GET /auth/jwks.json),
and RasTokenAuthProvider so existing generated services validate RAS
tokens through ras-auth-core unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RasInternalTokenSource (#12/#13 bridge): obtains internal service
tokens from the RAS authority, never minting locally. EmbeddedAuthority
calls the issuer in-process; HttpAuthority posts to a central
authority's /auth/token. v1 is service-as-service only; other principal
modes fail closed before any authority call. End-to-end test proves a
capability-scoped client attaches an authority-issued single-audience
JWT that validates via the authority's JWKS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements #14: an axum-based reverse proxy that validates ras_web_session
tokens locally via JWKS, matches routes with deterministic longest-prefix
segment-aligned rules (duplicates fail validation, unmatched fail closed),
narrows session permissions to the route's audience, and mints short-lived
single-audience ras_gateway_access tokens that never outlive the session.
The original session token is never forwarded; inbound Authorization/
Cookie/Host/hop-by-hop headers are stripped; bodies stream unbuffered.
Derived-token cache keyed by session/subject/audience/authz-version.
Missing audience permissions fail closed unless a route is explicitly
authenticated-only; WebSocket upgrades fail closed (501) in v1. Consumes
generated topology gateway profiles with startup upstream validation; a
backend_validation_options helper pins issuer/audience/token type for
backends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements #15: a deployment-agnostic topology model with deterministic
build-time validation (unique service ids/audiences, per-gateway route
conflicts, undeclared references, public-gateway exposure of private
services requiring explicit expose_private, edge permissions checked
against target manifests with an explicit custom escape hatch) and
byte-deterministic artifacts with content-derived ids: authorization
policy JSON consumed by ras-authorization-core's ServiceGraphPolicy,
gateway profile TOML consumed by ras-authorization-gateway, and Mermaid
diagrams. The ras_topology! macro generates the validated builder from a
declarative graph with typed references to manifest functions and
generated permission constants; duplicate ids and undeclared references
fail at compile time with spanned errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires the whole extension together with real generated services:
ras_topology! declares the graph; the generated policy artifact
constrains the embedded authority and the generated gateway profile
configures the gateway (deployment-provided upstream bindings). Billing
serves /summary by acquiring a RAS internal token (TokenManager +
RasInternalTokenSource, embedded mode) and calling the generated
InvoiceServiceClient; the gateway narrows alice's web session to
single-audience tokens per route; the invoice service accepts internal
and gateway tokens via composed RasTokenAuthProviders while generated
WITH_PERMISSIONS enforcement still applies per operation. e2e tests
cover the happy paths plus fail-closed cases: missing audience
permissions (403), missing session (401), unknown route (404),
direct backend access with a session token (401), per-operation
permission rejection, and undeclared topology edges denied by the
authority. Topology builder now accepts generated ServicePermissions
directly via IntoManifest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four new mdBook chapters (Service-To-Service Auth, Outbound
Integrations, The Auth Gateway, Topology) covering the deployment
presets, token model, fail-closed semantics, gateway deployment shape
(behind existing ingress, WebSocket limitation), and topology artifact
flow; CHANGELOG entry for the #12-#15 extension crates; README feature
bullet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The documentation-hygiene CI check requires every crate's declared
readme target to exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant