Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c74eab5
feat: block-level encryption at rest
polaz Mar 22, 2026
a5c65fc
fix(encryption): add missing doc sections and fix config field docs
polaz Mar 22, 2026
183ea0b
fix(encryption): account for encryption overhead in block size caps a…
polaz Mar 22, 2026
7eccaab
fix(encryption): move ENCRYPTION_OVERHEAD const to module level
polaz Mar 22, 2026
7861012
feat(encryption): add max_overhead() to EncryptionProvider trait
polaz Mar 22, 2026
64070c8
docs(vlog): acknowledge key range leak in unencrypted blob metadata
polaz Mar 22, 2026
27b7354
test(encryption): add tamper detection integration test
polaz Mar 22, 2026
ea18777
fix(encryption): add missing encryption param in metrics-gated test p…
polaz Mar 22, 2026
fa8bc8a
docs(encryption): clarify that encryption state is caller-determined
polaz Mar 22, 2026
ef46915
refactor(encryption): simplify encrypt/decrypt buffer ownership in Bl…
polaz Mar 22, 2026
b5be1fc
fix(encryption): add debug_assert for enc_overhead u32 addition safety
polaz Mar 22, 2026
c2be454
fix(encryption): use clippy-approved checked conversion in debug_assert
polaz Mar 22, 2026
ed05506
fix(encryption): validate encrypted payload length fits u32 before cast
polaz Mar 22, 2026
b4fadaa
docs(encryption): improve from_reader doc on encryption mismatch beha…
polaz Mar 22, 2026
c46801f
fix(encryption): validate write-path payload against read-path size cap
polaz Mar 22, 2026
7f90d91
fix(encryption): align write-path size cap with read-path and restric…
polaz Mar 22, 2026
2dbae8e
fix(encryption): validate payload size in u64 before u32 cast in writ…
polaz Mar 22, 2026
0020db7
refactor(encryption): use if-let binding to avoid expect() in from_fi…
polaz Mar 22, 2026
0639432
docs(encryption): clarify blob file and KV separation encryption gaps
polaz Mar 22, 2026
9d09ebc
fix(encryption): cap write-path max_payload at u32::MAX for safe cast
polaz Mar 22, 2026
5f16021
refactor(encryption): unify from_reader size check to u64 arithmetic
polaz Mar 22, 2026
842a5f5
refactor(encryption): change max_overhead() return type from usize to…
polaz Mar 22, 2026
5d81b8e
fix(encryption): resolve clippy cast warnings for max_overhead u32 re…
polaz Mar 22, 2026
21fd832
docs(encryption): qualify from_reader doc with authenticated provider…
polaz Mar 22, 2026
68d9e79
fix(encryption): use MAX_SEQNO instead of u64::MAX in integration tests
polaz Mar 22, 2026
849488d
fix(encryption): replace .expect() with ? in unit tests for clippy co…
polaz Mar 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ path = "src/lib.rs"
default = []
lz4 = ["dep:lz4_flex"]
zstd = ["dep:zstd"]
encryption = ["dep:aes-gcm"]
bytes_1 = ["dep:bytes"]
metrics = []

Expand All @@ -40,6 +41,7 @@ sfa = "~1.0.0"
tempfile = "3.20.0"
varint-rs = "2.2.0"
xxhash-rust = { version = "0.8.15", features = ["xxh3"] }
aes-gcm = { version = "0.10", optional = true, features = ["aes"] }

[dev-dependencies]
criterion = { version = "0.8.0", features = ["html_reports"] }
Expand Down
1 change: 1 addition & 0 deletions src/blob_tree/ingest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ impl<'a> BlobIngestion<'a> {
index.config.descriptor_table.clone(),
false,
false,
index.config.encryption.clone(),
index.config.comparator.clone(),
#[cfg(feature = "metrics")]
index.metrics.clone(),
Expand Down
2 changes: 2 additions & 0 deletions src/blob_tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ impl AbstractTree for BlobTree {

table_writer =
table_writer.use_prefix_extractor(self.index.config.prefix_extractor.clone());
table_writer = table_writer.use_encryption(self.index.config.encryption.clone());

#[expect(
clippy::expect_used,
Expand Down Expand Up @@ -532,6 +533,7 @@ impl AbstractTree for BlobTree {
self.index.config.descriptor_table.clone(),
pin_filter,
pin_index,
self.index.config.encryption.clone(),
self.index.config.comparator.clone(),
#[cfg(feature = "metrics")]
self.index.metrics.clone(),
Expand Down
2 changes: 2 additions & 0 deletions src/compaction/flavour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pub(super) fn prepare_table_writer(
// filter writer (preserving the extractor). Only use_partitioned_filter
// replaces the writer entirely (handled above, lines 85-90).
.use_prefix_extractor(opts.config.prefix_extractor.clone())
.use_encryption(opts.config.encryption.clone())
.use_bloom_policy({
use crate::config::FilterPolicyEntry::{Bloom, None};
use crate::table::filter::BloomConstructionPolicy;
Expand Down Expand Up @@ -376,6 +377,7 @@ impl StandardCompaction {
opts.config.descriptor_table.clone(),
pin_filter,
pin_index,
opts.config.encryption.clone(),
opts.config.comparator.clone(),
#[cfg(feature = "metrics")]
opts.metrics.clone(),
Expand Down
39 changes: 37 additions & 2 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub type PartitioningPolicy = PinningPolicy;
use crate::{
compaction::filter::Factory,
comparator::{self, SharedComparator},
encryption::EncryptionProvider,
merge_operator::MergeOperator,
path::absolute_path,
prefix::PrefixExtractor,
Expand Down Expand Up @@ -260,11 +261,21 @@ pub struct Config {
#[doc(hidden)]
pub(crate) comparator: SharedComparator,

/// The global sequence number generator
/// Block-level encryption provider for encryption at rest.
///
/// Should be shared between multiple trees of a database
/// When set, all blocks (data, index, filter, meta) are encrypted
/// using this provider after compression and before checksumming.
pub(crate) encryption: Option<Arc<dyn EncryptionProvider>>,

/// The global sequence number generator.
///
/// Should be shared between multiple trees of a database.
pub(crate) seqno: SharedSequenceNumberGenerator,

/// Sequence number watermark that is visible to readers.
///
/// Used for MVCC snapshots and to control which updates are
/// observable in a given view of the database.
pub(crate) visible_seqno: SharedSequenceNumberGenerator,
}

Expand Down Expand Up @@ -327,6 +338,7 @@ impl Default for Config {
kv_separation_opts: None,

comparator: comparator::default_comparator(),
encryption: None,
}
}
}
Expand Down Expand Up @@ -559,6 +571,29 @@ impl Config {
self
}

/// Sets the block-level encryption provider for encryption at rest.
///
/// When set, all blocks written to SST files are encrypted after
/// compression and before checksumming, using the provided
/// [`EncryptionProvider`].
///
/// The caller is responsible for key management and rotation.
/// See [`crate::Aes256GcmProvider`] (behind the `encryption` feature)
/// for a ready-to-use AES-256-GCM implementation.
///
/// **Important constraints:**
/// - Encryption state is NOT recorded in SST metadata. Opening an
/// encrypted tree without the correct provider (or vice versa) will
/// cause block validation errors, not silent corruption.
/// - Blob files (KV-separated large values) are NOT covered by
/// block-level encryption. Large values stored via KV separation
/// remain in plaintext on disk.
#[must_use]
pub fn with_encryption(mut self, encryption: Option<Arc<dyn EncryptionProvider>>) -> Self {
self.encryption = encryption;
self
}
Comment thread
polaz marked this conversation as resolved.

/// Opens a tree using the config.
///
/// # Errors
Expand Down
Loading
Loading