Skip to content

feat(enc): encrypt list elements and stream field values - #35

Open
mattyhogan wants to merge 2 commits into
mainfrom
feat/encrypt-lists-streams
Open

feat(enc): encrypt list elements and stream field values#35
mattyhogan wants to merge 2 commits into
mainfrom
feat/encrypt-lists-streams

Conversation

@mattyhogan

Copy link
Copy Markdown
Contributor

Extends value-level encryption-at-rest (already supported for strings, hashes, and table columns) to the remaining opaque-value primitives: lists and streams.

API

  • LPUSH key v1 v2 ... ENCRYPTED / RPUSH ... ENCRYPTED — seal each element.
  • XADD key [opts] id field val ... ENCRYPTED — seal each field value.

Mirrors the existing SET/HSET ... ENCRYPTED convention. Encryption is per-write; reads auto-detect the envelope (LUXENC2 magic) and decrypt, so encrypted and plaintext entries coexist.

Design

  • List AAD is key-independent (__lux_list/element), so LMOVE/RPOPLPUSH relocate an encrypted element to another key without re-keying and it still decrypts. Stream AAD binds key + field.
  • Decrypt on every read path: LRANGE/LINDEX/LPOP/RPOP (+ COUNT), LMOVE/RPOPLPUSH/BLPOP/BLMOVE, and the deferred blocking-pop resolution; XRANGE/XREVRANGE/XREAD/XREADGROUP/XCLAIM/XAUTOCLAIM (decrypted in the store read fns).
  • WAL determinism: encrypted pushes self-log ENC RAWLPUSH/RAWRPUSH; encrypted XADD self-logs a resolved XADD carrying the ciphertext (flag stripped). Replay stores envelopes verbatim, no re-encryption.
  • No grant-gating needed: lists are operator-only over HTTP and streams have no HTTP route, so decryption only happens on the (operator-authed) RESP path.

Bundled bug fix

The shard-local read fast-path reads stored bytes directly and has no keyring, so it returned ciphertext for encrypted GET/HGET/LRANGE/XRANGE over RESP (pre-existing; shipped in v0.25.0, but latent — the in-crate tests exercise the slow executor, and table/HTTP encryption was unaffected). Reads and pipelines now fall through to the decrypting slow path whenever encryption is active. Caught by the live e2e, not the unit tests — hence the new TCP integration test.

Limitations (documented)

  • Value-matching ops on encrypted list elements (LREM, LPOS, LINSERT pivot) compare against ciphertext and won't match — inherent to at-rest encryption. ENCRYPTED write support is on the append family (LPUSH/RPUSH) + XADD.
  • Perf: when encryption is active, all reads take the slow path (the fast-path can't decrypt). Only affects instances that have run ENC INIT.

Tests

Unit: LPUSH/XADD WAL-ciphertext + replay round-trips, LMOVE-across-keys. Integration (tests/encryption.rs): RESP-over-TCP round-trips for encrypted string/hash/list/stream + LMOVE. Live e2e confirmed plaintext round-trips, LMOVE portability, and no plaintext on disk. Full suite 909 pass, clippy clean.

LPUSH/RPUSH and XADD accept a trailing ENCRYPTED flag (mirroring
SET/HSET): elements/field-values are sealed as envelopes at rest and
decrypted on read. List AAD is key-independent so LMOVE/RPOPLPUSH relocate
ciphertext without re-keying; stream AAD binds key+field. Encrypted pushes
self-log ENC RAWLPUSH/RAWRPUSH and XADD self-logs the resolved ciphertext,
so WAL replay is deterministic.

Also fixes a pre-existing bug: the shard-local read fast-path has no
keyring and returned ciphertext for encrypted GET/HGET/LRANGE/XRANGE over
RESP. Reads (and pipelines) now fall through to the decrypting slow path
whenever encryption is active.
REWRAP only re-wrapped Str/StrBuf/Hash, so encrypted list elements and
stream field values kept their DEK wrapped under the old key; after RETIRE
they became undecryptable. REWRAP now rewraps List and Stream in place and
the RETIRE guard checks them too. Both also persist and truncate the WAL so
the rewrap is durable across a restart (a WAL replay was reverting in-memory
rewraps to the pre-rewrap ciphertext).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant