Skip to content

Add RAS-native authorization control plane and internal service token issuer #13

@JedimEmO

Description

@JedimEmO

Summary

Add a RAS-native authorization control plane and internal service token issuer.

External identity providers such as Auth0, Entra, Google, or Okta should authenticate human users only. RAS should own application-level authorization: users, internal services, service accounts, applications, roles, grants, permissions, optional tenant/context scope, and audit events.

For internal service-to-service calls, services should be configured entirely in RAS. They should not require Auth0/Entra application/client setup. A service proves its identity to RAS through a pluggable verifier, RAS resolves locally managed permissions, and RAS issues signed JWTs that downstream services validate through JWKS.

This complements #12, which tracks the outbound token/client framework. This issue defines the RAS-controlled source of truth and internal token issuer that #12 can consume through a RasInternalTokenSource.

This must support a progressive deployment model. The default experience should work inside one Axum/RAS service, with auth authority routes mounted in the same process. Central authority and gateway deployments should be opt-in migration paths, not prerequisites.

For multi-service systems, #15 should provide the logical topology macro/artifacts that this authority can consume as service graph and permission policy input.

Problem

RAS already has a useful split between identity and permissions:

  • external or local identity providers verify who a user is
  • service macros declare required permission strings
  • sessions carry permissions and generated services enforce them
  • permission manifests expose the permission vocabulary

The missing piece is a RAS-native management layer for internal tools where teams do not want to manage application roles, service accounts, internal service clients, and service permissions inside an external IdP.

Today, internal systems often end up with one of these awkward choices:

  • push app-specific roles and service clients into Auth0/Entra
  • hard-code service tokens and permissions per project
  • duplicate role/grant stores in every app
  • rely on long-lived secrets without a central audit trail
  • manually coordinate service-to-service auth across projects

The desired model:

  • external IdP answers: "who is this human?"
  • RAS answers: "what can this user/service/application do here?"
  • RAS issues internal service tokens for internal services
  • internal services validate RAS tokens locally

Proposed Direction

Add a new authorization crate family, likely under crates/authorization/:

  • ras-authorization-token
    • shared RAS token claims, token families/types, audience model, permission grouping, signing, verification, JWKS, key ID, key rotation, clock-skew, and validation helpers
    • used by embedded sessions, central authority service tokens, gateway-derived backend tokens, and downstream validators
    • should be designed before the larger control-plane and gateway pieces so token shape does not diverge across crates
  • ras-authorization-core
    • principal, role, grant, service registration, IdP mapping, and audit types
    • AuthorizationStore, AuthorizationResolver, AuditSink
    • ServiceIdentityVerifier trait for pluggable service identity proof
    • TokenIssuer and JwksProvider traits/types
    • in-memory store/verifier for tests and examples
  • ras-authorization-session
    • adapter(s) for existing RAS identity/session flow
    • ManagedUserPermissions implementing existing UserPermissions
    • ManagedAuthProvider or equivalent adapter that validates sessions/service tokens and resolves current permissions
  • ras-authorization-admin
    • reusable RAS REST admin API for services, applications, service accounts, roles, bindings, grants, IdP mapping rules, and audit queries
    • protected by RAS permissions such as authorization:read, authorization:write, and authorization:admin
  • ras-authorization-gateway-support
    • shared claims, token narrowing, validation, and route/audience config types used by the optional auth gateway tracked separately
    • does not require applications to deploy a gateway
  • examples/authorization-admin
    • demonstrates external user login mapped into local RAS roles
    • demonstrates internal service registration and service-token issuance
    • demonstrates internal service-to-service authorization without Auth0/Entra app/client setup
    • demonstrates embedded authority mode in one Axum server

Deployment Presets

