Skip to main content
CyborgDB Service v0.17 supports per-index Key Management Service (KMS) integration: each index can be wrapped by its own operator-managed key, optionally living in a customer’s own AWS account (Bring Your Own KMS). The SDK never holds long-term encryption keys for KMS-backed indexes.
This page is service-only. The embedded libraries’ KMS story is covered separately under Managing Encryption Keys.
When KMS-backed, omit index_key everywhere. Migrating an index from the SDK-supplied path to KMS-backed means removing index_key from every callcreate_index, load_index, upsert, query, get, delete, delete_index. The service resolves the KEK server-side from the stored KMSBlob; supplying index_key against a real KMS slot is rejected with HTTP 400. If your code still passes a key, the rejection is loud — but worth a single search-and-clean pass before you ramp.

AWS access — what the service expects

The service uses the standard AWS credential provider chain to reach aws-kms and aws (Secrets Manager) slots. The exact resolution depends on where you run it:
The CYBORGDB_S3_* storage credentials are deliberately separate from AWS_* KMS credentials. KMS uses the AWS default chain (or AssumeRole); S3 storage with a custom endpoint uses its own explicit keys. This means your storage backend (MinIO, R2, etc.) and your KMS (AWS) cannot accidentally end up sharing credentials.

Required IAM permissions (per provider)

provider: aws-kms — on the wrap key:
For same-account setups, these permissions can live on the KMS key’s key policy (preferred) or on the runtime principal’s IAM policy. provider: aws (Secrets Manager) — on the secret:
The trailing -* is required (Secrets Manager appends 6 random characters to every secret ARN).

Model

CyborgDB Service uses a two-key hierarchy per index:
  • KEK (Key-Encryption Key, 32 bytes) — per index. Resolved from the named KMS registry slot at index creation, then re-fetched (or unwrapped) on every load. Held in a short-lived in-process cache; the TTL is set by INDEX_KEK_CACHE_TTL_SECONDS (default 60 s).
  • DEK (Data-Encryption Key, 32 bytes) — per index. Generated internally at index-creation time, wrapped under the KEK with AES-GCM, and persisted alongside the index. Never crosses out of the core engine.
There are exactly two ways to provision a CyborgDB index: Supplying both kms_name and index_key is rejected with a 400. Supplying neither is also a 400.

YAML Registry

Per-index KMS slots live in the service YAML file (not environment variables) under kms.registry. Each slot is a named entry that create_index(..., kms_name=<slot>) can reference.
cyborgdb.yaml
String values support env-var substitution: ${VAR} (required, fails on startup if unset) and ${VAR:-default} (uses the default when unset). Use this to keep BYOK role ARNs and external IDs out of the checked-in file.

Provider types

The provider field selects how the KEK is wrapped: Both providers additionally accept role_arn + external_id for cross-account access (BYOK) — the service calls sts:AssumeRole before reaching the key. There is no registry slot for the SDK-supplied path. Omit kms_name entirely from create_index and supply index_key directly; the persisted envelope records provider: none.

Caching and revocation

The service caches plaintext KEKs in memory for INDEX_KEK_CACHE_TTL_SECONDS (default 60 s). Only KMS-derived KEKs are cached — the SDK-supplied path always passes through. Shorter TTLs propagate KMS revocations (key deletion, IAM policy detach, trust-policy edit) faster but cost more KMS calls. To force-revoke an index globally, revoke the wrap key in the KMS provider; cached KEKs expire within INDEX_KEK_CACHE_TTL_SECONDS.

Creating a KMS-backed index

The SDK call is unchanged except for swapping index_key= for kms_name=:
On every subsequent request for this index — load_index, upsert, query, delete — the SDK omits index_key. The service looks up the index’s persisted envelope, resolves the KEK via the named KMS slot (cache hit, or fresh wrap/unwrap on miss), and passes it to the engine.

Bring Your Own KMS (cross-account)

In BYOK, the wrap key lives in the customer’s AWS account. CyborgDB Service holds no long-term credentials to that account — access flows through sts:AssumeRole with an ExternalId on every wrap or unwrap call. Setup is a two-party handshake:
1

Service operator: generate an ExternalId

Treat as a credential — it’s the cryptographic gate preventing one customer’s role ARN from being abused by another.
2

Service operator: share three values with the customer

  • The service’s AWS principal ARN (the identity boto3 will use). Get it with:
  • The ExternalId you generated.
  • The customer-facing setup steps (Step 3 below).
3

Customer: create the wrap key and IAM role

1. Create the wrap key in Secrets Manager (32 random bytes):
Keep a backup. If this secret is deleted, every index wrapped under it becomes permanently unreadable.
2. Create an IAM role with this trust policy (substituting the operator-supplied values):
3. Attach an inline permission policy for the secret:
The trailing -* is required (Secrets Manager appends 6 random chars to every secret ARN).Send three values back to the operator: role ARN, secret name, region. Never share AWS credentials.
4

Service operator: add the slot to YAML

Under kms.registry::
Then restart the service. On boot, look for:
5

(Optional) Verify the AssumeRole

From the same shell environment as the service:
Prints ASIA... → trust policy and ExternalId match. AccessDenied → the customer’s trust policy is wrong; share the error.

Revocation

  • Pause access — customer detaches the inline permission policy from the role. The service starts failing on the next cache miss (i.e. within INDEX_KEK_CACHE_TTL_SECONDS).
  • Permanent revoke — customer deletes the role or the secret. Note: deleting the secret renders every index wrapped under it permanently unreadable.

Configuration changes after creation

The persisted envelope records a snapshot of the YAML config (provider, key_id, region) that was used to wrap each index. At startup the service compares that snapshot against the current YAML and:
  • role_arn / external_id / role_session_name changes — applied transparently on the next unwrap. No restart of the index needed.
  • provider / key_id / region changes — interpreted as an operator-initiated rotation. The service automatically unwraps the existing KEK with the old snapshot, generates a new KEK via the new entry, re-wraps the data, and updates the snapshot. The old wrap key must still be accessible for this to succeed.

Troubleshooting

Trust policy mismatch. Either the Principal doesn’t match the service’s AWS identity, or sts:ExternalId doesn’t match the operator-provided UUID. Re-run the manual verify in Step 5 above and share the exact error with the customer.
The permission policy on the customer’s role doesn’t cover the secret, or the Resource ARN is missing the required -* suffix (Secrets Manager appends 6 random chars).
Secret was deleted, or the region in the YAML slot doesn’t match the region where the secret lives.
The Secrets Manager value is the wrong size. Replace with exactly 32 random bytes.
Supplying both fields is rejected with HTTP 400 regardless of provider type. Pick one path per index.
Plaintext KEKs are cached for INDEX_KEK_CACHE_TTL_SECONDS (default 60 s). For tighter revocation windows, drop the TTL — at the cost of more KMS calls. There is no live invalidation API in v0.17.

See also

  • Environment Variables — full configuration reference, including INDEX_KEK_CACHE_TTL_SECONDS.
  • Managing Encryption Keys — the SDK-supplied (provider: none) path and the legacy “client decrypts via KMS” pattern.
  • Create Index — the kms_name parameter and the SDK-supplied alternative.