You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Scope: Let external/automated clients (CLI, scripts, partner services) ingest into and search Knowledge Bases with consistent, team-scoped RBAC.
KB access today is effectively browser-only; there is real demand to use it programmatically (CLI, automation, partner services, CI).
The building blocks already exist (OpenFGA ReBAC, an ext-authz bridge, and a KB MCP route), but a couple of gaps make external, per-team-scoped access awkward.
We propose two things: (1) a public entry that accepts Authorization: Bearer for programmatic clients, and (2) aligning the RAG server's authorization for service-account callers with what the OpenFGA model and the ext-authz bridge already express, so users and service accounts are scoped identically for both ingest and search.
This is an RFC. Open questions for maintainers at the end.
1. Motivation
Today Knowledge Base access is effectively browser-only. There is growing demand to use the KB programmatically: a CLI/automation that ingests and queries, external/partner services that push content into a team's KB, and CI jobs that keep a KB in sync. The goal: a client authenticating as either a user or a service account should get the same, correctly team-scoped access to both ingest and search.
2. Current architecture (as-is)
Entry points
Surface
Exposure
Auth
UI BFF /api/rag/*
public (the app's front door)
browser session today
rag-server REST /v1/*
in-cluster only (ClusterIP, no route)
validated JWT, then its own RBAC
rag-server MCP /mcp (FastMCP)
in-cluster, fronted by agentgateway route /mcp/knowledge-base
JWT auth + agentgateway ext-authz
Two RBAC layers
Invocation gate: can this caller call this tool/endpoint at all. Enforced for tool calls by agentgateway -> openfga-authz-bridge.
Result filter: which datasources' documents/chunks are returned or writable. Enforced inside rag-server (get_accessible_datasource_ids / inject_kb_filter).
3. Gaps we found
Gap A - service-account callers are not scoped consistently. The RAG server authenticates client-credentials (service-account) tokens but does not apply the same per-resource OpenFGA scoping to them that it applies to human users, and that the ext-authz bridge already applies. The OpenFGA model already defines service_account as a valid principal on knowledge_base/data_source, and the bridge already keys on the service_account subject; the RAG server's own path does not yet build that principal. Net: service-account access is not consistently team-scoped on the REST/MCP path. (Specific mechanics shared with maintainers via the security channel rather than here.)
Gap B - result filtering lives in the RAG server. For search, which chunks are returned is decided inside the RAG server. So consistent scoping for service-account callers depends on Gap A being resolved. Routing through the gateway gates invocation but cannot scope search results.
Gap C - generic datasource upsert vs. ownership tuples.POST /v1/datasource persists owner_team_slug to config but, unlike the /v1/ingest/* endpoints, does not call write_datasource_ownership, so config and OpenFGA can drift (a datasource with an owning team in config but no ownership tuples).
Gap D - no ingest MCP tool. The KB MCP server exposes search, fetch_document, list_datasources, and graph reads, but no ingest tool. Ingestion is REST-only (/v1/ingest/*).
Gap E - no programmatic public entry. The UI BFF's /api/rag/* proxy accepts a browser session only, not Authorization: Bearer, and the RAG server REST API is in-cluster only. So there is no clean public entry for CLI/automation.
4. Proposed architecture (to-be)
Principle
One public entry, one authz model, identity preserved to the data layer.
Public entry: the UI BFF (already public), extended to accept Authorization: Bearer for programmatic clients.
One authz model: OpenFGA ReBAC, applied uniformly to user and service_account principals, at both the invocation gate and the result filter.
The caller's identity is carried unchanged from the front door to the data layer, and every check keys on it.
Access flow
external caller (Bearer = user JWT OR per-partner service-account JWT)
| Authorization: Bearer
v
UI BFF /api/rag/* validate JWT + role gate + forward same token
v
rag-server (REST or MCP)
| OpenFGA principal = user:{sub} OR service_account:{sub}
v
OpenFGA check: can_ingest / can_read on knowledge_base | data_source
v
scoped to the caller's team(s)
ingest -> writes ownership tuples for owner_team
search -> returns only readable chunks
The agentgateway KB route already exists (deploy/agentgateway/config.yaml, /mcp/knowledge-base to rag-server:9446/mcp, with ext-authz to openfga-authz-bridge). It is wired but bypassed by default. The bridge gates invocation; the result filter for search still lives in the RAG server (Gap B), so Gap A must be resolved for scoped search regardless of path.
Core change (the single unlock)
Align the RAG server's authorization so service-account callers are scoped like users: build a service_account principal (matching the ext-authz bridge) and run the same OpenFGA checks. Preserve a narrow trusted-service allowance for the in-cluster connector ingestors that legitimately need cross-KB writes (an allowlist of trusted client IDs or a dedicated role, rather than a blanket exception). This makes ingest and search behave identically for users and service accounts, with no new BFF and no MCP-client machinery.
Identity options (both scoped by OpenFGA team grants)
Per-partner service account: one confidential client per external party, client_credentials; scoped by service_account team grants. Enabled by the core change.
Optional (secondary)
Expose ingestion as an MCP tool so it can also flow through the agent/gateway path (invocation-gated by a tool object for ingest). Not required for the REST path once the core change lands.
5. Why
Consistency: the RAG server aligns with the OpenFGA model (which already defines service_account) and with the gateway bridge (which already scopes it).
Least privilege: external/multi-tenant callers no longer need an over-broad credential; a per-partner credential is limited to that partner's team.
Simplicity: the public REST path stays a plain HTTP forward; no MCP client or separate BFF needed for correct scoping.
Uniformity: the same change makes ingest and search behave identically for users and service accounts.
6. Proposed change set
Align RAG-server RBAC for service accounts: build the service_account principal and run the standard OpenFGA checks, with a trusted-service allowlist/role to preserve the in-cluster connector ingestors that currently need cross-KB writes.
BFF public entry accepts Authorization: Bearer on /api/rag/* (in addition to the browser session): validate the JWT, enforce the role gate, forward the same token to the RAG server.
POST /v1/datasource writes ownership tuples when owner_team_slug is set (mirror the ingest endpoints), or document it explicitly as admin/connector-only.
(Optional) ingestion MCP tool with a tool object for invocation-gating on the agent/gateway path.
7. Open questions for maintainers
Is the current service-account behavior on the RAG-server path intentional for trusted in-cluster connectors? If so, is an allowlist / trusted-role the preferred way to keep that while scoping other service accounts?
Preferred external identity model: per-partner service accounts, real users, or both?
Should ingestion be exposed as an MCP tool (agent/gateway path), or kept REST-only behind the BFF?
Any objection to POST /v1/datasource writing ownership tuples (vs. documenting it as connector-only)?
Appendix - relevant areas (upstream)
ai_platform_engineering/knowledge_bases/rag/server/src/server/rbac.py - RAG-server authorization (principal resolution, per-resource checks, result filtering)
ai_platform_engineering/knowledge_bases/rag/server/src/server/restapi.py - /v1/ingest/*, /v1/datasource, /v1/query, FastMCP mount at /mcp
ai_platform_engineering/knowledge_bases/rag/server/src/server/tools.py - MCP tools (search/fetch/list/graph; no ingest)
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Status: Proposal for discussion (RFC)
Scope: Let external/automated clients (CLI, scripts, partner services) ingest into and search Knowledge Bases with consistent, team-scoped RBAC.
Authorization: Bearerfor programmatic clients, and (2) aligning the RAG server's authorization for service-account callers with what the OpenFGA model and the ext-authz bridge already express, so users and service accounts are scoped identically for both ingest and search.1. Motivation
Today Knowledge Base access is effectively browser-only. There is growing demand to use the KB programmatically: a CLI/automation that ingests and queries, external/partner services that push content into a team's KB, and CI jobs that keep a KB in sync. The goal: a client authenticating as either a user or a service account should get the same, correctly team-scoped access to both ingest and search.
2. Current architecture (as-is)
Entry points
/api/rag/*/v1/*/mcp(FastMCP)/mcp/knowledge-baseTwo RBAC layers
get_accessible_datasource_ids/inject_kb_filter).3. Gaps we found
service_accountas a valid principal onknowledge_base/data_source, and the bridge already keys on theservice_accountsubject; the RAG server's own path does not yet build that principal. Net: service-account access is not consistently team-scoped on the REST/MCP path. (Specific mechanics shared with maintainers via the security channel rather than here.)POST /v1/datasourcepersistsowner_team_slugto config but, unlike the/v1/ingest/*endpoints, does not callwrite_datasource_ownership, so config and OpenFGA can drift (a datasource with an owning team in config but no ownership tuples).search,fetch_document,list_datasources, and graph reads, but no ingest tool. Ingestion is REST-only (/v1/ingest/*)./api/rag/*proxy accepts a browser session only, notAuthorization: Bearer, and the RAG server REST API is in-cluster only. So there is no clean public entry for CLI/automation.4. Proposed architecture (to-be)
Principle
One public entry, one authz model, identity preserved to the data layer.
Authorization: Bearerfor programmatic clients.userandservice_accountprincipals, at both the invocation gate and the result filter.Access flow
Sequence - ingest (REST via BFF, primary path)
Sequence - search (REST via BFF; result filtering happens in rag-server)
Sequence - optional MCP via agentgateway (reuses the existing
/mcp/knowledge-baseroute)Core change (the single unlock)
Align the RAG server's authorization so service-account callers are scoped like users: build a
service_accountprincipal (matching the ext-authz bridge) and run the same OpenFGA checks. Preserve a narrow trusted-service allowance for the in-cluster connector ingestors that legitimately need cross-KB writes (an allowlist of trusted client IDs or a dedicated role, rather than a blanket exception). This makes ingest and search behave identically for users and service accounts, with no new BFF and no MCP-client machinery.Identity options (both scoped by OpenFGA team grants)
client_credentials; scoped byservice_accountteam grants. Enabled by the core change.Optional (secondary)
toolobject for ingest). Not required for the REST path once the core change lands.5. Why
service_account) and with the gateway bridge (which already scopes it).6. Proposed change set
service_accountprincipal and run the standard OpenFGA checks, with a trusted-service allowlist/role to preserve the in-cluster connector ingestors that currently need cross-KB writes.Authorization: Beareron/api/rag/*(in addition to the browser session): validate the JWT, enforce the role gate, forward the same token to the RAG server.POST /v1/datasourcewrites ownership tuples whenowner_team_slugis set (mirror the ingest endpoints), or document it explicitly as admin/connector-only.toolobject for invocation-gating on the agent/gateway path.7. Open questions for maintainers
POST /v1/datasourcewriting ownership tuples (vs. documenting it as connector-only)?Appendix - relevant areas (upstream)
ai_platform_engineering/knowledge_bases/rag/server/src/server/rbac.py- RAG-server authorization (principal resolution, per-resource checks, result filtering)ai_platform_engineering/knowledge_bases/rag/server/src/server/restapi.py-/v1/ingest/*,/v1/datasource,/v1/query, FastMCP mount at/mcpai_platform_engineering/knowledge_bases/rag/server/src/server/tools.py- MCP tools (search/fetch/list/graph; no ingest)deploy/openfga/model.fga-knowledge_base,data_source,service_account,tooltypesdeploy/openfga/bridge/main.py- ext-authz caller keying (service_account vs user)deploy/agentgateway/config.yaml-/mcp/knowledge-baseroute + ext-authzui/src/app/api/rag/[...path]/route.ts- BFF RAG proxy (public entry)All reactions