Repository navigation
Security RBAC
This document describes Felix RBAC mutation and delegation semantics.
- Keep policy evaluation simple and predictable (allow rules only).
- Prevent privilege escalation by enforcing scope containment at write time.
- Separate RBAC-administration permissions from resource-operation permissions.
- Keep tenant boundaries explicit in every object string.
Canonical actions:
rbac.viewrbac.policy.managerbac.assignment.managetenant.managens.managestream.managecache.managestream.publishstream.subscribecache.readcache.write-
node.view— cluster-scoped only; see Cluster scope -
node.manage— overnode:{node_id}orcluster:*
There is no separate action for queues. A consumer-group operation is
authorized as stream.subscribe on the stream it reads — poll, acknowledge,
hand back, and the dead-letter requests alike. A group is a read position over a
stream, so granting a consumer stream.subscribe is what lets it work the
queue, and there is nothing finer to grant.
Worth knowing in both directions: a principal with stream.subscribe can poll a
group and acknowledge records, which advances a cursor other consumers share.
That is a wider capability than a plain subscription, and the grammar does not
currently let the two be separated.
Valid canonical objects:
tenant:{tenant_id}namespace:{tenant_id}/{namespace}stream:{tenant_id}/{namespace}/{stream}cache:{tenant_id}/{namespace}/{cache}
Allowed wildcards:
namespace:{tenant_id}/*stream:{tenant_id}/{namespace}/*cache:{tenant_id}/{namespace}/*-
stream:{tenant_id}/*/*andcache:{tenant_id}/*/*— every stream or cache in the tenant, which is what token exchange expands a tenant-wide grant to. A wildcard namespace under a named leaf (stream:t1/*/orders) is refused: a grant across namespaces should not look like a single-stream grant.
flowchart TB
subgraph tenant["Tenant scope - what a tenant admin can delegate"]
direction TB
T["tenant:t1"] --> N["namespace:t1/*"]
N --> S["stream:t1/ns/*"]
N --> K["cache:t1/ns/*"]
end
subgraph cluster["Cluster scope - operators only"]
direction TB
CL["cluster:*<br/>node.view"]
end
T x-.-x|"never contains"| CL
CL x-.-x|"never contains"| T
A permission is only writable when its object already sits inside the writer's
own scope. The two crossed links are the whole security property: because no
arrow runs between them, a tenant admin cannot write themselves cluster:*, and
cluster scope cannot read tenant data.
One object sits outside the tenant hierarchy:
- Cluster:
cluster:*— broker membership, liveness, and placement standing.
It is an island in both directions, and that is the whole security property:
-
No tenant scope contains it. A policy write is admitted only when its
object is already inside the caller's scope, so a tenant admin cannot grant
themselves
cluster:*. Nothing in the bootstrap seed grants it either. - It contains no tenant object. Cluster scope confers nothing inside a tenant, so it is not a backdoor into tenant data.
node.view:cluster:* is required by GET /v1/nodes and GET /v1/nodes/{node_id}.
The tenant comes from the token's own tid claim rather than a path segment,
because the cluster is not a tenant resource; that claim only selects which
tenant's signing keys to verify against, exactly as kid selects a key without
conferring one.
cluster:* is the only accepted spelling. cluster:nodes and a bare cluster:
are rejected, so a typo is refused rather than silently scoped.
Rejected on writes:
tenant:*-
stream:*/stream:*/* -
cache:*/cache:*/* - any malformed or cross-tenant object
HTTP semantics:
-
400invalid grammar/action/payload -
401missing or invalid bearer token -
403caller authenticated but lacks required scope/action
RBAC endpoints are authorized only by RBAC admin actions:
- list policies/groupings:
rbac.view - mutate policies:
rbac.policy.manage - mutate assignments:
rbac.assignment.manage
Delegation safety rule:
- Caller can only mutate policies/assignments whose target objects are within the caller's scope.
- Caller cannot assign a role if any policy on that role exceeds caller scope.
Admin scope examples:
-
rbac.policy.manage:tenant:t1- can create
namespace:t1/*,stream:t1/payments/*,cache:t1/payments/sessions - cannot create
tenant:*or anyt2object
- can create
-
rbac.policy.manage:namespace:t1/payments- can create stream/cache policies only under
payments - cannot create
namespace:t1/ordersortenant:t1
- can create stream/cache policies only under
-
rbac.policy.manage:stream:t1/payments/orders- can only manage that single stream object
- cannot create
stream:t1/payments/*
Examples:
-
rbac.policy.manage:namespace:t1/paymentscan manage rules inpaymentsonly. -
rbac.policy.manage:stream:t1/payments/orderscannot grantstream:t1/payments/*. -
rbac.policy.manage:tenant:t1can manage tenantt1RBAC, but still cannot usetenant:*.
- Strict grammar is enforced for RBAC evaluation and writes.
- Group-claim RBAC is evaluated at token exchange by materializing transient
principal -> group:*links before Casbin permission expansion.
Generated from docs/security/rbac.md in the repository. That file is the source of truth; this page may be behind it.
Architecture & Design
- Broker-internal forwarding protocol
- Felix Architecture
- Felix Wire Protocol (v1)
- Felix semantics
- Felix – Comprehensive Design Document
Storage & Replication
- Durable Storage
- Durable Storage Performance
- Felix Durable Segment Format (v2)
- One core log, many semantics
- Plan: group-commit the cache write path
- Shard replication and leader failover
- The cache is a projection over the log
Control Plane & Cluster
- A formal model of one shard
- Control Plane + RAFT Plan (Draft)
- Metadata Raft
- Subscribe routing: redirect, not proxy
- The Postgres the control plane requires
- The local cluster harness
Clients & Configuration
- Auth Design: OIDC Exchange + Felix Tokens
- Broker Config Examples
- Client Config
- Running a client against a cluster
Security
- Bootstrap security
- Panic and abort audit — broker data path
- RBAC Model (Control Plane)
- Soak and resource-leak report — broker
- Threat model: the broker-internal surface
Performance Investigations
- Real-network performance suite
- Sharding investigation: keyed publishes, io_uring, and a ceiling that would not move
- The 0.4.0 performance matrix
- Throughput investigation: why Felix is slower than raw QUIC
Explorations & History