Document and support three progressive presets:

  1. Embedded App Auth: default for one service, internal tools, demos, and small apps. One Axum server hosts both /auth/* authority routes and app /api/* routes. The service validates its own RAS web session cookie/JWT and enforces generated RAS permissions directly. No gateway or external auth service is required.
  2. Central Authority: shared RAS auth service for multiple backend services. Services validate RAS-issued tokens through the central authority's JWKS/config and use a service-specific audience.
  3. Auth Gateway: optional reverse proxy/token exchanger for browser frontends that fan out to multiple backend services. The gateway validates a RAS web session, narrows claims to the target audience, mints short-lived service-specific tokens, and forwards only the narrowed token. This is tracked separately and must not be required for embedded or central-authority use.

Recommended docs path:

Start with Embedded.
Move to Central Authority when multiple services need shared authz.
Add Gateway only when browser traffic fans out to multiple services and backends should not see cross-service permissions.

Core Model

Principals:

enum Principal {
    User { user_id: String },
    ServiceAccount { service_account_id: String },
    Application { application_id: String },
}

Scopes:

enum AuthzScope {
    Global,
    Tenant(String),
}

Service registration:

struct ServiceRegistration {
    service_id: String,
    display_name: String,
    owners: Vec<Principal>,
    allowed_audiences: Vec<String>,
    allowed_permissions: Vec<String>,
}

Service identity:

#[async_trait]
trait ServiceIdentityVerifier {
    async fn verify(&self, proof: ServiceIdentityProof) -> Result<VerifiedServiceIdentity, AuthzError>;
}

The trait should support simple development proofs and production-grade adapters:

  • static client ID/secret for local/dev/internal tools
  • mTLS certificates
  • Kubernetes service-account JWTs
  • SPIFFE/SPIRE identity
  • cloud workload identity

Token issuance:

struct InternalTokenRequest {
    requester: Principal,
    audience: String,
    permissions: Vec<String>,
    scope: AuthzScope,
}

RAS verifies that the requester is registered and locally granted the requested audience/permissions, then issues a signed JWT with:

  • issuer
  • subject/principal
  • audience
  • permissions
  • optional tenant/context
  • issued-at and expiry
  • key ID

Downstream services validate the token locally using RAS JWKS.

For browser sessions, RAS may issue a signed web session JWT/cookie containing audience-scoped permission data so local validation can avoid hot-path calls to the authority. For multi-service browser deployments, the optional auth gateway can narrow this multi-audience session into a single-audience backend token.

Token And Grant Shape Clarifications

The authorization layer should establish one shared RAS token model instead of letting sessions, service tokens, and gateway tokens grow separate claim conventions.

Required token concepts:

  • token type/family, for example web session, internal service access, and gateway-derived backend access
  • issuer, subject/principal, audience, issued-at, not-before where used, expiry, key ID, and algorithm allowlist
  • optional tenant/context and authorization/session version
  • permissions grouped by audience for web/session tokens
  • single-audience permissions for internal service tokens and gateway-derived backend tokens

Grants and role resolution should be scoped by target service/audience as well as permission string. A grant should answer "principal X can request permission P for audience A" rather than relying only on globally unique permission strings. This avoids accidental permission collision across services and lines up with topology policy from #15.

The model should distinguish at least these internal call modes:

  • service-as-service: billing-service calls invoice-service using billing's own service identity and grants
  • user-delegated service call: billing-service calls invoice-service on behalf of user alice, constrained by both the service edge/grant and Alice's delegated permissions
  • application/service-account call: a non-human principal calls an internal service under explicitly granted permissions

User-delegated service-to-service calls do not need to be fully polished in v1, but the token/request model should leave a clear path for them so #12 does not have to invent a parallel convention later.

Generated and imported policy artifacts, including permission manifests and #15 topology policy, should carry explicit schema versions and stable IDs so authorization decisions can be audited and rolled forward deliberately.

Relationship To #12

Issue #12 should provide the outbound token/client framework:

  • TokenManager
  • TokenSource
  • token caching
  • bearer attachment
  • host/audience validation
  • test fakes

This issue should provide the RAS-controlled internal token source:

RasInternalTokenSource

Flow for internal services:

billing-service
  -> asks RAS for token to call invoice-service
  -> RAS verifies billing-service identity
  -> RAS checks local grants/roles/audience
  -> RAS issues signed JWT
  -> billing-service calls invoice-service
  -> invoice-service validates JWT via RAS JWKS

No Auth0/Entra service app/client configuration is required for internal services.

The optional auth gateway/proxy issue should provide the browser-facing multi-service pattern:

browser RAS web session
  -> gateway validates locally
  -> gateway narrows permissions to route audience
  -> gateway mints/caches short-lived single-audience token
  -> backend validates simple service-specific token

Issue #15 should generate the topology policy/config that binds service audiences, gateway route profiles, and allowed service-to-service edges. This authority should be able to load those artifacts and refuse token issuance outside the declared topology.

Security Requirements

  • Treat this control plane as critical infrastructure. Compromise of admin APIs, signing keys, or storage can allow broad internal impersonation and privilege escalation.
  • Separate admin permissions from token issuance permissions. Managing roles/grants, managing services, managing signing keys, and issuing service tokens should not collapse into one broad permission.
  • External IdP claims/groups are inputs to mapping rules, not the source of truth for app authorization.
  • RAS local roles/grants are authoritative for application and service permissions.
  • Permission grants must be scoped by target audience/service and permission; permission strings alone are not enough as the long-term authorization key.
  • Service token requests must fail closed when the service is unknown, disabled, outside its allowed audience, or requesting undeclared/ungranted permissions.
  • JWTs must be short-lived by default.
  • JWT validation must check issuer, audience, expiry, not-before when present, signature, key ID, algorithm allowlist, token type, tenant/context, and required permission groups.
  • Signing keys must support rotation through JWKS.
  • Signing keys should be isolated from ordinary application data storage where possible, and backup/export paths must be treated as sensitive.
  • Admin mutations must emit audit events.
  • Audit events must never include raw secrets or token values.
  • Admin APIs must reject privilege escalation where the caller tries to assign permissions they are not allowed to administer, unless they hold a dedicated break-glass/super-admin permission.
  • Tenant/context-scoped grants must not bleed into global or other-tenant authorization.
  • Permission assignments should default to permissions known from imported RAS permission manifests. Custom/manual permissions must be explicitly marked as such.
  • Topology-generated policy from Add RAS topology macro for compile-checked service graphs and generated artifacts #15 should be versioned and treated as authorization input for service-to-service token issuance.
  • Service identity verifier implementations must make replay and credential leakage risks explicit.
  • Static service secrets are supported only as a development/simple-deployment verifier; production guidance should prefer mTLS, SPIFFE/SPIRE, Kubernetes service-account JWTs, or cloud workload identity.
  • Service identity proofs should be bound to service ID, environment/issuer, audience, and expiry where the verifier supports those concepts.
  • Revocation is not immediate with offline JWT validation; default token TTLs, emergency signing-key rotation, and any optional future introspection path must be documented clearly.
  • Multi-audience browser/session tokens must keep permissions grouped by audience. A backend service must never be required to parse or reason about permissions for other audiences.
  • Gateway-derived service tokens must contain only the target audience and that audience's permissions.

Acceptance Criteria

  • Internal services/applications can be registered in RAS.
  • Shared token/JWKS primitives exist for web sessions, internal service tokens, gateway-derived tokens, and downstream validators.
  • Embedded authority mode works in a single Axum/RAS service with /auth/* and application routes in the same process.
  • Central authority mode lets services validate RAS-issued tokens through JWKS/config without embedding the authority routes.
  • Users, service accounts, and applications can receive roles and direct permission grants.
  • Permissions can be resolved from local RBAC plus explicit grants.
  • Grants and resolved permissions are scoped by target audience/service as well as permission.
  • Role bindings and direct grants support optional tenant/context scoping.
  • External IdP claims/groups can be mapped into local RAS roles through constrained mapping rules.
  • Generated RAS permission manifests can be imported, and permission assignments reject unknown permissions by default.
  • Topology policy artifacts from Add RAS topology macro for compile-checked service graphs and generated artifacts #15 can be imported to constrain service-to-service token issuance by declared service graph edges.
  • RAS can issue signed JWTs for internal service-to-service calls.
  • RAS exposes JWKS for downstream local token validation.
  • Downstream validation verifies issuer, audience, expiry, not-before when present, signature, key ID, algorithm allowlist, token type, tenant/context, and required permission groups.
  • Services cannot request undeclared or ungranted permissions.
  • Services cannot request tokens for unallowed audiences.
  • Static-secret service identity is clearly marked as dev/simple mode, while the verifier trait supports production workload identity adapters.
  • Role/grant changes affect newly issued tokens; short TTL limits stale tokens.
  • Service registration, grant assignment, token issuance failure, revocation, signing-key changes, and admin changes emit audit events.
  • Admin APIs prevent non-super-admin callers from granting themselves or others permissions they cannot administer.
  • Audit events are append-only at the API level and omit raw token/secret values.
  • Signing-key rotation through JWKS is supported and tested.
  • In-memory store and verifier implementations are available for tests and examples.
  • A worked example shows internal service-to-service auth with no Auth0/Entra app/client setup.
  • Documentation presents Embedded, Central Authority, and Auth Gateway as progressive presets with Embedded as the default recommendation.

Test Plan

  • Unit-test role resolution, direct grants, role bindings, optional tenant scope, and unknown permission rejection.
  • Test manifest import and default rejection of assignments to unknown permissions.
  • Test topology policy import from Add RAS topology macro for compile-checked service graphs and generated artifacts #15 and rejection of token issuance outside declared service graph edges.
  • Test IdP mapping rules for provider/issuer/claim/group matching and non-matching cases.
  • Test tenant isolation: tenant-scoped roles do not grant access globally or in other tenants.
  • Test service identity verification through a mock verifier and static-secret dev verifier.
  • Test service identity proof rejection for wrong audience, wrong issuer/environment, expired proof, replayed proof where supported, and mismatched service ID.
  • Test token issuance for registered service with allowed audience/permissions.
  • Test grants with the same permission string under different target audiences do not satisfy each other accidentally.
  • Test token issuance rejection for unknown service, disabled service, unallowed audience, and ungranted permission.
  • Test JWT validation rejection for wrong issuer, wrong audience, expired token, wrong token type, wrong tenant/context, disallowed algorithm, missing/unknown key ID, and insufficient permission group.
  • Test JWKS validation and key rotation behavior, including old/new key overlap and emergency key removal behavior.
  • Test admin API authorization, audit event emission, revocation, and privilege-escalation rejection.
  • Test audit payloads redact/omit secrets and are append-only through the public API.
  • Test embedded authority mode by mounting auth/session routes and generated API routes in one Axum app.
  • Test central-authority validation mode using JWKS/config without local authority routes.
  • Test multi-audience session permission grouping and derived single-audience token narrowing behavior at the shared type/helper level.
  • Add docs/example tests showing a RAS service importing generated permission manifests and assigning those permissions through local roles.

Non-Goals For v1

  • Do not replace external IdPs for human authentication.
  • Do not build a polished built-in admin UI in v1; ship APIs and an example first.
  • Do not ship SQL/Redis/Vault storage adapters in v1; keep storage trait-based with in-memory test/dev implementation.
  • Do not implement ABAC or relationship/Zanzibar-style authorization in v1.
  • Do not require introspection for every internal request; JWT + JWKS is the default validation path.

Notes

The goal is to make internal tooling simple: configure users, apps, services, roles, grants, and internal service tokens in RAS, while leaving external IdPs to authenticate humans only.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions