refactor(kms): consolidate encryption metadata key constants into their shared home (#5995)

The shared module rustfs_utils::http::object_encryption_keys is the single source of truth for encryption metadata key names, but three call sites still carried their own copies or bare literals: crates/kms/src/service.rs (two private constants plus four bare x-rustfs-encryption-* literals on both the write and read path), rustfs/src/app/select_object.rs (six SELECT_* copies), and rustfs/src/storage/options.rs (two private prefix copies now imported from header_compat). All values are unchanged, so the change is a compiler-verified rename.

The reader-only x-rustfs-internal-server-side-encryption- family gets a named constant with the verified judgment recorded on it: no writer emits these keys anywhere in the repo (the SSE writer persists the MinIO-branded keys verbatim for interop), the two comments claiming the dual-key invariant writes this twin were wrong and are corrected, and the defensive redaction/strip readers are kept because removing them is risk-asymmetric.

rustfs-kms's rustfs-utils dependency now declares the http feature it uses instead of relying on feature unification from sibling crates.

Refs rustfs/backlog#1775, rustfs/backlog#1562.
This commit is contained in:
Zhengchao An
2026-08-13 00:37:38 +08:00
committed by GitHub
parent 24ca61eb6e
commit 60d8e8a20b
6 changed files with 46 additions and 34 deletions
+3 -2
View File
@@ -277,9 +277,10 @@ pub struct FileInfo {
/// Values of these keys must never reach logs at any level. /// Values of these keys must never reach logs at any level.
fn is_sensitive_metadata_key(key: &str) -> bool { fn is_sensitive_metadata_key(key: &str) -> bool {
// `is_encryption_metadata_key` covers the x-minio-internal- SSE prefix but not // `is_encryption_metadata_key` covers the x-minio-internal- SSE prefix but not
// its x-rustfs-internal- twin, which the dual-key invariant writes alongside it. // its reserved x-rustfs-internal- twin, which has no writer today but must
// stay redacted in case one appears.
is_encryption_metadata_key(key) is_encryption_metadata_key(key)
|| starts_with_ignore_ascii_case(key, "x-rustfs-internal-server-side-encryption-") || starts_with_ignore_ascii_case(key, rustfs_utils::http::RUSTFS_INTERNAL_ENCRYPTION_PREFIX)
|| rustfs_utils::http::REPLICATION_SSE_TRANSPORT_PREFIXES || rustfs_utils::http::REPLICATION_SSE_TRANSPORT_PREFIXES
.iter() .iter()
.any(|prefix| starts_with_ignore_ascii_case(key, prefix)) .any(|prefix| starts_with_ignore_ascii_case(key, prefix))
+12 -17
View File
@@ -27,6 +27,10 @@ use base64::Engine;
use jiff::Zoned; use jiff::Zoned;
use md5::{Digest as Md5Digest, Md5}; use md5::{Digest as Md5Digest, Md5};
use rand::random; use rand::random;
use rustfs_utils::http::object_encryption_keys::{
INTERNAL_ENCRYPTION_ALGORITHM_HEADER, INTERNAL_ENCRYPTION_CONTEXT_HEADER, INTERNAL_ENCRYPTION_IV_HEADER,
INTERNAL_ENCRYPTION_KEY_HEADER, INTERNAL_ENCRYPTION_KEY_ID_HEADER, INTERNAL_ENCRYPTION_TAG_HEADER,
};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Cursor; use std::io::Cursor;
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt};
@@ -81,15 +85,6 @@ fn request_encryption_context(context: &ObjectEncryptionContext) -> HashMap<Stri
enc_context enc_context
} }
// Canonical owners of the internal encryption header names. Note on
// INTERNAL_ENCRYPTION_ALGORITHM_HEADER: it carries the AEAD algorithm the
// object was sealed with. The S3 `x-amz-server-side-encryption` header records
// the *SSE mode* (`AES256` / `aws:kms`), not the cipher, so it cannot
// round-trip `ChaCha20Poly1305`. Without this header a ChaCha-sealed object
// comes back from the projection claiming `aws:kms` and is then opened with
// the wrong cipher.
use rustfs_utils::http::object_encryption_keys::{INTERNAL_ENCRYPTION_ALGORITHM_HEADER, INTERNAL_ENCRYPTION_KEY_ID_HEADER};
/// Result of object encryption /// Result of object encryption
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EncryptionResult { pub struct EncryptionResult {
@@ -805,19 +800,19 @@ impl ObjectEncryptionService {
// Internal headers for decryption // Internal headers for decryption
headers.insert( headers.insert(
"x-rustfs-encryption-iv".to_string(), INTERNAL_ENCRYPTION_IV_HEADER.to_string(),
base64::engine::general_purpose::STANDARD.encode(&metadata.iv), base64::engine::general_purpose::STANDARD.encode(&metadata.iv),
); );
if let Some(ref tag) = metadata.tag { if let Some(ref tag) = metadata.tag {
headers.insert( headers.insert(
"x-rustfs-encryption-tag".to_string(), INTERNAL_ENCRYPTION_TAG_HEADER.to_string(),
base64::engine::general_purpose::STANDARD.encode(tag), base64::engine::general_purpose::STANDARD.encode(tag),
); );
} }
headers.insert( headers.insert(
"x-rustfs-encryption-key".to_string(), INTERNAL_ENCRYPTION_KEY_HEADER.to_string(),
base64::engine::general_purpose::STANDARD.encode(&metadata.encrypted_data_key), base64::engine::general_purpose::STANDARD.encode(&metadata.encrypted_data_key),
); );
@@ -829,7 +824,7 @@ impl ObjectEncryptionService {
None => context_aad(&metadata.encryption_context).unwrap_or_default(), None => context_aad(&metadata.encryption_context).unwrap_or_default(),
}; };
headers.insert( headers.insert(
"x-rustfs-encryption-context".to_string(), INTERNAL_ENCRYPTION_CONTEXT_HEADER.to_string(),
String::from_utf8_lossy(&context_bytes).into_owned(), String::from_utf8_lossy(&context_bytes).into_owned(),
); );
@@ -874,13 +869,13 @@ impl ObjectEncryptionService {
}; };
let iv = headers let iv = headers
.get("x-rustfs-encryption-iv") .get(INTERNAL_ENCRYPTION_IV_HEADER)
.ok_or_else(|| KmsError::validation_error("Missing IV header"))?; .ok_or_else(|| KmsError::validation_error("Missing IV header"))?;
let iv = base64::engine::general_purpose::STANDARD let iv = base64::engine::general_purpose::STANDARD
.decode(iv) .decode(iv)
.map_err(|e| KmsError::validation_error(format!("Invalid IV: {e}")))?; .map_err(|e| KmsError::validation_error(format!("Invalid IV: {e}")))?;
let tag = if let Some(tag_str) = headers.get("x-rustfs-encryption-tag") { let tag = if let Some(tag_str) = headers.get(INTERNAL_ENCRYPTION_TAG_HEADER) {
Some( Some(
base64::engine::general_purpose::STANDARD base64::engine::general_purpose::STANDARD
.decode(tag_str) .decode(tag_str)
@@ -890,7 +885,7 @@ impl ObjectEncryptionService {
None None
}; };
let encrypted_data_key = if let Some(key_str) = headers.get("x-rustfs-encryption-key") { let encrypted_data_key = if let Some(key_str) = headers.get(INTERNAL_ENCRYPTION_KEY_HEADER) {
base64::engine::general_purpose::STANDARD base64::engine::general_purpose::STANDARD
.decode(key_str) .decode(key_str)
.map_err(|e| KmsError::validation_error(format!("Invalid encrypted key: {e}")))? .map_err(|e| KmsError::validation_error(format!("Invalid encrypted key: {e}")))?
@@ -902,7 +897,7 @@ impl ObjectEncryptionService {
// callers that inspect the context, but the bytes are carried through // callers that inspect the context, but the bytes are carried through
// untouched: re-serializing the parsed map is exactly how the original // untouched: re-serializing the parsed map is exactly how the original
// ordering — and with it the ability to open the object — was lost. // ordering — and with it the ability to open the object — was lost.
let (encryption_context, context_aad) = match headers.get("x-rustfs-encryption-context") { let (encryption_context, context_aad) = match headers.get(INTERNAL_ENCRYPTION_CONTEXT_HEADER) {
Some(context_str) => ( Some(context_str) => (
serde_json::from_str(context_str) serde_json::from_str(context_str)
.map_err(|e| KmsError::validation_error(format!("Invalid encryption context: {e}")))?, .map_err(|e| KmsError::validation_error(format!("Invalid encryption context: {e}")))?,
+2 -2
View File
@@ -30,8 +30,8 @@ use std::borrow::Cow;
const RUSTFS_PREFIX: &str = "x-rustfs-"; const RUSTFS_PREFIX: &str = "x-rustfs-";
const MINIO_PREFIX: &str = "x-minio-"; const MINIO_PREFIX: &str = "x-minio-";
const MINIO_ENCRYPTION_PREFIX: &str = "x-minio-encryption-"; pub const MINIO_ENCRYPTION_PREFIX: &str = "x-minio-encryption-";
const RUSTFS_ENCRYPTION_PREFIX: &str = "x-rustfs-encryption-"; pub const RUSTFS_ENCRYPTION_PREFIX: &str = "x-rustfs-encryption-";
const MINIO_INTERNAL_ENCRYPTION_PREFIX: &str = "x-minio-internal-server-side-encryption-"; const MINIO_INTERNAL_ENCRYPTION_PREFIX: &str = "x-minio-internal-server-side-encryption-";
const MINIO_INTERNAL_ENCRYPTED_MULTIPART: &str = "x-minio-internal-encrypted-multipart"; const MINIO_INTERNAL_ENCRYPTED_MULTIPART: &str = "x-minio-internal-encrypted-multipart";
const RUSTFS_ENCRYPTION_ORIGINAL_SIZE: &str = super::object_encryption_keys::INTERNAL_ENCRYPTION_ORIGINAL_SIZE_HEADER; const RUSTFS_ENCRYPTION_ORIGINAL_SIZE: &str = super::object_encryption_keys::INTERNAL_ENCRYPTION_ORIGINAL_SIZE_HEADER;
@@ -30,6 +30,13 @@ use super::headers::{SSEC_ALGORITHM_HEADER, SSEC_KEY_MD5_HEADER};
pub const INTERNAL_ENCRYPTION_KEY_ID_HEADER: &str = "x-rustfs-encryption-key-id"; pub const INTERNAL_ENCRYPTION_KEY_ID_HEADER: &str = "x-rustfs-encryption-key-id";
pub const INTERNAL_ENCRYPTION_KEY_HEADER: &str = "x-rustfs-encryption-key"; pub const INTERNAL_ENCRYPTION_KEY_HEADER: &str = "x-rustfs-encryption-key";
pub const INTERNAL_ENCRYPTION_IV_HEADER: &str = "x-rustfs-encryption-iv"; pub const INTERNAL_ENCRYPTION_IV_HEADER: &str = "x-rustfs-encryption-iv";
/// Carries the AEAD algorithm the object was sealed with.
///
/// The S3 `x-amz-server-side-encryption` header records the *SSE mode*
/// (`AES256` / `aws:kms`), not the cipher, so it cannot round-trip
/// `ChaCha20Poly1305`. Without this header a ChaCha-sealed object comes back
/// from the projection claiming `aws:kms` and is then opened with the wrong
/// cipher.
pub const INTERNAL_ENCRYPTION_ALGORITHM_HEADER: &str = "x-rustfs-encryption-algorithm"; pub const INTERNAL_ENCRYPTION_ALGORITHM_HEADER: &str = "x-rustfs-encryption-algorithm";
pub const INTERNAL_ENCRYPTION_ORIGINAL_SIZE_HEADER: &str = "x-rustfs-encryption-original-size"; pub const INTERNAL_ENCRYPTION_ORIGINAL_SIZE_HEADER: &str = "x-rustfs-encryption-original-size";
pub const INTERNAL_ENCRYPTION_CONTEXT_HEADER: &str = "x-rustfs-encryption-context"; pub const INTERNAL_ENCRYPTION_CONTEXT_HEADER: &str = "x-rustfs-encryption-context";
@@ -45,6 +52,15 @@ pub const MINIO_INTERNAL_ENCRYPTION_KMS_KEY_ID_HEADER: &str = "X-Minio-Internal-
pub const MINIO_INTERNAL_ENCRYPTION_KMS_DATA_KEY_HEADER: &str = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key"; pub const MINIO_INTERNAL_ENCRYPTION_KMS_DATA_KEY_HEADER: &str = "X-Minio-Internal-Server-Side-Encryption-S3-Kms-Sealed-Key";
pub const MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER: &str = "X-Minio-Internal-Server-Side-Encryption-Context"; pub const MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER: &str = "X-Minio-Internal-Server-Side-Encryption-Context";
/// Reserved RustFS-branded twin of the MinIO-internal SSE key family.
///
/// No RustFS writer emits these keys today — the SSE writer persists the
/// MinIO-branded `X-Minio-Internal-Server-Side-Encryption-*` keys verbatim for
/// interoperability — but redaction (`rustfs_filemeta`) and replication
/// stripping treat the family as sensitive so that a future or third-party
/// writer cannot leak sealed material through the reserved names.
pub const RUSTFS_INTERNAL_ENCRYPTION_PREFIX: &str = "x-rustfs-internal-server-side-encryption-";
pub const REPLICATION_SSEC_ALGORITHM_HEADER: &str = "X-Rustfs-Replication-Ssec-Algorithm"; pub const REPLICATION_SSEC_ALGORITHM_HEADER: &str = "X-Rustfs-Replication-Ssec-Algorithm";
pub const REPLICATION_SSEC_KEY_MD5_HEADER: &str = "X-Rustfs-Replication-Ssec-Key-Md5"; pub const REPLICATION_SSEC_KEY_MD5_HEADER: &str = "X-Rustfs-Replication-Ssec-Key-Md5";
pub const REPLICATION_SSEC_ORIGINAL_SIZE_HEADER: &str = "X-Rustfs-Replication-Ssec-Original-Size"; pub const REPLICATION_SSEC_ORIGINAL_SIZE_HEADER: &str = "X-Rustfs-Replication-Ssec-Original-Size";
@@ -125,13 +141,14 @@ pub fn ssec_replication_transport_header(stored_key: &str) -> Option<&'static st
/// SSE-C material. SSE-C passthrough re-adds its keys through the transport /// SSE-C material. SSE-C passthrough re-adds its keys through the transport
/// mapping instead. /// mapping instead.
pub fn is_replication_stripped_encryption_key(key: &str) -> bool { pub fn is_replication_stripped_encryption_key(key: &str) -> bool {
// The dual-key invariant writes an x-rustfs-internal- twin next to every // The x-rustfs-internal- SSE prefix is a reserved name family with no
// x-minio-internal- SSE key; cover it here so this predicate is safe to // writer today (see RUSTFS_INTERNAL_ENCRYPTION_PREFIX); cover it here so
// use standalone, without an is_internal_key backstop. // this predicate is safe to use standalone, without an is_internal_key
// backstop.
super::is_encryption_metadata_key(key) super::is_encryption_metadata_key(key)
|| super::is_sse_header(key) || super::is_sse_header(key)
|| key.eq_ignore_ascii_case(SSEC_ORIGINAL_SIZE_HEADER) || key.eq_ignore_ascii_case(SSEC_ORIGINAL_SIZE_HEADER)
|| super::starts_with_ignore_ascii_case(key, "x-rustfs-internal-server-side-encryption-") || super::starts_with_ignore_ascii_case(key, RUSTFS_INTERNAL_ENCRYPTION_PREFIX)
} }
#[cfg(test)] #[cfg(test)]
+5 -6
View File
@@ -30,6 +30,11 @@ use rustfs_utils::http::headers::{
AMZ_ENCRYPTION_AES, AMZ_ENCRYPTION_KMS, AMZ_SERVER_SIDE_ENCRYPTION, AMZ_SERVER_SIDE_ENCRYPTION_KMS_CONTEXT, AMZ_ENCRYPTION_AES, AMZ_ENCRYPTION_KMS, AMZ_SERVER_SIDE_ENCRYPTION, AMZ_SERVER_SIDE_ENCRYPTION_KMS_CONTEXT,
AMZ_SERVER_SIDE_ENCRYPTION_KMS_ID, SSEC_ALGORITHM_HEADER, SSEC_KEY_HEADER, SSEC_KEY_MD5_HEADER, AMZ_SERVER_SIDE_ENCRYPTION_KMS_ID, SSEC_ALGORITHM_HEADER, SSEC_KEY_HEADER, SSEC_KEY_MD5_HEADER,
}; };
use rustfs_utils::http::object_encryption_keys::{
INTERNAL_ENCRYPTION_KEY_ID_HEADER, MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER, MINIO_INTERNAL_ENCRYPTION_KMS_KEY_ID_HEADER,
MINIO_INTERNAL_ENCRYPTION_KMS_SEALED_KEY_HEADER, MINIO_INTERNAL_ENCRYPTION_S3_SEALED_KEY_HEADER,
MINIO_INTERNAL_ENCRYPTION_SSEC_SEALED_KEY_HEADER,
};
use s3s::dto::{ use s3s::dto::{
CSVOutput, CompressionType, ContinuationEvent, EndEvent, ExpressionType, FileHeaderInfo, InputSerialization, JSONInput, CSVOutput, CompressionType, ContinuationEvent, EndEvent, ExpressionType, FileHeaderInfo, InputSerialization, JSONInput,
JSONOutput, JSONType, OutputSerialization, Progress, ProgressEvent, QuoteFields, RecordsEvent, SelectObjectContentEvent, JSONOutput, JSONType, OutputSerialization, Progress, ProgressEvent, QuoteFields, RecordsEvent, SelectObjectContentEvent,
@@ -57,12 +62,6 @@ const BUSY_MESSAGE: &str = "The service is unavailable. Try again later.";
const EMPTY_SELECT_EXPRESSION_MESSAGE: &str = "empty SQL expression"; const EMPTY_SELECT_EXPRESSION_MESSAGE: &str = "empty SQL expression";
const SLOW_DOWN_MESSAGE: &str = "Reduce your request rate."; const SLOW_DOWN_MESSAGE: &str = "Reduce your request rate.";
const UNSUPPORTED_SQL_STRUCTURE_MESSAGE: &str = "We encountered an unsupported SQL structure. Check the SQL Reference."; const UNSUPPORTED_SQL_STRUCTURE_MESSAGE: &str = "We encountered an unsupported SQL structure. Check the SQL Reference.";
use rustfs_utils::http::object_encryption_keys::{
INTERNAL_ENCRYPTION_KEY_ID_HEADER, MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER, MINIO_INTERNAL_ENCRYPTION_KMS_KEY_ID_HEADER,
MINIO_INTERNAL_ENCRYPTION_KMS_SEALED_KEY_HEADER, MINIO_INTERNAL_ENCRYPTION_S3_SEALED_KEY_HEADER,
MINIO_INTERNAL_ENCRYPTION_SSEC_SEALED_KEY_HEADER,
};
// No canonical owner exists for the KMS key ARN prefix; keep it local. // No canonical owner exists for the KMS key ARN prefix; keep it local.
const SELECT_KMS_ARN_PREFIX: &str = "arn:aws:kms:"; const SELECT_KMS_ARN_PREFIX: &str = "arn:aws:kms:";
+3 -3
View File
@@ -19,7 +19,9 @@ use http::{HeaderMap, HeaderValue};
use rustfs_utils::http::{ use rustfs_utils::http::{
AMZ_BUCKET_REPLICATION_STATUS, SUFFIX_FORCE_DELETE, SUFFIX_REPLICATION_ACTUAL_OBJECT_SIZE, SUFFIX_REPLICATION_SSEC_CRC, AMZ_BUCKET_REPLICATION_STATUS, SUFFIX_FORCE_DELETE, SUFFIX_REPLICATION_ACTUAL_OBJECT_SIZE, SUFFIX_REPLICATION_SSEC_CRC,
SUFFIX_SOURCE_DELETEMARKER, SUFFIX_SOURCE_ETAG, SUFFIX_SOURCE_MTIME, SUFFIX_SOURCE_REPLICATION_REQUEST, SUFFIX_SOURCE_DELETEMARKER, SUFFIX_SOURCE_ETAG, SUFFIX_SOURCE_MTIME, SUFFIX_SOURCE_REPLICATION_REQUEST,
SUFFIX_SOURCE_VERSION_ID, get_header, insert_header_map, SUFFIX_SOURCE_VERSION_ID, get_header,
header_compat::{MINIO_ENCRYPTION_PREFIX, RUSTFS_ENCRYPTION_PREFIX},
insert_header_map,
metadata_compat::{MINIO_INTERNAL_PREFIX, RUSTFS_INTERNAL_PREFIX}, metadata_compat::{MINIO_INTERNAL_PREFIX, RUSTFS_INTERNAL_PREFIX},
}; };
use rustfs_utils::http::{ use rustfs_utils::http::{
@@ -646,8 +648,6 @@ fn archive_content_encoding_strict_mode() -> bool {
const USER_METADATA_PREFIXES: &[&str] = &["x-amz-meta-", "x-rustfs-meta-", "x-minio-meta-"]; const USER_METADATA_PREFIXES: &[&str] = &["x-amz-meta-", "x-rustfs-meta-", "x-minio-meta-"];
const CANONICAL_USER_METADATA_PREFIX: &str = "x-amz-meta-"; const CANONICAL_USER_METADATA_PREFIX: &str = "x-amz-meta-";
const RUSTFS_ENCRYPTION_PREFIX: &str = "x-rustfs-encryption-";
const MINIO_ENCRYPTION_PREFIX: &str = "x-minio-encryption-";
/// Keys a client must not be able to materialize as bare stored metadata. /// Keys a client must not be able to materialize as bare stored metadata.
/// ///