feat(kms): add vault transit engine

This commit is contained in:
reatang
2026-04-03 00:46:00 +08:00
parent 84f58af628
commit 30a757a9da
20 changed files with 1232 additions and 75 deletions
+54 -2
View File
@@ -42,6 +42,48 @@ fn kms_service_manager_from_context() -> std::sync::Arc<rustfs_kms::KmsServiceMa
})
}
fn token_is_blank(auth_method: &rustfs_kms::config::VaultAuthMethod) -> bool {
matches!(
auth_method,
rustfs_kms::config::VaultAuthMethod::Token { token } if token.trim().is_empty()
)
}
fn existing_vault_auth(config: &KmsConfig) -> Option<rustfs_kms::config::VaultAuthMethod> {
match &config.backend_config {
rustfs_kms::config::BackendConfig::VaultKv2(vault) => Some(vault.auth_method.clone()),
rustfs_kms::config::BackendConfig::VaultTransit(vault) => Some(vault.auth_method.clone()),
rustfs_kms::config::BackendConfig::Local(_) => None,
}
}
fn normalize_configure_request_auth(
request: &mut ConfigureKmsRequest,
existing_config: Option<&KmsConfig>,
) -> Result<(), String> {
let needs_existing_auth = match request {
ConfigureKmsRequest::VaultKv2(req) => token_is_blank(&req.auth_method),
ConfigureKmsRequest::VaultTransit(req) => token_is_blank(&req.auth_method),
ConfigureKmsRequest::Local(_) => false,
};
if !needs_existing_auth {
return Ok(());
}
let existing_auth = existing_config
.and_then(existing_vault_auth)
.ok_or_else(|| "Vault token is required when no existing KMS credentials are available".to_string())?;
match request {
ConfigureKmsRequest::VaultKv2(req) => req.auth_method = existing_auth,
ConfigureKmsRequest::VaultTransit(req) => req.auth_method = existing_auth,
ConfigureKmsRequest::Local(_) => {}
}
Ok(())
}
/// Save KMS configuration to cluster storage
#[instrument(skip(config))]
async fn save_kms_config(config: &KmsConfig) -> Result<(), String> {
@@ -153,7 +195,7 @@ impl Operation for ConfigureKmsHandler {
.await
.map_err(|e| s3_error!(InvalidRequest, "failed to read request body: {}", e))?;
let configure_request: ConfigureKmsRequest = if body.is_empty() {
let mut configure_request: ConfigureKmsRequest = if body.is_empty() {
return Ok(S3Response::new((
StatusCode::BAD_REQUEST,
Body::from("Request body is required".to_string()),
@@ -171,6 +213,11 @@ impl Operation for ConfigureKmsHandler {
info!("Configuring KMS with request: {:?}", configure_request);
let service_manager = kms_service_manager_from_context();
let existing_config = service_manager.get_config().await;
if let Err(e) = normalize_configure_request_auth(&mut configure_request, existing_config.as_ref()) {
return Ok(S3Response::new((StatusCode::BAD_REQUEST, Body::from(e))));
}
// Convert request to KmsConfig
let kms_config = configure_request.to_kms_config();
@@ -508,7 +555,7 @@ impl Operation for ReconfigureKmsHandler {
.await
.map_err(|e| s3_error!(InvalidRequest, "failed to read request body: {}", e))?;
let configure_request: ConfigureKmsRequest = if body.is_empty() {
let mut configure_request: ConfigureKmsRequest = if body.is_empty() {
return Ok(S3Response::new((
StatusCode::BAD_REQUEST,
Body::from("Request body is required".to_string()),
@@ -526,6 +573,11 @@ impl Operation for ReconfigureKmsHandler {
info!("Reconfiguring KMS with request: {:?}", configure_request);
let service_manager = kms_service_manager_from_context();
let existing_config = service_manager.get_config().await;
if let Err(e) = normalize_configure_request_auth(&mut configure_request, existing_config.as_ref()) {
return Ok(S3Response::new((StatusCode::BAD_REQUEST, Body::from(e))));
}
// Convert request to KmsConfig
let kms_config = configure_request.to_kms_config();
+31 -11
View File
@@ -22,7 +22,7 @@ use crate::auth::{check_key_valid, get_session_token};
use crate::server::{ADMIN_PREFIX, RemoteAddr};
use hyper::{HeaderMap, Method, StatusCode};
use matchit::Params;
use rustfs_kms::init_global_kms_service_manager;
use rustfs_kms::{KmsBackend, init_global_kms_service_manager};
use rustfs_policy::policy::action::{Action, AdminAction};
use s3s::header::CONTENT_TYPE;
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
@@ -30,14 +30,26 @@ use serde::{Deserialize, Serialize};
use tracing::{error, info, warn};
async fn kms_encryption_service_from_context() -> Option<std::sync::Arc<rustfs_kms::ObjectEncryptionService>> {
let manager = match resolve_kms_runtime_service_manager() {
let manager = kms_service_manager_from_context();
manager.get_encryption_service().await
}
fn kms_service_manager_from_context() -> std::sync::Arc<rustfs_kms::KmsServiceManager> {
match resolve_kms_runtime_service_manager() {
Some(manager) => manager,
None => {
warn!("KMS service manager not initialized, initializing now as fallback");
init_global_kms_service_manager()
}
};
manager.get_encryption_service().await
}
}
fn backend_name(backend: &KmsBackend) -> &'static str {
match backend {
KmsBackend::Local => "local",
KmsBackend::VaultKv2 => "vault-kv2",
KmsBackend::VaultTransit => "vault-transit",
}
}
#[derive(Debug, Serialize, Deserialize)]
@@ -168,11 +180,15 @@ impl Operation for KmsStatusHandler {
hit_count: hits,
miss_count: misses,
});
let config = kms_service_manager_from_context().get_config().await;
let response = KmsStatusResponse {
backend_type: "vault".to_string(), // TODO: Get from config
backend_type: config
.as_ref()
.map(|cfg| backend_name(&cfg.backend).to_string())
.unwrap_or_else(|| "unknown".to_string()),
backend_status,
cache_enabled: cache_stats.is_some(),
cache_enabled: config.as_ref().is_some_and(|cfg| cfg.enable_cache),
cache_stats,
default_key_id: service.get_default_key_id().cloned(),
};
@@ -213,12 +229,16 @@ impl Operation for KmsConfigHandler {
return Err(s3_error!(InternalError, "KMS service not initialized"));
};
// TODO: Get actual config from service
let config = kms_service_manager_from_context()
.get_config()
.await
.ok_or_else(|| s3_error!(InternalError, "KMS config not available"))?;
let response = KmsConfigResponse {
backend: "vault".to_string(),
cache_enabled: true,
cache_max_keys: 1000,
cache_ttl_seconds: 300,
backend: backend_name(&config.backend).to_string(),
cache_enabled: config.enable_cache,
cache_max_keys: config.cache_config.max_keys,
cache_ttl_seconds: config.cache_config.ttl.as_secs(),
default_key_id: service.get_default_key_id().cloned(),
};
+6 -1
View File
@@ -218,7 +218,7 @@ pub struct ServerOpts {
#[arg(long, default_value_t = false, env = "RUSTFS_KMS_ENABLE")]
pub kms_enable: bool,
/// KMS backend type (local or vault)
/// KMS backend type: local, vault or vault-kv2 (Vault KV2+Transit), vault-transit
#[arg(long, default_value_t = rustfs_config::DEFAULT_KMS_BACKEND.to_string(), env = "RUSTFS_KMS_BACKEND")]
pub kms_backend: String,
@@ -234,6 +234,10 @@ pub struct ServerOpts {
#[arg(long, env = "RUSTFS_KMS_VAULT_TOKEN")]
pub kms_vault_token: Option<String>,
/// Vault mount path for vault or vault-transit backend
#[arg(long, env = "RUSTFS_KMS_VAULT_MOUNT_PATH")]
pub kms_vault_mount_path: Option<String>,
/// Default KMS key ID for encryption
#[arg(long, env = "RUSTFS_KMS_DEFAULT_KEY_ID")]
pub kms_default_key_id: Option<String>,
@@ -284,6 +288,7 @@ pub fn default_server_opts() -> ServerOpts {
kms_key_dir: None,
kms_vault_address: None,
kms_vault_token: None,
kms_vault_mount_path: None,
kms_default_key_id: None,
buffer_profile_disable: false,
buffer_profile: "GeneralPurpose".to_string(),
+6
View File
@@ -97,6 +97,9 @@ pub struct Config {
/// Vault token for vault backend
pub kms_vault_token: Option<String>,
/// Vault mount path for vault or vault-transit backend
pub kms_vault_mount_path: Option<String>,
/// Default KMS key ID for encryption
pub kms_default_key_id: Option<String>,
@@ -129,6 +132,7 @@ impl Config {
kms_key_dir,
kms_vault_address,
kms_vault_token,
kms_vault_mount_path,
kms_default_key_id,
buffer_profile_disable,
buffer_profile,
@@ -157,6 +161,7 @@ impl Config {
kms_key_dir,
kms_vault_address,
kms_vault_token,
kms_vault_mount_path,
kms_default_key_id,
buffer_profile_disable,
buffer_profile,
@@ -197,6 +202,7 @@ impl std::fmt::Debug for Config {
.field("kms_key_dir", &self.kms_key_dir)
.field("kms_vault_address", &self.kms_vault_address)
.field("kms_vault_token", &Masked(self.kms_vault_token.as_deref()))
.field("kms_vault_mount_path", &self.kms_vault_mount_path)
.field("kms_default_key_id", &self.kms_default_key_id)
.field("buffer_profile_disable", &self.buffer_profile_disable)
.field("buffer_profile", &self.buffer_profile)
+2
View File
@@ -46,6 +46,7 @@ pub struct Opt {
pub kms_key_dir: Option<String>,
pub kms_vault_address: Option<String>,
pub kms_vault_token: Option<String>,
pub kms_vault_mount_path: Option<String>,
pub kms_default_key_id: Option<String>,
pub buffer_profile_disable: bool,
pub buffer_profile: String,
@@ -73,6 +74,7 @@ impl Opt {
kms_key_dir: o.kms_key_dir,
kms_vault_address: o.kms_vault_address,
kms_vault_token: o.kms_vault_token,
kms_vault_mount_path: o.kms_vault_mount_path,
kms_default_key_id: o.kms_default_key_id,
buffer_profile_disable: o.buffer_profile_disable,
buffer_profile: o.buffer_profile,
+35 -4
View File
@@ -196,14 +196,14 @@ fn build_vault_kms_config(cfg: &config::Config) -> std::io::Result<rustfs_kms::c
.ok_or_else(|| Error::other("Vault token is required for vault backend"))?;
Ok(rustfs_kms::config::KmsConfig {
backend: rustfs_kms::config::KmsBackend::Vault,
backend_config: rustfs_kms::config::BackendConfig::Vault(Box::new(rustfs_kms::config::VaultConfig {
backend: rustfs_kms::config::KmsBackend::VaultKv2,
backend_config: rustfs_kms::config::BackendConfig::VaultKv2(Box::new(rustfs_kms::config::VaultConfig {
address: vault_address.clone(),
auth_method: rustfs_kms::config::VaultAuthMethod::Token {
token: vault_token.clone(),
},
namespace: None,
mount_path: "transit".to_string(),
mount_path: cfg.kms_vault_mount_path.clone().unwrap_or_else(|| "transit".to_string()),
kv_mount: "secret".to_string(),
key_path_prefix: "rustfs/kms/keys".to_string(),
tls: None,
@@ -216,6 +216,36 @@ fn build_vault_kms_config(cfg: &config::Config) -> std::io::Result<rustfs_kms::c
})
}
/// Build KMS configuration for Vault Transit backend
fn build_vault_transit_kms_config(cfg: &config::Config) -> std::io::Result<rustfs_kms::config::KmsConfig> {
let vault_address = cfg
.kms_vault_address
.as_ref()
.ok_or_else(|| Error::other("Vault address is required for vault-transit backend"))?;
let vault_token = cfg
.kms_vault_token
.as_ref()
.ok_or_else(|| Error::other("Vault token is required for vault-transit backend"))?;
Ok(rustfs_kms::config::KmsConfig {
backend: rustfs_kms::config::KmsBackend::VaultTransit,
backend_config: rustfs_kms::config::BackendConfig::VaultTransit(Box::new(rustfs_kms::config::VaultTransitConfig {
address: vault_address.clone(),
auth_method: rustfs_kms::config::VaultAuthMethod::Token {
token: vault_token.clone(),
},
namespace: None,
mount_path: cfg.kms_vault_mount_path.clone().unwrap_or_else(|| "transit".to_string()),
tls: None,
})),
default_key_id: cfg.kms_default_key_id.clone(),
timeout: std::time::Duration::from_secs(30),
retry_attempts: 3,
enable_cache: true,
cache_config: rustfs_kms::config::CacheConfig::default(),
})
}
/// Configure and start KMS service
async fn configure_and_start_kms(
service_manager: &std::sync::Arc<rustfs_kms::KmsServiceManager>,
@@ -258,7 +288,8 @@ pub(crate) async fn init_kms_system(config: &config::Config) -> std::io::Result<
// Create KMS configuration from command line options
let kms_config = match config.kms_backend.as_str() {
"local" => build_local_kms_config(config)?,
"vault" => build_vault_kms_config(config)?,
"vault" | "vault-kv2" | "vault_kv2" => build_vault_kms_config(config)?,
"vault-transit" | "vault_transit" => build_vault_transit_kms_config(config)?,
_ => return Err(Error::other(format!("Unsupported KMS backend: {}", config.kms_backend))),
};