Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions docs/docs/security/rbac/cas-coverage-gaps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# CAS Coverage and Migration Gaps

**Status:** Living inventory, last reviewed 2026-08-15.

**CAS** means the CAIPE **Centralized Authorization Service** in
`ui/src/lib/authz`. Compare-and-set is a separate mechanism used when publishing
authorization reconciliation state.

## Target state

Runtime authorization decisions and relationship mutations should pass through
CAS so every path gets the same:

- product-policy pre-checks, including private-resource interaction rules;
- OpenFGA capability mapping;
- decision and mutation audit records;
- cache invalidation after relationship changes;
- fail-closed error behavior.

```mermaid
flowchart LR
BFF["BFF resource-scoped routes"] --> CAS["CAS"]
DA["Dynamic Agents chat"] --> CAS
WF["Workflow engine"] --> CAS
CAS --> FGA[(OpenFGA)]

GW["AgentGateway authz bridge"] -. "direct checks" .-> FGA
LEG["Legacy BFF gates"] -. "direct checks" .-> FGA
RAG["RAG server"] -. "direct checks and writes" .-> FGA
BOT["Slack and Webex routing"] -. "direct reads and writes" .-> FGA
MUT["Bootstrap, team, service-account mutations"] -. "direct writes" .-> FGA
```

Solid lines are CAS-backed. Dotted lines are migration gaps unless listed as an
intentional exception below.

## Current CAS coverage

| Surface | CAS entry point | Notes |
|---|---|---|
| BFF resource decisions | `ui/src/lib/rbac/resource-authz.ts` | `requireResourcePermission`, `filterResourcesByPermission`, and related helpers use CAS by default. A test-injected `check` function deliberately bypasses CAS. |
| Dynamic Agents chat execution | `ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/authz.py` | Sends the caller-bound decision to `/api/authz/v1/decisions` and fails closed if CAS is unavailable. |
| Workflow authorization | `ui/src/lib/server/workflow-cas-authz.ts` | Uses in-process `authorize` and `authorizeMany`. |
| Shareable-resource reconciliation | `ui/src/lib/rbac/openfga-owned-resources-reconcile.ts` | Agent, MCP server, credential, and other migrated lifecycles call `reconcileTupleDiff`. |
| CAS tuple reconciliation | `ui/src/lib/authz/reconcile.ts` | Writes the OpenFGA diff, invalidates the CAS decision cache, and emits a reconciliation audit event. |

## Active gaps

### 1. AgentGateway MCP authorization

`deploy/openfga/bridge/main.py` calls OpenFGA directly for gateway, MCP server,
owner, agent, and tool capabilities.

This is the highest-risk gap because it is a runtime data-plane enforcement
point. It cannot safely call the existing public, subject-bound CAS endpoint:
AgentGateway may consume the bearer and send only verified identity metadata to
`ext_authz`.

**Migration requirement:** add an authenticated internal CAS protocol that
accepts only gateway-verified identity and trusted interaction context. Do not
accept a caller-supplied subject header as proof of identity.

### 2. Legacy BFF organization and admin gates

The following helpers perform direct OpenFGA checks instead of calling CAS:

- `requireRbacPermission` in `ui/src/lib/api-middleware.ts`;
- `requireAdminSurfaceManage`, `requireBaselineAdminSurfaceRead`, and profile
helpers in `ui/src/lib/rbac/require-openfga.ts`;
- `requireAgentUsePermission` in
`ui/src/lib/rbac/openfga-agent-authz.ts`;
- direct `checkOpenFgaTuple` calls in route handlers and legacy RBAC helpers.

The resource-scoped BFF decision may already use CAS while an outer, coarse
organization gate still uses direct OpenFGA. That hybrid path is protected, but
it produces two authorization implementations and inconsistent audit semantics.

### 3. Slack and Webex authorization

Direct-message agent selection calls `/api/user/check_agent_access`. That route
applies the private-resource context rule locally, then uses the direct-OpenFGA
`evaluateAgentAccess` helper.

Channel and space paths also access OpenFGA directly:

- Slack routing and auto-assignment:
`ai_platform_engineering/integrations/slack_bot/utils/slack_agent_routes.py`
and `slack_channel_auto_assign.py`;
- Webex routing and auto-assignment:
`ai_platform_engineering/integrations/webex_bot/utils/webex_agent_routes.py`
and `webex_space_auto_assign.py`;
- Slack/Webex admin synchronization writers in the bot admin API modules.

