Introducing caipe-authz: one authorization service, migrated safely #2406
sriaradhyula
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
What is changing?
CAIPE is introducing
caipe-authz, a standalone authorization service that gives the BFF and AgentGateway one consistent policy decision point while allowing each path to migrate independently.This is the concrete evolution of the Central Authorization Service proposal in discussion #1778. The service name is
caipe-authz; “central authorization service” describes its architectural role.The implementation is available for review in PR #2395.
Why introduce a separate service?
Today, authorization behavior exists in more than one runtime. Conditional policy makes duplication riskier because every enforcement path must agree on:
caipe-authzcentralizes those responsibilities without requiring a flag-day replacement of the current paths.Architecture at a glance
flowchart LR BFF["CAIPE BFF"] --> BR["BFF migration router"] GW["Envoy / AgentGateway"] --> GR["Gateway migration router"] BR --> LEGACY["Existing evaluators"] GR --> LEGACY BR --> AUTHZ["caipe-authz"] GR --> AUTHZ AUTHZ --> CTX["Trusted context builder"] AUTHZ --> POL["Typed policy templates"] CTX --> FGA["OpenFGA relationships + CEL"] POL --> FGA AUTHZ --> OUTBOX["Durable audit outbox"] OUTBOX --> AUDIT["Audit Service"] AUTHZ --> GRAPH["Sanitized inspection graph"] AUDIT --> UI["CAIPE admin UI"] GRAPH --> UIOne decision core, two transports
The transport adapters normalize different protocols before policy evaluation:
sequenceDiagram autonumber participant App as Application caller participant BFF as CAIPE BFF participant Gateway as Envoy / AgentGateway participant Authz as caipe-authz decision core participant Context as Trusted context resolvers participant FGA as OpenFGA + native CEL participant Outbox as Durable audit outbox alt Application authorization App->>BFF: Protected HTTP request BFF->>Authz: HTTP or batch decision request else Gateway authorization App->>Gateway: MCP tools/call Gateway->>Authz: ext_authz CheckRequest end Authz->>Authz: Normalize subject, action, and resource Authz->>Context: Resolve trusted identity, schema, and request context Context-->>Authz: Typed bounded context and revisions Authz->>FGA: Check relationship with explicit model ID and context FGA-->>Authz: Allowed or denied Authz->>Outbox: Journal one normalized decision event Authz-->>BFF: HTTP decision when BFF is the caller Authz-->>Gateway: gRPC decision when gateway is the callerNeither adapter selects a policy provider or changes failure semantics. A deny
from the shared decision core remains a deny at both enforcement points.
caipe-authzserviceext_authzgRPCcaipe-authzHow conditional policy works
Conditional policies are constructed at two different times:
caipe-authzvalidates the policy, pins the applicable schema, and reconciles condition constants onto an OpenFGA tuple.caipe-authzderives bounded context from authenticated request data. OpenFGA evaluates the relationship and its condition.Administrators and callers cannot submit arbitrary CEL. Caller-provided values are never treated as trusted policy constants.
The existing
model.fgagains named conditions and conditional relations, but remains additive during migration. The OpenFGA bridge changes only enough to pass canonical trusted context for exact-tool checks; broad legacy checks remain context-free until their scope is promoted.Policy creation and request evaluation are separate
sequenceDiagram autonumber actor Admin as Administrator participant UI as Policy UI participant Authz as caipe-authz policy API participant Catalog as Tool schema catalog participant Metadata as Policy metadata participant FGA as OpenFGA participant Request as Later protected request Admin->>UI: Choose exact tool, subject, template, field, and values UI->>Authz: Save typed template instance Authz->>Catalog: Validate field type and current schema hash Catalog-->>Authz: Eligible field and trusted schema hash Authz->>Metadata: Persist RECONCILING revision Authz->>FGA: Write and read-back conditional tuple FGA-->>Authz: Verified condition name and constants Authz->>Metadata: Mark policy ACTIVE Authz-->>UI: Active policy revision Note over Admin,FGA: No request argument is used while authoring policy Request->>Authz: Authenticated call with observed request arguments Authz->>Catalog: Resolve current schema and eligible projection Catalog-->>Authz: Current trusted schema hash Authz->>FGA: Check with typed request context FGA-->>Authz: Relationship and condition result Authz-->>Request: ALLOW or DENYThe tuple stores administrator-controlled constants. The Check request carries
request-derived values, so a caller cannot replace the policy field, allowlist,
or expected schema hash.
Can one service handle both the BFF and gateway?
Yes. The transports differ, but the decision contract is shared:
ext_authzgRPC.Why CEL first instead of Cedar or OPA?
OpenFGA already owns CAIPE relationship authorization. OpenFGA-native CEL lets v1 evaluate relationship and request conditions together without introducing a second authoritative engine or translating semantics between languages.
The provider interface leaves room for Cedar or OPA later, but they are intentionally disabled in v1. Enabling another provider should require a concrete use case, conformance tests, equivalent audit semantics, and an explicit decision about which engine is authoritative.
Parallel rollout
Each BFF or gateway scope advances independently:
LEGACYcaipe-authzdecision requiredSHADOWcaipe-authzruns asynchronously for comparisonCANARYAUTHZcaipe-authzAUTHZ_ONLYcaipe-authzstateDiagram-v2 [*] --> LEGACY LEGACY --> SHADOW: deploy and compare SHADOW --> CANARY: promotion evidence passes CANARY --> AUTHZ: bounded cohort is healthy AUTHZ --> AUTHZ_ONLY: enter retention window CANARY --> SHADOW: routing rollback AUTHZ --> SHADOW: routing rollback SHADOW --> LEGACY: disable comparison AUTHZ_ONLY --> AUTHZ: rollback while legacy release is retained AUTHZ_ONLY --> RETIRED: retention and removal gates pass RETIRED --> [*]AUTHZ_ONLYstops invoking legacy code but does not immediately delete it.RETIREDis a later, separately reviewed removal step.Promotion requires evidence for model compatibility, context-schema compatibility, audit health, semantic parity, provider errors, latency SLOs, ownership, and rollback readiness.
Two independent rollback paths
Physical removal of legacy code is deferred until every applicable scope completes the configured
AUTHZ_ONLYretention period and a compatible rollback release still exists.Audit and visualization
The CAIPE admin UI adds:
caipe-authzoutcomes, reasons, errors, provider codes, and durations.Inspection and audit projections expose structure, identifiers, hashes, and revisions—not tool argument values or condition constants.
Current state and history remain separate
flowchart LR UI["CAIPE authorization explorer"] --> BFF["Admin BFF"] BFF --> Inspect["caipe-authz inspection API"] Inspect --> FGA["OpenFGA current model and tuples"] Inspect --> Metadata["Current policy metadata"] BFF --> Audit["Audit Service query API"] Audit --> History["Decisions, changes, comparisons, revisions"] Inspect --> Current["Current authorization graph"] History --> Overlay["Historical and migration overlays"] Current --> Layers["Joined display layers"] Overlay --> Layers Layers --> UIOpenFGA and active policy metadata define current access. Audit events annotate
that graph with history and rollout evidence; replaying audit history never
creates the current authorization state.
Security and operational invariants
What has been validated?
PR #2395 includes automated proof for:
LEGACY → SHADOW → CANARY → AUTHZ → AUTHZ_ONLYagainst a realcaipe-authzgRPC server and pinned OpenFGAv1.15.1container.The PR description contains the complete test counts and packaging checks.
Feedback requested
We would especially value feedback on:
AUTHZ_ONLYretention period should become defaults?Please use this discussion for architecture and rollout feedback, and PR #2395 for implementation-specific review.
All reactions