mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 00:47:13 +00:00
feat(kms): add master key version to data key envelope contract (#5480)
* fix(kms): restore vault backend test compilation after timeout parameter PR #5472 added an attempt_timeout parameter to VaultKmsClient::new while PR #5474 landed tests still using the one-argument form, leaving 'cargo test -p rustfs-kms' unable to compile on main. Pass the same 30-second timeout the surrounding integration tests already use. * feat(kms): add master key version to data key envelope contract DataKeyEnvelope gains an optional master_key_version field recording which KEK version wrapped the DEK, so rotation-aware backends can load the matching historical material on decrypt. The field is skipped when None, keeping envelopes from non-rotating backends byte-identical to the historical seven-field JSON shape, and legacy envelopes without the field deserialize to None. The envelope discriminator marker is untouched, so mixed-format routing is unchanged in both directions. Adds the KeyVersionNotFound typed error for version-addressed material lookups that must fail closed instead of falling back to the current version. Refs rustfs/backlog#1565
This commit is contained in:
@@ -942,7 +942,8 @@ impl KmsClient for LocalKmsClient {
|
||||
// Encrypt the data key with the master key
|
||||
let (encrypted_key, nonce) = self.encrypt_with_master_key(&request.master_key_id, &plaintext_key).await?;
|
||||
|
||||
// Create data key envelope with master key version for rotation support
|
||||
// Local rotation is rejected, so every envelope is wrapped by the key's sole
|
||||
// material and needs no master key version.
|
||||
let envelope = DataKeyEnvelope {
|
||||
key_id: uuid::Uuid::new_v4().to_string(),
|
||||
master_key_id: request.master_key_id.clone(),
|
||||
@@ -951,6 +952,7 @@ impl KmsClient for LocalKmsClient {
|
||||
nonce,
|
||||
encryption_context: request.encryption_context.clone(),
|
||||
created_at: Zoned::now(),
|
||||
master_key_version: None,
|
||||
};
|
||||
|
||||
// Serialize the envelope as the ciphertext
|
||||
|
||||
@@ -137,6 +137,8 @@ impl KmsClient for StaticKmsBackend {
|
||||
nonce: nonce_bytes.to_vec(),
|
||||
encryption_context: request.encryption_context.clone(),
|
||||
created_at: Zoned::now(),
|
||||
// The static backend has a single fixed key with no rotation.
|
||||
master_key_version: None,
|
||||
};
|
||||
let ciphertext = serde_json::to_vec(&envelope)?;
|
||||
|
||||
@@ -181,6 +183,8 @@ impl KmsClient for StaticKmsBackend {
|
||||
nonce: nonce_bytes.to_vec(),
|
||||
encryption_context: request.encryption_context.clone(),
|
||||
created_at: Zoned::now(),
|
||||
// The static backend has a single fixed key with no rotation.
|
||||
master_key_version: None,
|
||||
};
|
||||
let ciphertext = serde_json::to_vec(&envelope)?;
|
||||
|
||||
|
||||
@@ -311,6 +311,7 @@ impl KmsClient for VaultKmsClient {
|
||||
nonce,
|
||||
encryption_context: request.encryption_context.clone(),
|
||||
created_at: Zoned::now(),
|
||||
master_key_version: None,
|
||||
};
|
||||
|
||||
// Serialize the envelope as the ciphertext
|
||||
@@ -938,7 +939,9 @@ mod tests {
|
||||
async fn test_vault_kv2_rotate_key_rejected_without_touching_storage() {
|
||||
// No Vault instance needed: rotation must be rejected before any storage access,
|
||||
// so the call cannot read or overwrite key material.
|
||||
let client = VaultKmsClient::new(integration_vault_config()).await.expect("client");
|
||||
let client = VaultKmsClient::new(integration_vault_config(), Duration::from_secs(30))
|
||||
.await
|
||||
.expect("client");
|
||||
|
||||
let err = client
|
||||
.rotate_key("any-key", None)
|
||||
@@ -950,7 +953,9 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_vault_kv2_backend_info_reports_at_rest_protection() {
|
||||
let client = VaultKmsClient::new(integration_vault_config()).await.expect("client");
|
||||
let client = VaultKmsClient::new(integration_vault_config(), Duration::from_secs(30))
|
||||
.await
|
||||
.expect("client");
|
||||
|
||||
let info = client.backend_info();
|
||||
assert_eq!(info.backend_type, "vault-kv2");
|
||||
@@ -962,7 +967,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
#[ignore] // Requires a running Vault instance (dev mode)
|
||||
async fn test_vault_kv2_rotate_rejected_and_material_untouched() {
|
||||
let client = VaultKmsClient::new(integration_vault_config()).await.expect("client");
|
||||
let client = VaultKmsClient::new(integration_vault_config(), Duration::from_secs(30))
|
||||
.await
|
||||
.expect("client");
|
||||
|
||||
let key_id = format!("rotate-{}", uuid::Uuid::new_v4());
|
||||
client.create_key(&key_id, "AES_256", None).await.expect("create");
|
||||
|
||||
@@ -406,6 +406,9 @@ impl KmsClient for VaultTransitKmsClient {
|
||||
nonce: Vec::new(),
|
||||
encryption_context: request.encryption_context.clone(),
|
||||
created_at: Zoned::now(),
|
||||
// Transit ciphertext already self-describes its key version
|
||||
// ("vault:vN:..."), so the envelope never carries one.
|
||||
master_key_version: None,
|
||||
};
|
||||
|
||||
let ciphertext = serde_json::to_vec(&envelope)?;
|
||||
|
||||
Reference in New Issue
Block a user