**Migration requirement:** pass server-derived `surface` and
`conversationKind` to CAS. Group paths must never fall back to a direct human
owner grant.

### 4. RAG authorization

`ai_platform_engineering/knowledge_bases/rag/server/src/server/rbac.py`
implements direct OpenFGA checks, list-objects calls, tuple reads, and tuple
writes for data sources, knowledge bases, MCP tools, team capabilities, and
organization administration.

The BFF proxy may perform a CAS-backed resource check before forwarding, but
the RAG server remains an independent policy implementation.

### 5. Relationship mutations outside CAS

Direct `writeOpenFgaTuples`, `writeOpenFgaTupleDiff`, and
`deleteExactOpenFgaTuples` calls remain in these lifecycle families:

- service-account create, scope, revoke, and cleanup;
- team membership, team capabilities, archived-team cleanup, and identity sync;
- Slack channel and Webex space routes, resources, teams, and defaults;
- conversation sharing and implicit conversation grants;
- login, bootstrap-admin, super-admin, and unlinked-service-account repair;
- Tome administrator/data-steward reconciliation;
- RBAC migrations, repair, and backfill paths.

These mutations reach OpenFGA but skip CAS cache invalidation and the canonical
`cas_reconcile` audit event. Product lifecycle writers should migrate to
`reconcileTupleDiff`. Temporary probe grants should use an explicitly scoped CAS
helper with guaranteed cleanup.

## Intentional direct OpenFGA operations

Not every direct call is a migration defect. These operations may remain direct
when they are isolated from product authorization decisions:

- OpenFGA store creation, model publication, and first-install seed tuples;
- raw OpenFGA administration, graph inspection, explain diagnostics, and
self-check tooling;
- read-only drift reports and migration planning;
- the low-level OpenFGA adapter used internally by CAS.

Mutation-capable administrative tools still require a CAS-backed meta-
authorization gate and must identify their direct writes in audit logs.

The legacy Dynamic Agents module
`dynamic_agents/auth/openfga_authz.py` also calls OpenFGA directly, but the
active chat routes import `dynamic_agents/auth/authz.py`, which uses CAS. Treat
the legacy module as removal work, not an active enforcement gap.

## Source snapshot

The following counts are overlapping indicators, not a total number of unique
gaps. They were measured on 2026-08-15:

| Indicator | API route files |
|---|---:|
| Uses `requireRbacPermission` | 78 |
| Uses a `require-openfga` admin/profile helper | 21 |
| Uses `requireAgentUsePermission` | 5 |
| Directly references `checkOpenFgaTuple` | 17 |
| Directly references an OpenFGA tuple mutation helper | 30 |

Reproduce the inventory from the repository root:

```bash
rg -l 'requireRbacPermission' ui/src/app/api -g '*.ts' -g '!**/__tests__/**' \
-g '!**/*.test.ts'
rg -l 'requireAdminSurfaceManage|requireBaselineAdminSurfaceRead|requireUserProfileRead' \
ui/src/app/api -g '*.ts' -g '!**/__tests__/**' -g '!**/*.test.ts'
rg -l 'requireAgentUsePermission' ui/src/app/api -g '*.ts' \
-g '!**/__tests__/**' -g '!**/*.test.ts'
rg -l 'checkOpenFgaTuple' ui/src/app/api -g '*.ts' -g '!**/__tests__/**' \
-g '!**/*.test.ts'
rg -l 'writeOpenFgaTuples|writeOpenFgaTupleDiff|deleteExactOpenFgaTuples' \
ui/src/app/api -g '*.ts' -g '!**/__tests__/**' -g '!**/*.test.ts'
rg -n 'OPENFGA|openfga' ai_platform_engineering -g '*.py' -g '!**/test_*.py'
```

Review each match: imports, read-only diagnostics, CAS's own adapter, and test
injection points are not product-policy bypasses.

## Migration order

1. Add an authenticated internal CAS transport for AgentGateway.
2. Move `/api/user/check_agent_access` and the active bot decision paths to CAS.
3. Move RAG data-plane decisions to CAS.
4. Replace legacy BFF organization/admin gates with CAS actions.
5. Route service-account, team, channel, space, and conversation mutations
through CAS reconciliation.
6. Retire dead direct-OpenFGA modules and then review bootstrap/migration tools.

