// 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. #![allow(dead_code)] #![allow(clippy::upper_case_acronyms)] //! KMS-specific utilities for end-to-end tests //! //! This module provides KMS-specific functionality including: //! - Vault server management and configuration //! - KMS backend configuration (Local and Vault) //! - SSE encryption testing utilities use crate::common::{ RustFSTestEnvironment, awscurl_available, awscurl_get, awscurl_post, init_logging as common_init_logging, local_http_client, }; use aws_sdk_s3::Client; use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::types::ServerSideEncryption; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use serde_json; use std::process::{Child, Command}; use std::time::Duration; use tokio::fs; use tokio::net::TcpStream; use tokio::time::sleep; use tracing::{debug, error, info}; // KMS-specific constants pub const TEST_BUCKET: &str = "kms-test-bucket"; // Vault constants pub const VAULT_URL: &str = "http://127.0.0.1:8200"; pub const VAULT_ADDRESS: &str = "127.0.0.1:8200"; pub const VAULT_TOKEN: &str = "dev-root-token"; pub const VAULT_TRANSIT_PATH: &str = "transit"; pub const VAULT_KEY_NAME: &str = "rustfs-master-key"; pub const ENV_TEST_VAULT_BIN: &str = "RUSTFS_TEST_VAULT_BIN"; /// Initialize tracing for KMS tests with KMS-specific log levels pub fn init_logging() { common_init_logging(); // Additional KMS-specific logging configuration can be added here if needed } pub fn skip_if_kms_admin_tool_unavailable(test_name: &str) -> bool { if awscurl_available() { return false; } info!("Skipping {} because awscurl is not available in PATH", test_name); true } pub fn sse_customer_key_md5_base64(key: &str) -> String { BASE64.encode(md5::compute(key).0) } // KMS-specific helper functions /// Configure KMS backend via admin API pub async fn configure_kms( base_url: &str, config_json: &str, access_key: &str, secret_key: &str, ) -> Result<(), Box> { let url = format!("{base_url}/rustfs/admin/v3/kms/configure"); awscurl_post(&url, config_json, access_key, secret_key).await?; info!("KMS configured successfully"); Ok(()) } /// Start KMS service via admin API pub async fn start_kms( base_url: &str, access_key: &str, secret_key: &str, ) -> Result<(), Box> { let url = format!("{base_url}/rustfs/admin/v3/kms/start"); awscurl_post(&url, "{}", access_key, secret_key).await?; info!("KMS started successfully"); Ok(()) } /// Get KMS status via admin API pub async fn get_kms_status( base_url: &str, access_key: &str, secret_key: &str, ) -> Result> { let url = format!("{base_url}/rustfs/admin/v3/kms/status"); let status = awscurl_get(&url, access_key, secret_key).await?; info!("KMS status retrieved: {}", status); Ok(status) } /// Create a default KMS key for testing and return the created key ID pub async fn create_default_key( base_url: &str, access_key: &str, secret_key: &str, ) -> Result> { let create_key_body = serde_json::json!({ "key_usage": "ENCRYPT_DECRYPT", "description": "Default key for e2e testing" }) .to_string(); let url = format!("{base_url}/rustfs/admin/v3/kms/keys"); let response = awscurl_post(&url, &create_key_body, access_key, secret_key).await?; // Parse response to get the actual key ID let create_result: serde_json::Value = serde_json::from_str(&response)?; let key_id = create_result["key_id"] .as_str() .ok_or("Failed to get key_id from create response")? .to_string(); info!("Default KMS key created: {}", key_id); Ok(key_id) } /// Create a KMS key with a specific ID (by directly writing to the key directory) pub async fn create_key_with_specific_id(key_dir: &str, key_id: &str) -> Result<(), Box> { use rand::Rng; use std::collections::HashMap; use tokio::fs; // Create a 32-byte AES key let mut key_data = [0u8; 32]; rand::rng().fill_bytes(&mut key_data); // Create the stored key structure that Local KMS backend expects let stored_key = serde_json::json!({ "key_id": key_id, "version": 1u32, "algorithm": "AES_256", "usage": "EncryptDecrypt", "status": "Active", "metadata": HashMap::::new(), "created_at": format!("{}[UTC]", chrono::Utc::now().to_rfc3339()), "rotated_at": serde_json::Value::Null, "created_by": "e2e-test", "encrypted_key_material": BASE64.encode(key_data), "nonce": Vec::::new() }); // Write the key to file with the specified ID as JSON let key_path = format!("{key_dir}/{key_id}.key"); let content = serde_json::to_vec_pretty(&stored_key)?; fs::write(&key_path, &content).await?; info!("Created KMS key with ID '{}' at path: {}", key_id, key_path); Ok(()) } /// Test SSE-C encryption with the given S3 client pub async fn test_sse_c_encryption(s3_client: &Client, bucket: &str) -> Result<(), Box> { info!("Testing SSE-C encryption"); let test_key = "01234567890123456789012345678901"; // 32-byte key let test_key_b64 = base64::engine::general_purpose::STANDARD.encode(test_key); let test_key_md5 = sse_customer_key_md5_base64(test_key); let test_data = b"Hello, KMS SSE-C World!"; let object_key = "test-sse-c-object"; // Upload with SSE-C (customer-provided key encryption) // Note: For SSE-C, we should NOT set server_side_encryption, only the customer key headers let put_response = s3_client .put_object() .bucket(bucket) .key(object_key) .body(ByteStream::from(test_data.to_vec())) .sse_customer_algorithm("AES256") .sse_customer_key(&test_key_b64) .sse_customer_key_md5(&test_key_md5) .send() .await?; info!("SSE-C upload successful, ETag: {:?}", put_response.e_tag()); // For SSE-C, server_side_encryption should be None since customer provides the key // The encryption algorithm is specified via SSE-C headers instead // Download with SSE-C info!("Starting SSE-C download test"); let get_response = s3_client .get_object() .bucket(bucket) .key(object_key) .sse_customer_algorithm("AES256") .sse_customer_key(&test_key_b64) .sse_customer_key_md5(&test_key_md5) .send() .await?; info!("SSE-C download successful"); info!("Starting to collect response body"); let downloaded_data = get_response.body.collect().await?.into_bytes(); info!("Downloaded data length: {}, expected length: {}", downloaded_data.len(), test_data.len()); assert_eq!(downloaded_data.as_ref(), test_data); // For SSE-C, we don't check server_side_encryption since it's customer-managed info!("SSE-C encryption test completed successfully"); Ok(()) } /// Test SSE-S3 encryption (server-managed keys) pub async fn test_sse_s3_encryption(s3_client: &Client, bucket: &str) -> Result<(), Box> { info!("Testing SSE-S3 encryption"); let test_data = b"Hello, KMS SSE-S3 World!"; let object_key = "test-sse-s3-object"; // Upload with SSE-S3 let put_response = s3_client .put_object() .bucket(bucket) .key(object_key) .body(ByteStream::from(test_data.to_vec())) .server_side_encryption(ServerSideEncryption::Aes256) .send() .await?; info!("SSE-S3 upload successful, ETag: {:?}", put_response.e_tag()); assert_eq!(put_response.server_side_encryption(), Some(&ServerSideEncryption::Aes256)); // Download object let get_response = s3_client.get_object().bucket(bucket).key(object_key).send().await?; let encryption = get_response.server_side_encryption().cloned(); let downloaded_data = get_response.body.collect().await?.into_bytes(); assert_eq!(downloaded_data.as_ref(), test_data); assert_eq!(encryption, Some(ServerSideEncryption::Aes256)); info!("SSE-S3 encryption test completed successfully"); Ok(()) } /// Test SSE-KMS encryption (KMS-managed keys) pub async fn test_sse_kms_encryption( s3_client: &aws_sdk_s3::Client, bucket: &str, ) -> Result<(), Box> { info!("Testing SSE-KMS encryption"); let object_key = "test-sse-kms-object"; let test_data = b"Hello, SSE-KMS World! This data should be encrypted with KMS-managed keys."; // Upload object with SSE-KMS encryption let put_response = s3_client .put_object() .bucket(bucket) .key(object_key) .body(aws_sdk_s3::primitives::ByteStream::from(test_data.to_vec())) .server_side_encryption(ServerSideEncryption::AwsKms) .send() .await?; info!("SSE-KMS upload successful, ETag: {:?}", put_response.e_tag()); assert_eq!(put_response.server_side_encryption(), Some(&ServerSideEncryption::AwsKms)); // Download object let get_response = s3_client.get_object().bucket(bucket).key(object_key).send().await?; let encryption = get_response.server_side_encryption().cloned(); let downloaded_data = get_response.body.collect().await?.into_bytes(); assert_eq!(downloaded_data.as_ref(), test_data); assert_eq!(encryption, Some(ServerSideEncryption::AwsKms)); info!("SSE-KMS encryption test completed successfully"); Ok(()) } /// Test KMS key management APIs pub async fn test_kms_key_management( base_url: &str, access_key: &str, secret_key: &str, ) -> Result<(), Box> { if skip_if_kms_admin_tool_unavailable("test_kms_key_management") { return Ok(()); } info!("Testing KMS key management APIs"); // Test CreateKey let create_key_body = serde_json::json!({ "key_usage": "EncryptDecrypt", "description": "Test key for e2e testing" }) .to_string(); let create_response = awscurl_post(&format!("{base_url}/rustfs/admin/v3/kms/keys"), &create_key_body, access_key, secret_key).await?; let create_result: serde_json::Value = serde_json::from_str(&create_response)?; let key_id = create_result["key_id"] .as_str() .ok_or("Failed to get key_id from create response")?; info!("Created key with ID: {}", key_id); // Test DescribeKey let describe_response = awscurl_get(&format!("{base_url}/rustfs/admin/v3/kms/keys/{key_id}"), access_key, secret_key).await?; info!("DescribeKey response: {}", describe_response); let describe_result: serde_json::Value = serde_json::from_str(&describe_response)?; info!("Parsed describe result: {:?}", describe_result); assert_eq!(describe_result["key_metadata"]["key_id"], key_id); info!("Successfully described key: {}", key_id); // Test ListKeys let list_response = awscurl_get(&format!("{base_url}/rustfs/admin/v3/kms/keys"), access_key, secret_key).await?; let list_result: serde_json::Value = serde_json::from_str(&list_response)?; let keys = list_result["keys"] .as_array() .ok_or("Failed to get keys array from list response")?; let found_key = keys.iter().any(|k| k["key_id"].as_str() == Some(key_id)); assert!(found_key, "Created key not found in list"); info!("Successfully listed keys, found created key"); info!("KMS key management API tests completed successfully"); Ok(()) } /// Test error scenarios pub async fn test_error_scenarios(s3_client: &Client, bucket: &str) -> Result<(), Box> { info!("Testing error scenarios"); // Test SSE-C with wrong key for download let test_key = "01234567890123456789012345678901"; let wrong_key = "98765432109876543210987654321098"; let test_key_b64 = base64::engine::general_purpose::STANDARD.encode(test_key); let wrong_key_b64 = base64::engine::general_purpose::STANDARD.encode(wrong_key); let test_key_md5 = sse_customer_key_md5_base64(test_key); let wrong_key_md5 = sse_customer_key_md5_base64(wrong_key); let test_data = b"Test data for error scenarios"; let object_key = "test-error-object"; // Upload with correct key (SSE-C) s3_client .put_object() .bucket(bucket) .key(object_key) .body(ByteStream::from(test_data.to_vec())) .sse_customer_algorithm("AES256") .sse_customer_key(&test_key_b64) .sse_customer_key_md5(&test_key_md5) .send() .await?; // Try to download with wrong key - should fail let wrong_key_result = s3_client .get_object() .bucket(bucket) .key(object_key) .sse_customer_algorithm("AES256") .sse_customer_key(&wrong_key_b64) .sse_customer_key_md5(&wrong_key_md5) .send() .await; assert!(wrong_key_result.is_err(), "Download with wrong SSE-C key should fail"); info!("โœ… Correctly rejected download with wrong SSE-C key"); info!("Error scenario tests completed successfully"); Ok(()) } /// Vault test environment management pub struct VaultTestEnvironment { pub base_env: RustFSTestEnvironment, pub vault_process: Option, } impl VaultTestEnvironment { fn resolve_vault_binary() -> String { std::env::var(ENV_TEST_VAULT_BIN).unwrap_or_else(|_| "vault".to_string()) } /// Create a new Vault test environment pub async fn new() -> Result> { let base_env = RustFSTestEnvironment::new().await?; Ok(Self { base_env, vault_process: None, }) } /// Start Vault server in development mode pub async fn start_vault(&mut self) -> Result<(), Box> { info!("Starting Vault server in development mode"); let vault_binary = Self::resolve_vault_binary(); let vault_process = Command::new(&vault_binary) .args([ "server", "-dev", "-dev-root-token-id", VAULT_TOKEN, "-dev-listen-address", VAULT_ADDRESS, ]) .spawn()?; self.vault_process = Some(vault_process); // Wait for Vault to start self.wait_for_vault_ready().await?; Ok(()) } async fn wait_for_vault_ready(&self) -> Result<(), Box> { info!("Waiting for Vault server to be ready..."); for i in 0..30 { let port_check = TcpStream::connect(VAULT_ADDRESS).await.is_ok(); if port_check { // Additional check by making a health request if let Ok(response) = local_http_client().get(format!("{VAULT_URL}/v1/sys/health")).send().await && response.status().is_success() { info!("Vault server is ready after {} seconds", i); return Ok(()); } } if i == 29 { return Err("Vault server failed to become ready".into()); } sleep(Duration::from_secs(1)).await; } Ok(()) } /// Setup Vault transit secrets engine pub async fn setup_vault_transit(&self) -> Result<(), Box> { let client = local_http_client(); info!("Enabling Vault transit secrets engine"); // Enable transit secrets engine let enable_response = client .post(format!("{VAULT_URL}/v1/sys/mounts/{VAULT_TRANSIT_PATH}")) .header("X-Vault-Token", VAULT_TOKEN) .json(&serde_json::json!({ "type": "transit" })) .send() .await?; if !enable_response.status().is_success() && enable_response.status() != 400 { let error_text = enable_response.text().await?; return Err(format!("Failed to enable transit engine: {error_text}").into()); } info!("Creating Vault encryption key"); // Create encryption key let key_response = client .post(format!("{VAULT_URL}/v1/{VAULT_TRANSIT_PATH}/keys/{VAULT_KEY_NAME}")) .header("X-Vault-Token", VAULT_TOKEN) .json(&serde_json::json!({ "type": "aes256-gcm96" })) .send() .await?; if !key_response.status().is_success() && key_response.status() != 400 { let error_text = key_response.text().await?; return Err(format!("Failed to create encryption key: {error_text}").into()); } info!("Vault transit engine setup completed"); Ok(()) } /// Start RustFS server for Vault backend; dynamic configuration will be applied later. pub async fn start_rustfs_for_vault(&mut self) -> Result<(), Box> { self.base_env.start_rustfs_server(Vec::new()).await } /// Configure Vault Transit KMS backend pub async fn configure_vault_transit_kms(&self) -> Result<(), Box> { let kms_config = serde_json::json!({ "backend_type": "VaultTransit", "address": VAULT_URL, "auth_method": { "Token": { "token": VAULT_TOKEN } }, "mount_path": VAULT_TRANSIT_PATH, "default_key_id": VAULT_KEY_NAME, "skip_tls_verify": true }) .to_string(); configure_kms(&self.base_env.url, &kms_config, &self.base_env.access_key, &self.base_env.secret_key).await } } impl Drop for VaultTestEnvironment { fn drop(&mut self) { if let Some(mut process) = self.vault_process.take() { info!("Terminating Vault process"); if let Err(e) = process.kill() { error!("Failed to kill Vault process: {}", e); } else { let _ = process.wait(); } } } } /// Encryption types for multipart upload testing #[derive(Debug, Clone)] pub enum EncryptionType { None, SSES3, SSEKMS, SSEC { key: String, key_md5: String }, } /// Configuration for multipart upload tests #[derive(Debug, Clone)] pub struct MultipartTestConfig { pub object_key: String, pub part_size: usize, pub total_parts: usize, pub encryption_type: EncryptionType, } impl MultipartTestConfig { pub fn new(object_key: impl Into, part_size: usize, total_parts: usize, encryption_type: EncryptionType) -> Self { Self { object_key: object_key.into(), part_size, total_parts, encryption_type, } } pub fn total_size(&self) -> usize { self.part_size * self.total_parts } } /// Perform a comprehensive multipart upload test with the specified configuration pub async fn test_multipart_upload_with_config( s3_client: &Client, bucket: &str, config: &MultipartTestConfig, ) -> Result<(), Box> { let total_size = config.total_size(); info!("๐Ÿงช Starting multipart upload test - {:?}", config.encryption_type); info!( " Object: {}, parts: {}, part size: {} MB, total: {} MB", config.object_key, config.total_parts, config.part_size / (1024 * 1024), total_size / (1024 * 1024) ); // Generate test data with patterns for verification let test_data: Vec = (0..total_size) .map(|i| { let part_num = i / config.part_size; let offset_in_part = i % config.part_size; ((part_num * 100 + offset_in_part / 1000) % 256) as u8 }) .collect(); // Prepare encryption parameters let (sse_c_key_b64, sse_c_key_md5) = match &config.encryption_type { EncryptionType::SSEC { key, key_md5 } => { let key_b64 = base64::engine::general_purpose::STANDARD.encode(key); (Some(key_b64), Some(key_md5.clone())) } _ => (None, None), }; // Step 1: Create multipart upload let mut create_request = s3_client.create_multipart_upload().bucket(bucket).key(&config.object_key); create_request = match &config.encryption_type { EncryptionType::None => create_request, EncryptionType::SSES3 => create_request.server_side_encryption(ServerSideEncryption::Aes256), EncryptionType::SSEKMS => create_request.server_side_encryption(ServerSideEncryption::AwsKms), EncryptionType::SSEC { .. } => create_request .sse_customer_algorithm("AES256") .sse_customer_key(sse_c_key_b64.as_ref().unwrap()) .sse_customer_key_md5(sse_c_key_md5.as_ref().unwrap()), }; let create_multipart_output = create_request.send().await?; let upload_id = create_multipart_output.upload_id().unwrap(); info!("๐Ÿ“‹ Created multipart upload, ID: {}", upload_id); // Step 2: Upload parts let mut completed_parts = Vec::new(); for part_number in 1..=config.total_parts { let start = (part_number - 1) * config.part_size; let end = std::cmp::min(start + config.part_size, total_size); let part_data = &test_data[start..end]; info!("๐Ÿ“ค Uploading part {} ({:.2} MB)", part_number, part_data.len() as f64 / (1024.0 * 1024.0)); let mut upload_request = s3_client .upload_part() .bucket(bucket) .key(&config.object_key) .upload_id(upload_id) .part_number(part_number as i32) .body(ByteStream::from(part_data.to_vec())); // Add encryption headers for SSE-C parts if let EncryptionType::SSEC { .. } = &config.encryption_type { upload_request = upload_request .sse_customer_algorithm("AES256") .sse_customer_key(sse_c_key_b64.as_ref().unwrap()) .sse_customer_key_md5(sse_c_key_md5.as_ref().unwrap()); } let upload_part_output = upload_request.send().await?; let etag = upload_part_output.e_tag().unwrap().to_string(); completed_parts.push( aws_sdk_s3::types::CompletedPart::builder() .part_number(part_number as i32) .e_tag(&etag) .build(), ); debug!("Part {} uploaded with ETag {}", part_number, etag); } // Step 3: Complete multipart upload let completed_multipart_upload = aws_sdk_s3::types::CompletedMultipartUpload::builder() .set_parts(Some(completed_parts)) .build(); info!("๐Ÿ”— Completing multipart upload"); let complete_output = s3_client .complete_multipart_upload() .bucket(bucket) .key(&config.object_key) .upload_id(upload_id) .multipart_upload(completed_multipart_upload) .send() .await?; debug!("Multipart upload finalized with ETag {:?}", complete_output.e_tag()); // Step 4: Download and verify info!("๐Ÿ“ฅ Downloading object for verification"); let mut get_request = s3_client.get_object().bucket(bucket).key(&config.object_key); // Add encryption headers for SSE-C GET if let EncryptionType::SSEC { .. } = &config.encryption_type { get_request = get_request .sse_customer_algorithm("AES256") .sse_customer_key(sse_c_key_b64.as_ref().unwrap()) .sse_customer_key_md5(sse_c_key_md5.as_ref().unwrap()); } let get_response = get_request.send().await?; // Verify encryption headers match &config.encryption_type { EncryptionType::None => { assert_eq!(get_response.server_side_encryption(), None); } EncryptionType::SSES3 => { assert_eq!(get_response.server_side_encryption(), Some(&ServerSideEncryption::Aes256)); } EncryptionType::SSEKMS => { assert_eq!(get_response.server_side_encryption(), Some(&ServerSideEncryption::AwsKms)); } EncryptionType::SSEC { .. } => { assert_eq!(get_response.sse_customer_algorithm(), Some("AES256")); } } // Verify data integrity let downloaded_data = get_response.body.collect().await?.into_bytes(); assert_eq!(downloaded_data.len(), total_size); assert_eq!(&downloaded_data[..], &test_data[..]); info!("โœ… Multipart upload test passed - {:?}", config.encryption_type); Ok(()) } /// Create a standard SSE-C encryption configuration for testing pub fn create_sse_c_config() -> EncryptionType { let key = "01234567890123456789012345678901"; // 32-byte key let key_md5 = sse_customer_key_md5_base64(key); EncryptionType::SSEC { key: key.to_string(), key_md5, } } /// Test all encryption types for multipart uploads pub async fn test_all_multipart_encryption_types( s3_client: &Client, bucket: &str, base_object_key: &str, ) -> Result<(), Box> { info!("๐Ÿงช Testing multipart uploads for every encryption type"); let part_size = 5 * 1024 * 1024; // 5MB per part let total_parts = 2; // Test configurations for all encryption types let test_configs = vec![ MultipartTestConfig::new(format!("{base_object_key}-no-encryption"), part_size, total_parts, EncryptionType::None), MultipartTestConfig::new(format!("{base_object_key}-sse-s3"), part_size, total_parts, EncryptionType::SSES3), MultipartTestConfig::new(format!("{base_object_key}-sse-kms"), part_size, total_parts, EncryptionType::SSEKMS), MultipartTestConfig::new(format!("{base_object_key}-sse-c"), part_size, total_parts, create_sse_c_config()), ]; // Run tests for each encryption type for config in test_configs { test_multipart_upload_with_config(s3_client, bucket, &config).await?; } info!("โœ… Multipart uploads succeeded for every encryption type"); Ok(()) } /// Local KMS test environment management pub struct LocalKMSTestEnvironment { pub base_env: RustFSTestEnvironment, pub kms_keys_dir: String, } impl LocalKMSTestEnvironment { /// Create a new Local KMS test environment pub async fn new() -> Result> { let base_env = RustFSTestEnvironment::new().await?; let kms_keys_dir = format!("{}/kms-keys", base_env.temp_dir); fs::create_dir_all(&kms_keys_dir).await?; Ok(Self { base_env, kms_keys_dir }) } /// Start RustFS server configured for Local KMS backend with a default key pub async fn start_rustfs_for_local_kms(&mut self) -> Result> { // Create a default key first let default_key_id = "rustfs-e2e-test-default-key"; create_key_with_specific_id(&self.kms_keys_dir, default_key_id).await?; let extra_args = vec![ "--kms-enable", "--kms-backend", "local", "--kms-key-dir", &self.kms_keys_dir, "--kms-default-key-id", default_key_id, ]; self.base_env .start_rustfs_server_with_env(extra_args, &[("RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS", "true")]) .await?; Ok(default_key_id.to_string()) } /// Configure Local KMS backend with a predefined default key pub async fn configure_local_kms(&self) -> Result> { // Use a fixed, predictable default key ID let default_key_id = "rustfs-e2e-test-default-key"; // Create the default key file first using our manual method create_key_with_specific_id(&self.kms_keys_dir, default_key_id).await?; // Configure KMS with the default key in one step let kms_config = serde_json::json!({ "backend_type": "Local", "key_dir": self.kms_keys_dir, "file_permissions": 0o600, "default_key_id": default_key_id }) .to_string(); configure_kms(&self.base_env.url, &kms_config, &self.base_env.access_key, &self.base_env.secret_key).await?; Ok(default_key_id.to_string()) } }