Encryption at Rest¶
IndentiaDB encrypts its write-ahead logs, checkpoints, Raft state, and backups at the application layer with AES-256-GCM (via a FIPS-capable cryptography backend), using envelope encryption rooted in HashiCorp Vault Transit. Encryption is off by default and can be enabled on an existing deployment without downtime.
Key Hierarchy¶
Vault Transit KEK (never leaves Vault)
│ unwraps once at boot
▼
Root key (per deployment / tenant, in-memory only, zeroized)
│ wraps locally
▼
Per-file DEK (random 256-bit, one per file)
│
▼
AES-256-GCM sealed file
- Vault is contacted once per boot (one
transit/decryptcall) to unwrap the root key — not per file. - Every sealed file gets a fresh random Data Encryption Key; the wrapped DEK travels in the sealed file's header.
- The GCM authentication tag is bound to additional authenticated data (format version, cipher, key ID, tenant, file identity), so a sealed file cannot be silently swapped or replayed into a different context.
What Is Encrypted¶
| Surface | Encrypted |
|---|---|
| RDF write-ahead log (WAL), shard WALs, meta WAL | ✅ AES-256-GCM |
| RDF checkpoints | ✅ AES-256-GCM |
| Raft log entries | ✅ AES-256-GCM (per-node DEK) |
| Raft snapshots | ✅ AES-256-GCM (node-agnostic — restorable on any node) |
Backups (indentiagraph backup create) |
✅ AES-256-GCM, chunked with an authenticated footer |
Memory-mapped index files (permutations, vocabulary, vectors) and the :9200 mirror store |
❌ Rely on volume-level encryption (LUKS / cloud CMK) |
Why the mmap indexes use volume encryption
The query-critical index files are accessed via zero-copy memory mapping below the page cache. Application-level encryption there would forfeit that performance model, so full-volume encryption (LUKS, Azure/AWS CMK-encrypted disks) is the intended layer for them. A production deployment uses both: app-level sealing for WAL/checkpoint/Raft/backups, volume encryption for everything.
Configuration¶
| Variable | Default | Effect |
|---|---|---|
IG_ENCRYPTION_ENABLED |
off | Master switch. Off means plaintext. |
IG_ENCRYPTION_REQUIRED |
off (tri-state) | Fail closed: refuse to boot on key-provider failure, a static dev key, or plaintext files where sealed ones are expected. See below. |
IG_FIPS_REQUIRED |
off | Assert the FIPS-validated crypto backend at startup; fails closed on a non-FIPS build (build with --features fips). |
IG_CRYPTO_TENANT_ID |
default |
Tenant slug bound into key derivation and authenticated data. |
IG_CLUSTER_ID |
— | Stable cluster UUID; mandatory whenever encryption is active. Raft snapshot authentication binds to it, so record it with your recovery secrets. |
IG_CRYPTO_VAULT_ADDR |
— | Vault address (HTTPS-only in production). |
IG_CRYPTO_VAULT_MOUNT |
transit |
Vault Transit mount. |
IG_CRYPTO_VAULT_KEY |
ig-tenant-<tenant> |
Transit key name. |
IG_CRYPTO_VAULT_WRAPPED_ROOT[_FILE] |
— | The vault:v1:… wrapped root key. |
IG_CRYPTO_VAULT_CA_FILE |
— | Explicit Vault trust root; required under IG_PRODUCTION. |
IG_VAULT_TOKEN[_FILE] |
— | Vault token. |
IG_CRYPTO_KEYRING_FILE |
— | JSON multi-scope keyring (system / per-tenant / per-group keys) replacing the single-key variables. |
IG_CRYPTO_KEY_FILE / IG_CRYPTO_STATIC_KEY |
— | Dev/test static keys. Rejected when encryption is required — node-held key custody is not KMS custody. |
IG_CRYPTO_MIGRATE |
off | One-boot sanctioned plaintext→sealed re-seal pass (atomic, bounded, resumable). |
The IG_ENCRYPTION_REQUIRED Tri-State¶
| Value | Behavior |
|---|---|
1 / true / yes / on |
Required — fail closed. |
0 / false / no / off |
Explicitly not required — the deliberate opt-out during a migration window, even under IG_PRODUCTION=true. |
| Unset, empty, or unrecognized | Falls through to IG_PRODUCTION: production implies required. A typo can never silently disable the policy. |
Vault Setup¶
# One-time provisioning
vault secrets enable transit
vault write -f transit/keys/ig-tenant-acme
# Wrap a random 32-byte root key; store the returned vault:v1:… value
vault write transit/encrypt/ig-tenant-acme \
plaintext="$(openssl rand -base64 32)"
Grant IndentiaDB a least-privilege policy:
Vault-side KEK rotation (vault write -f transit/keys/ig-tenant-acme/rotate) requires no data re-encryption — the wrapped root key is simply re-wrapped under the new KEK version.
Enabling Encryption on an Existing Deployment¶
Readers accept both plaintext and sealed file formats, so encryption can be introduced with rolling upgrades and no flag-day:
- Enable dark: set
IG_ENCRYPTION_ENABLED=trueplus the Vault variables, keepIG_ENCRYPTION_REQUIRED=false. New WAL segments, checkpoints, and Raft state are written sealed; existing plaintext files remain readable. - Re-seal history: boot once with
IG_CRYPTO_MIGRATE=true. Existing plaintext files are re-sealed atomically before the server starts serving; then remove the flag. - Fail closed: flip
IG_ENCRYPTION_REQUIRED=true(or simply run underIG_PRODUCTION=trueand drop the explicitfalse). From now on a missing key provider or unexpected plaintext file refuses boot.
Record IG_CLUSTER_ID with your recovery secrets
Encrypted Raft snapshots authenticate against the cluster UUID. A disaster-recovery restore on fresh nodes must reuse the original IG_CLUSTER_ID — a different value fails restore authentication by design (a stolen snapshot cannot be used to fork a replacement cluster).
Multi-Tenant Keyring¶
For deployments that need separate keys per tenant (crypto-shred, per-customer KMS custody), IG_CRYPTO_KEYRING_FILE points at a JSON registry of scoped keys:
{
"default_scope": "system",
"missing_scope_policy": "reject",
"keys": [
{ "scope": "system", "vault_key": "ig-system", "wrapped_root_file": "/etc/ig/keys/system.wrapped" },
{ "scope": "tenant:acme", "vault_key": "ig-tenant-acme", "wrapped_root_file": "/etc/ig/keys/acme.wrapped" }
]
}
Every root key is unwrapped and canary-tested at boot; one unavailable or duplicate key aborts startup. When encryption is required, missing_scope_policy must be reject — an unknown tenant scope never silently falls back to the default key.
See Also¶
- Production Hardening — the
IG_PRODUCTIONfail-closed boot gate - Backup & Disaster Recovery — sealed, signed backups and off-node S3 replication