## Related documentation

- [CAS implementation architecture](../../specs/2026-06-06-cas-implementation/architecture.md)
- [PDP coverage audit](./pdp-coverage-audit.md)
- [OpenFGA permission evaluation](./openfga-permission-evaluation.md)
1 change: 1 addition & 0 deletions docs/docs/security/rbac/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This is the canonical reference for how authentication and authorization work in
| Get the short end-to-end summary of the Comprehensive RBAC refactor, including Keycloak roles, AgentGateway, and OpenFGA | [Comprehensive RBAC Refactor](./comprehensive-rbac-refactor.md) |
| Understand how JWT identity and OpenFGA relationship checks work together | [JWT and OpenFGA](./jwt-and-openfga.md) |
| Understand exactly where OpenFGA union/computed permissions are evaluated, what gets stored vs. computed, and follow a worked end-to-end Probe-button example | [OpenFGA Permission Evaluation](./openfga-permission-evaluation.md) |
| See which authorization decisions and relationship mutations still bypass CAS, why, and in what order to migrate them | [CAS Coverage and Migration Gaps](./cas-coverage-gaps.md) |
| Trace a request — login, OBO token-exchange, end-to-end Slack/Webex flow, Slack channel or Webex space → agent routing | [Workflows](./workflows.md) |
| Log in, exercise a role, verify a denial, link a Slack/Webex user, run the demo | [Usage](./usage.md) |
| Find the file that owns a specific piece of the auth path | [File map](./file-map.md) |
Expand Down
18 changes: 11 additions & 7 deletions docs/docs/specs/2026-06-06-cas-implementation/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# CAS Architecture Diagrams

**Date:** 2026-06-07
**Last reviewed:** 2026-08-15
**Companion to:** [`spec.md`](./spec.md) · [`research.md`](./research.md) · [`../2026-06-05-centralized-bff-authz-pdp-agnostic/solution-architecture.md`](../2026-06-05-centralized-bff-authz-pdp-agnostic/solution-architecture.md)

> One decision core, multiple transports, PDP-agnostic. Additive today; consumers migrate incrementally.
Expand Down Expand Up @@ -31,7 +32,7 @@ flowchart TB
AD --> P
end

DA["Dynamic Agents — next"] -->|HTTP + user JWT| V1
DA["Dynamic Agents chat ✓ migrated"] -->|HTTP + user JWT| V1
RB["RAG · Slack · Webex — later"] -->|HTTP| V1
OFA --> STORE[("OpenFGA store")]
AU --> M[("audit_events — Mongo")]
Expand All @@ -43,7 +44,10 @@ flowchart TB
**Key points**
- The **OpenFGA adapter is private** to `lib/authz` (ESLint silo boundary). Swapping in Cedar/OPA later = a new adapter behind the same `PolicyEngine` / `PolicyAdmin` interfaces.
- The **bridge** stays data-plane (per-MCP-call `ext_authz`); it does **not** route through the BFF.
- DA/RAG/bots are **not migrated yet** — only the in-process `workflow-runs` route consumes CAS today.
- Dynamic Agents chat and the workflow engine use CAS. Standard BFF
resource-scoped helpers also use CAS by default.
- RAG, bots, legacy BFF gates, and the bridge are not fully migrated. See the
[living CAS gap inventory](../../security/rbac/cas-coverage-gaps.md).

---

Expand Down Expand Up @@ -104,14 +108,14 @@ sequenceDiagram
```mermaid
flowchart LR
subgraph now["Today (additive)"]
n1["workflow-runs route → CAS"]
n2["legacy paths untouched<br/>DA openfga_authz · RAG rbac · bridge"]
n1["workflow engine · DA chat<br/>BFF resource helpers → CAS"]
n2["legacy paths remain<br/>RAG · bots · bridge · coarse BFF gates"]
end
subgraph next["Incremental"]
x1["DA → CAS HTTP<br/>(delete openfga_authz, workflow_execution_authz)"]
x1["Internal CAS transport<br/>for AgentGateway"]
x2["RAG → CAS"]
x3["bots → CAS"]
x4["bridge: align vocabulary/audit"]
x3["bots and legacy BFF gates → CAS"]
x4["relationship mutations<br/>→ CAS reconcile"]
end
now --> next
```
Expand Down
Loading