Files
rustfs/crates/kms/examples/kms_local_demo.rs
T
Zhengchao An 8387528c9b feat(kms): record real cache hit, miss and eviction metrics (#5531)
* feat(kms): record real cache hit, miss and eviction metrics

The metadata cache reported (entry_count, 0) because moka exposes no hit
or miss counts, so the miss half of every cache report was a constant.

Track lookups and removals in the cache itself: hit/miss counters on the
lookup path, a moka eviction listener classifying removals by cause, and
an entry gauge refreshed whenever the entry set changes. The counters are
exported through the metrics facade under the rustfs_kms_ prefix with
static label values only, matching the operation-policy metrics, and are
also returned as a KmsCacheStats snapshot in place of the old tuple.

Cache semantics are unchanged: capacity, TTL and invalidation points are
the same, and remove now flushes pending maintenance so the gauge and the
removal notification describe the cache the caller sees.

Refs rustfs/backlog#1584

* fix(kms): report real cache counters through the admin status API

KmsStatusResponse.cache_stats mapped the old (entry_count, 0) tuple onto
hit_count and miss_count, so operators polling KMS status read the entry
count as a hit count and a miss count that was always zero.

Map the fields to the counters they claim to be, and add entry_count and
eviction_count as additive, defaulted fields so the entry number that
hit_count used to carry is still available.

Refs rustfs/backlog#1584

* fix(kms): refresh the cache entry gauge on lookup misses

The entry gauge was published only from the write paths, so an entry
dropped by TTL expiry left `rustfs_kms_metadata_cache_entries` reporting
a population that no longer existed until the next put, remove or clear.
A cache that goes quiet — entries ageing out with no further writes —
kept over-reporting indefinitely.

Republish the gauge from the lookup path when the lookup misses. A miss
is where expiry surfaces, and moka reaps expired entries in the
maintenance it runs during that same lookup, so the count read
afterwards reflects the reaping. Hits stay free of the extra work.

* docs(kms): correct the entry gauge convergence claim on the miss path

The comment on the miss-path gauge refresh said moka reaps expired
entries in the maintenance it runs on that same lookup. It does not:
`should_apply_reads` is gated on a full read log or an elapsed
housekeeping interval, so the removal that decrements `entry_count` and
reaches the eviction listener may land on a later lookup.

The behaviour and the test are unchanged — the gauge still converges,
and the test drives `run_pending_tasks` explicitly rather than riding on
that interval. Only the stated guarantee was wrong, so say interval
instead of same-lookup and record why forcing maintenance on the read
path was not the trade taken.
2026-08-01 05:19:25 +00:00

254 lines
11 KiB
Rust

// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! KMS Demo - Comprehensive example demonstrating RustFS KMS capabilities
//!
//! This example demonstrates:
//! - Initializing and configuring KMS service
//! - Creating master keys
//! - Generating data encryption keys
//! - Encrypting and decrypting data using high-level APIs
//! - Key management operations
//! - Cache statistics
//!
//! Run with: `cargo run --example demo1`
use rustfs_kms::{
CreateKeyRequest, DescribeKeyRequest, EncryptionAlgorithm, GenerateDataKeyRequest, KeySpec, KeyUsage, KmsConfig,
ListKeysRequest, init_global_kms_service_manager,
};
use std::collections::HashMap;
use std::fs;
use std::io::Cursor;
use tokio::io::AsyncReadExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Note: Tracing is optional - if tracing-subscriber is not available,
// the example will still work but with less detailed logging
println!("=== RustFS KMS Demo ===\n");
// Step 1: Initialize global KMS service manager
println!("1. Initializing KMS service manager...");
let service_manager = init_global_kms_service_manager();
println!(" ✓ Service manager initialized\n");
// Step 2: Create a temporary directory for local backend
println!("2. Setting up local backend...");
if fs::metadata("examples/local_data").is_err() {
fs::create_dir_all("examples/local_data")?;
}
let data_dir = std::path::PathBuf::from("examples/local_data");
println!(" ✓ Using data directory: {}\n", data_dir.display());
// Step 3: Configure KMS with local backend
println!("3. Configuring KMS with local backend...");
let config = KmsConfig::local(data_dir)
.with_default_key("demo-key-default-1".to_string())
.with_cache(true);
service_manager.configure(config).await?;
println!(" ✓ KMS configured\n");
// Step 4: Start the KMS service
println!("4. Starting KMS service...");
service_manager.start().await?;
println!(" ✓ KMS service started\n");
// Step 5: Get the encryption service
println!("5. Getting encryption service...");
let encryption_service = rustfs_kms::get_global_encryption_service()
.await
.ok_or("Encryption service not available")?;
println!(" ✓ Encryption service obtained\n");
// Step 6: Create a master key
println!("6. Creating a master key...");
let create_request = CreateKeyRequest {
key_name: Some("demo-key-master-1".to_string()),
key_usage: KeyUsage::EncryptDecrypt,
description: Some("Demo master key for encryption".to_string()),
policy: None,
tags: {
let mut tags = HashMap::new();
tags.insert("environment".to_string(), "demo".to_string());
tags.insert("purpose".to_string(), "testing".to_string());
tags
},
origin: Some("demo1.rs".to_string()),
};
let create_response = encryption_service.create_key(create_request).await?;
println!(" ✓ Master key created:");
println!(" - Key ID: {}", create_response.key_id);
println!(" - Key State: {:?}", create_response.key_metadata.key_state);
println!(" - Key Usage: {:?}", create_response.key_metadata.key_usage);
println!(" - Created: {}\n", create_response.key_metadata.creation_date);
let master_key_id = create_response.key_id.clone();
// Step 7: Describe the key
println!("7. Describing the master key...");
let describe_request = DescribeKeyRequest {
key_id: master_key_id.clone(),
};
let describe_response = encryption_service.describe_key(describe_request).await?;
let metadata = describe_response.key_metadata;
println!(" ✓ Key details:");
println!(" - Key ID: {}", metadata.key_id);
println!(" - Description: {:?}", metadata.description);
println!(" - Key Usage: {:?}", metadata.key_usage);
println!(" - Key State: {:?}", metadata.key_state);
println!(" - Tags: {:?}\n", metadata.tags);
// Step 8: Generate a data encryption key (OPTIONAL - for demonstration only)
// NOTE: This step is OPTIONAL and only for educational purposes!
// In real usage, you can skip this step and go directly to Step 9.
// encrypt_object() will automatically generate a data key internally.
println!("8. [OPTIONAL] Generating a data encryption key (for demonstration)...");
println!(" ⚠️ This step is OPTIONAL - only for understanding the two-layer key architecture:");
println!(" - Master Key (CMK): Used to encrypt/decrypt data keys");
println!(" - Data Key (DEK): Used to encrypt/decrypt actual data");
println!(" In production, you can skip this and use encrypt_object() directly!\n");
let data_key_request = GenerateDataKeyRequest {
key_id: master_key_id.clone(),
key_spec: KeySpec::Aes256,
encryption_context: {
let mut context = HashMap::new();
context.insert("bucket".to_string(), "demo-bucket".to_string());
context.insert("object_key".to_string(), "demo-object.txt".to_string());
context
},
};
let data_key_response = encryption_service.generate_data_key(data_key_request).await?;
println!(" ✓ Data key generated (for demonstration):");
println!(" - Master Key ID: {}", data_key_response.key_id);
println!(" - Data Key (plaintext) length: {} bytes", data_key_response.plaintext_key.len());
println!(
" - Encrypted Data Key (ciphertext blob) length: {} bytes",
data_key_response.ciphertext_blob.len()
);
println!(" - Note: This data key is NOT used in Step 9 - encrypt_object() generates its own!\n");
// Step 9: Encrypt some data using high-level API
// This is the RECOMMENDED way to encrypt data - everything is handled automatically!
println!("9. Encrypting data using object encryption service (RECOMMENDED)...");
println!(" ✅ This is all you need! encrypt_object() handles everything:");
println!(" 1. Validates/creates the master key (if needed)");
println!(" 2. Generates a NEW data key using the master key (independent of Step 8)");
println!(" 3. Uses the data key to encrypt the actual data");
println!(" 4. Stores the encrypted data key (ciphertext blob) in metadata");
println!(" You only need to provide the master_key_id - everything else is handled!\n");
let plaintext = b"Hello, RustFS KMS! This is a test message for encryption.";
println!(" Plaintext: {}", String::from_utf8_lossy(plaintext));
let reader = Cursor::new(plaintext);
// Just provide the master_key_id - encrypt_object() handles everything internally!
let encryption_result = encryption_service
.encrypt_object(
"demo-bucket",
"demo-object.txt",
reader,
&EncryptionAlgorithm::Aes256,
Some(&master_key_id), // Only need to provide master key ID
None,
)
.await?;
println!(" ✓ Data encrypted:");
println!(" - Encrypted data length: {} bytes", encryption_result.ciphertext.len());
println!(" - Algorithm: {}", encryption_result.metadata.algorithm);
println!(
" - Master Key ID: {} (used to encrypt the data key)",
encryption_result.metadata.key_id
);
println!(
" - Encrypted Data Key length: {} bytes (stored in metadata)",
encryption_result.metadata.encrypted_data_key.len()
);
println!(" - Original size: {} bytes\n", encryption_result.metadata.original_size);
// Step 10: Decrypt the data using high-level API
println!("10. Decrypting data...");
println!(" Note: decrypt_object() has the ENTIRE decryption flow built-in:");
println!(" 1. Extracts the encrypted data key from metadata");
println!(" 2. Uses master key to decrypt the data key");
println!(" 3. Uses the decrypted data key to decrypt the actual data");
println!(" You only need to provide the encrypted data and metadata!\n");
let mut decrypted_reader = encryption_service
.decrypt_object(
"demo-bucket",
"demo-object.txt",
encryption_result.ciphertext.clone(),
&encryption_result.metadata, // Contains everything needed for decryption
None,
)
.await?;
let mut decrypted_data = Vec::new();
decrypted_reader.read_to_end(&mut decrypted_data).await?;
println!(" ✓ Data decrypted:");
println!(" - Decrypted text: {}\n", String::from_utf8_lossy(&decrypted_data));
// Verify decryption
assert_eq!(plaintext, decrypted_data.as_slice());
println!(" ✓ Decryption verified: plaintext matches original\n");
// Step 11: List all keys
println!("11. Listing all keys...");
let list_request = ListKeysRequest {
limit: Some(10),
marker: None,
usage_filter: None,
status_filter: None,
};
let list_response = encryption_service.list_keys(list_request).await?;
println!(" ✓ Keys found: {}", list_response.keys.len());
for (idx, key_info) in list_response.keys.iter().enumerate() {
println!(" {}. {} ({:?})", idx + 1, key_info.key_id, key_info.status);
}
println!();
// Step 12: Check cache statistics
println!("12. Checking cache statistics...");
if let Some(stats) = encryption_service.cache_stats().await {
println!(" ✓ Cache statistics:");
println!(" - Cached entries: {}", stats.entries);
println!(" - Cache hits: {}", stats.hits);
println!(" - Cache misses: {}", stats.misses);
println!(" - Entries evicted: {}\n", stats.evictions);
} else {
println!(" - Cache is disabled\n");
}
// Step 13: Health check
println!("13. Performing health check...");
let is_healthy = encryption_service.health_check().await?;
println!(" ✓ KMS backend is healthy: {}\n", is_healthy);
// Step 14: Stop the service
println!("14. Stopping KMS service...");
service_manager.stop().await?;
println!(" ✓ KMS service stopped\n");
println!("=== Demo completed successfully! ===");
Ok(())
}