Skip to main content
CyborgDB Service v0.17 ships with an operator-side multi-tenancy model built on cryptographic isolation: each index is wrapped by its own key, and per-user API keys hold their own wrapped data-encryption keys (DEKs). Permissions aren’t stored as a policy blob — the set of wrapped DEKs a user holds is their permission set, enforced by the encrypted-index engine on every request. This page is the operator playbook: how to enable RBAC, mint and revoke users, and the constraints to be aware of.

Modes

ModeEnabled byWho can call what
Single-key (default)CYBORGDB_SERVICE_ROOT_KEY unsetOne CYBORGDB_API_KEY has full access to everything. No user routes available.
RBACCYBORGDB_SERVICE_ROOT_KEY setThe root key has admin (mint/list/revoke users, create/train/delete indexes). Per-user cdbk_… keys are scoped to one index with read / write permissions. The legacy CYBORGDB_API_KEY is no longer accepted on index routes.
You enable RBAC by setting both keys at startup:
export CYBORGDB_API_KEY=cyborg_your_legacy_key      # still required (license)
export CYBORGDB_SERVICE_ROOT_KEY=cyborg_your_root_key   # opt-in: enables RBAC
cyborgdb-service
Or via YAML:
cyborgdb.yaml
service:
  cyborgdb_api_key: ${CYBORGDB_API_KEY}
  cyborgdb_service_root_key: ${CYBORGDB_SERVICE_ROOT_KEY}
Pick fresh, distinct values for the two keys. The root key has admin privileges; treat it like a master credential. Rotate it independently of CYBORGDB_API_KEY.

Key kinds

When RBAC is on, the service classifies every X-API-Key request header into one of three kinds:
KindHeader valueWhat it can do
rootCYBORGDB_SERVICE_ROOT_KEYFull admin: create/train/delete indexes; mint/list/revoke users; all data operations.
userA cdbk_… token minted via POST /v1/indexes/{name}/usersData operations on one index, gated by the read / write permission set requested at mint time. Cannot create/delete indexes or manage users.
legacyCYBORGDB_API_KEYRejected on index routes when RBAC is on. Health endpoint still works.
If CYBORGDB_SERVICE_ROOT_KEY is unset, the legacy single key keeps its full-access behavior — RBAC is strictly opt-in.

Provisioning users

A user is scoped to exactly one index, with a non-empty subset of {"read", "write"} permissions. Mint a user with the root key:
curl -X POST "http://localhost:8000/v1/indexes/documents/users" \
     -H "X-API-Key: $CYBORGDB_SERVICE_ROOT_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "permissions": ["read", "write"]
     }'
# Response (returned ONCE — capture it):
# { "user_id": "ab12…", "api_key": "cdbk_…" }
The api_key is returned exactly once and is never stored by the service. If you lose it, you must revoke the user and mint a new one. Hand it to the user securely (out-of-band, encrypted channel, secret store).
The user authenticates by passing cdbk_… as api_key to their Client — they need no index key of their own (the service resolves the index KEK server-side).
# End user
from cyborgdb import Client

client = Client(base_url='http://localhost:8000', api_key='cdbk_…')
index = client.load_index('documents')  # no index_key argument
index.query(query_vectors=[0.1]*384, top_k=5)

Listing and revoking users

# List
curl -X GET "http://localhost:8000/v1/indexes/documents/users" \
     -H "X-API-Key: $CYBORGDB_SERVICE_ROOT_KEY"

# Revoke
curl -X DELETE "http://localhost:8000/v1/indexes/documents/users/ab12…" \
     -H "X-API-Key: $CYBORGDB_SERVICE_ROOT_KEY"
Revocation is cryptographic, not policy-based: the service erases that user’s wrapped DEK(s) for the index. On the next request, the service has nothing to unwrap for them — even a captured cdbk_… token becomes useless. There is no propagation lag.

KMS-backed constraint

Per-user keys work only against KMS-backed indexes — i.e., indexes created with kms_name=… rather than index_key=…. The reason is mechanical: a user does not hold the index key, so the service must be able to resolve it server-side from a kms.registry slot. For SDK-supplied indexes (provider: none), there is no server-side key to resolve, so per-user provisioning is not supported. If you want multi-tenant access to an index that today uses an SDK-supplied key, migrate it to KMS-backed first (see Managing Encryption Keys → Migrating).
Mint-time provisioning still requires the index KEK to bootstrap the new user’s wrapped DEK. For KMS-backed indexes this happens server-side; for SDK-supplied indexes you’d need to pass the index key on the create_user call, but the resulting user still wouldn’t have a path to authenticate (the service can’t resolve the KEK for their later requests). This is why the constraint exists end-to-end.

End-to-end example

A typical operator workflow for onboarding a new tenant:
1

Provision a KMS slot

Add a registry entry to your service YAML — see KMS & BYOK.
cyborgdb.yaml
kms:
  registry:
    tenant-acme:
      provider: aws-kms
      key_id:   alias/cyborgdb-acme
      region:   us-east-1
Restart the service. Look for KMS registry loaded in the logs.
2

Create the tenant's index (root)

curl -X POST "http://localhost:8000/v1/indexes/create" \
     -H "X-API-Key: $CYBORGDB_SERVICE_ROOT_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "index_name": "acme-documents",
       "kms_name": "tenant-acme",
       "dimension": 384,
       "metric": "cosine"
     }'
3

Mint a user key for the tenant (root)

curl -X POST "http://localhost:8000/v1/indexes/acme-documents/users" \
     -H "X-API-Key: $CYBORGDB_SERVICE_ROOT_KEY" \
     -H "Content-Type: application/json" \
     -d '{ "permissions": ["read", "write"] }'
Hand the returned cdbk_… to the tenant out-of-band.
4

Tenant uses the index

The tenant configures their SDK client with api_key = "cdbk_…" and operates only on acme-documents. They cannot list, describe, or touch any other tenant’s index.
5

Revoke when access ends

curl -X DELETE "http://localhost:8000/v1/indexes/acme-documents/users/<user_id>" \
     -H "X-API-Key: $CYBORGDB_SERVICE_ROOT_KEY"
Revocation is immediate — the tenant’s next request fails with 401.

Operational notes

  • Read vs write semantics. read covers query, get, list_ids, describe. write covers upsert, delete. Index lifecycle operations (create, train, delete_index) and user management are root-only — there’s no “delegated admin” permission.
  • KEK cache. Plaintext index KEKs live in an in-process cache for INDEX_KEK_CACHE_TTL_SECONDS (default 60s). Revoking a user is immediate and cryptographic; revoking KMS-level access to the wrap key propagates within the cache TTL.
  • Restarting the service. User keys survive restart — the wrapped DEKs are persisted alongside the index envelope. The root key is read from env/YAML on every start.
  • Auditing. The service logs the kind of key (root / user / legacy / none) on each request at INFO. Pair with structured logging to attribute index activity to users.

See also