At-Rest Encryption
Tellstone can encrypt data written to disk with ChaCha20-Poly1305: values persisted to the write-ahead log and sealed audit records are encrypted before they leave memory. Encryption is off by default and adds no overhead when disabled.
Enabling
Section titled “Enabling”Encryption needs exactly one key source. The server refuses to start with
--enable-encryption unless one of the two sources below is provided — it
never silently falls back to plaintext.
./bin/tellstone --enable-encryption --encryption-key "$(openssl rand -base64 32)"| Flag | Env var | Default | Meaning |
|---|---|---|---|
--enable-encryption | TSD_ENABLE_ENCRYPTION | false | Enable at-rest encryption |
--enable-envelope | TSD_ENABLE_ENVELOPE | false | Envelope encryption — wrap a per-shard random DEK with the configured key; requires --enable-encryption |
--encryption-key | TSD_ENCRYPTION_KEY | (none) | Base64-encoded 32-byte key; raw 32-character value accepted but deprecated |
--encryption-key-file | TSD_ENCRYPTION_KEY_FILE | (none) | Path to a file holding the raw 32-byte key; mutually exclusive with --encryption-key |
Key sources
Section titled “Key sources”--encryption-key (base64)
Section titled “--encryption-key (base64)”The flag carries the key base64-encoded because process arguments and
environment variables are NUL-terminated: roughly 1 in 8 random 32-byte keys
contains a 0x00 byte that cannot survive them. Base64 is text-safe.
./bin/tellstone \ --enable-encryption \ --encryption-key "$(openssl rand -base64 32)"Both the padded (...==) and unpadded base64 forms are accepted.
--encryption-key-file (raw bytes)
Section titled “--encryption-key-file (raw bytes)”The file carries the same key as raw, unencoded bytes, which suits a mounted Kubernetes Secret or a vault-agent-rendered file. Every byte of the file is significant, so it must be exactly 32 bytes with no trailing newline — a stray newline makes it 33 bytes and the key is rejected.
# umask 077 in a subshell so the file is created 0600; a later chmod would# leave the key world-readable in between.(umask 077; head -c 32 /dev/urandom > /etc/tellstone/key)./bin/tellstone --enable-encryption --encryption-key-file /etc/tellstone/keyThe two sources are mutually exclusive: setting both is rejected at startup.
Kubernetes
Section titled “Kubernetes”Mount the Secret and point --encryption-key-file at the projected file:
volumeMounts: - name: encryption-key mountPath: /etc/tellstone/encryption./bin/tellstone \ --enable-encryption \ --encryption-key-file /etc/tellstone/encryption/keyUnlike certificate rotation, the encryption key is read once at startup; rotating it requires a restart, which necessarily re-encrypts nothing already on disk under the old key.
Envelope encryption
Section titled “Envelope encryption”By default a single key seals everything, so a stolen key decrypts the whole
dataset. --enable-envelope upgrades this to envelope encryption: the
configured key becomes a Key Encryption Key (KEK) that wraps a per-shard
Data Encryption Key (DEK).
./bin/tellstone \ --enable-encryption \ --enable-envelope \ --encryption-key "$(openssl rand -base64 32)"--enable-envelope requires --enable-encryption and a key source; the
server refuses to start without them.
How it works
Section titled “How it works”Each shard generates its own random 32-byte DEK on first boot. That DEK is
wrapped (encrypted) with the KEK and stored beside the data the shard protects
as shard-<n>.env:
[version:1][KEK fingerprint:16][wrapped DEK: nonce(12) + ct(32) + tag(16)]The KEK never touches data — values are sealed by the shard’s DEK engine, and the KEK exists only to protect the DEK at rest. On restart each shard loads its envelope and unwraps its own DEK.
Envelope mode buys three things over the single-key mode:
- Key isolation. One compromised shard DEK does not decrypt the others.
- Separation of duties. The KEK is read once at startup and used only to wrap DEKs; no shard ever seals data with it.
- Colocated key and data are safe. A wrapped DEK is worthless without its KEK, so envelope files can live alongside the data they protect.
Envelope files
Section titled “Envelope files”Envelope files live in the persistence directory when --enable-persistence
is set, or the platform default data directory
(~/.local/share/tellstone/data on Linux) otherwise. They are created even
when persistence is disabled, so a DEK stays durable independently of the WAL.
Envelope directories and files are created with 0700 / 0600 permissions.
Rotating the KEK
Section titled “Rotating the KEK”Like the single-key mode, the KEK is read once at startup. Each envelope carries a fingerprint of the KEK that wrapped it, so a restart with a changed KEK fails closed with a fingerprint-mismatch error instead of silently generating fresh DEKs and bricking the dataset.
tellstone kek rewrap performs the offline rewrap: it loads every
shard-<n>.env and audit.env in the data directory, verifies each
envelope against the old KEK, re-wraps the DEK with the new KEK, and
writes the updated envelope back to disk. The server must be stopped
before running this command.
tellstone kek rewrap \ --data-dir ~/.local/share/tellstone/data \ --key-file /etc/tellstone/old.key \ --new-key-file /etc/tellstone/new.key \ --retain-old-keys| Flag | Env var | Meaning |
|---|---|---|
--data-dir | TSD_DATA_DIR | Directory holding shard-*.env, audit.env, and data files |
--key-file | TSD_KEY_FILE | Path to the current (old) KEK file — raw 32-byte key |
--new-key-file | TSD_NEW_KEY_FILE | Path to the new KEK file — raw 32-byte key |
--retain-old-keys | — | Keep a .bak copy of each original envelope before rewriting |
Both key files must be exactly 32 raw bytes (no trailing newline). The
two key sources are mutually exclusive with --encryption-key /
--encryption-key-file — this command always reads raw key files.
Safety properties
Section titled “Safety properties”- Fail-closed pre-scan. Before any file is rewritten, every envelope in the directory is verified against the old KEK. If any fingerprint is unrecognized, the command aborts with an error and no files are touched.
- Idempotent. A crashed rewrap can be recovered by running the same command again. Envelopes already rewrapped with the new KEK (new fingerprint) are skipped silently; only old-fingerprint envelopes are processed.
- Atomic per-file. Each envelope is written to a temporary file,
fsynced, and renamed into place. A power failure mid-rewrap leaves at most one file in its old state — never half-written. - DEK preservation. The data encryption keys are never touched. Only the envelope wrapper changes (old KEK → new KEK), so existing data remains readable by the same DEK engine.
Retaining old keys
Section titled “Retaining old keys”--retain-old-keys creates a .bak copy of each envelope before
rewriting it. The backup holds the original DEK wrapped with the old KEK,
so you can roll back if something goes wrong:
# roll back: rename .bak files backfor f in ~/.local/share/tellstone/data/*.env.bak; do mv "$f" "${f%.bak}"doneVerifying the rewrap
Section titled “Verifying the rewrap”After a successful rewrap the output confirms how many files were processed:
rewrap complete: 3 rewrapped, 0 skipped, 3 total- rewrapped — envelopes where the DEK was re-wrapped with the new KEK.
- skipped — envelopes already carrying the new KEK fingerprint (idempotent no-op).
- total — all
*.envfiles found in the directory.
Full rotation procedure
Section titled “Full rotation procedure”- Generate a new KEK file:
Terminal window (umask 077; head -c 32 /dev/urandom > /etc/tellstone/new.key) - Stop the Tellstone server.
- Run the rewrap:
Terminal window tellstone kek rewrap \--data-dir ~/.local/share/tellstone/data \--key-file /etc/tellstone/old.key \--new-key-file /etc/tellstone/new.key \--retain-old-keys - Start the Tellstone server with the new key:
Terminal window ./bin/tellstone \--enable-encryption \--enable-envelope \--encryption-key-file /etc/tellstone/new.key - Optionally remove old key and
.bakfiles once verified.
Error conditions
Section titled “Error conditions”- Mixed keys. If some envelopes carry a fingerprint that matches neither the old nor the new KEK, the command aborts with an error. This protects against partial rotations from interrupted prior runs.
- Wrong old key. If
--key-filedoes not match the fingerprint on any envelope, verification fails before any file is rewritten. - No envelope files. If the directory contains no
*.envfiles, the command exits with a clear error.
Audit log
Section titled “Audit log”In envelope mode the audit log gets a DEK of its own — never the KEK, never a
shard’s DEK — wrapped by the KEK and stored as an audit.env envelope beside
the audit records, mirroring how each shard seals its data. A stdout audit
destination is never persisted, so no envelope is created and records stay
plaintext.
Deprecation: raw --encryption-key values
Section titled “Deprecation: raw --encryption-key values”Before base64 decoding was added, --encryption-key used its value as raw
text, so a literal 32-character key such as
0123456789abcdef0123456789abcdef was accepted. That form still works and
logs a warning at startup, but it is insecure and will be removed in the next
major release.
The two forms are never ambiguous: a base64 key is 44 characters (43
unpadded), while a 32-character value can only decode to 24 bytes. Re-encode
an existing raw key with base64 < <keyfile>, or move it to
--encryption-key-file.
Related
Section titled “Related”- Security — TLS 1.3 transport, mTLS, and AUTH
- Audit Logging — audit records sealed with the crypto engine
- Configuration — full flag reference