This page is service-only. The embedded libraries’ KMS story is
covered separately under Managing Encryption Keys.
AWS access — what the service expects
The service uses the standard AWS credential provider chain to reachaws-kms and aws (Secrets Manager) slots. The exact resolution depends on where you run it:
| Environment | What you set | What the service uses |
|---|---|---|
| EC2 / ECS / EKS (same account) | Nothing — attach an IAM role to the instance/task with the required KMS or Secrets Manager permissions | Instance/task role via IMDS |
| EC2 / ECS / EKS (cross-account, BYOK) | role_arn + external_id on the registry slot | Instance/task role → sts:AssumeRole into the customer role |
| Off-AWS / local | AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (and optionally AWS_SESSION_TOKEN) env vars, or AWS_PROFILE pointing at ~/.aws/credentials | Default chain picks them up |
| Off-AWS, BYOK | Local credentials plus role_arn + external_id on the slot | Local creds → sts:AssumeRole |
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:
provider: aws (Secrets Manager) — on the secret:
-* 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.
| Mode | kms_name | index_key | KEK lives in… | SDK behavior |
|---|---|---|---|---|
| KMS-backed | a registry slot | omitted | the KMS provider | SDK omits index_key on every call; service resolves the KEK on demand. |
| SDK-supplied | omitted | 32 bytes from caller | the SDK client only | SDK supplies the same index_key on every call. Persisted envelope records provider: none. |
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) underkms.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
Theprovider field selects how the KEK is wrapped:
| Provider | Where the wrap key lives | KEK lifecycle |
|---|---|---|
aws-kms | AWS KMS (HSM-managed) | Service generates a random KEK and calls kms.Encrypt. On load, calls kms.Decrypt. |
aws | AWS Secrets Manager | Service generates a random KEK and AES-GCM-wraps it under the Secrets Manager value. On load, fetches the secret and unwraps locally. |
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 forINDEX_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 swappingindex_key= for
kms_name=:
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 throughsts:AssumeRole with an ExternalId on every
wrap or unwrap call.
Setup is a two-party handshake:
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.
Service operator: share three values with the customer
- The service’s AWS principal ARN (the identity boto3 will
use). Get it with:
- The
ExternalIdyou generated. - The customer-facing setup steps (Step 3 below).
Customer: create the wrap key and IAM role
1. Create the wrap key in Secrets Manager (32 random
bytes):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.Service operator: add the slot to YAML
Under Then restart the service. On boot, look for:
kms.registry::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_namechanges — applied transparently on the next unwrap. No restart of the index needed.provider/key_id/regionchanges — 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
`AccessDenied` on sts:AssumeRole
`AccessDenied` on sts:AssumeRole
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.`AccessDenied` on GetSecretValue
`AccessDenied` on GetSecretValue
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).`ResourceNotFoundException` on the secret
`ResourceNotFoundException` on the secret
Secret was deleted, or the
region in the YAML slot
doesn’t match the region where the secret lives.`Wrap key must be 32 bytes`
`Wrap key must be 32 bytes`
The Secrets Manager value is the wrong size. Replace with
exactly 32 random bytes.
Index creation: `index_key and kms_name are mutually exclusive`
Index creation: `index_key and kms_name are mutually exclusive`
Supplying both fields is rejected with HTTP 400 regardless
of provider type. Pick one path per index.
Cached KEK after revoking access
Cached KEK after revoking access
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_nameparameter and the SDK-supplied alternative.