mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-29 00:17:11 +00:00
[codex] Refactor P1-07: slim KMS handler ownership (#1935)
This commit is contained in:
@@ -19,58 +19,16 @@ use crate::admin::auth::validate_admin_request;
|
|||||||
use crate::admin::router::{AdminOperation, Operation, S3Router};
|
use crate::admin::router::{AdminOperation, Operation, S3Router};
|
||||||
use crate::auth::{check_key_valid, get_session_token};
|
use crate::auth::{check_key_valid, get_session_token};
|
||||||
use crate::server::RemoteAddr;
|
use crate::server::RemoteAddr;
|
||||||
use base64::Engine;
|
|
||||||
use hyper::{HeaderMap, StatusCode};
|
use hyper::{HeaderMap, StatusCode};
|
||||||
use matchit::Params;
|
use matchit::Params;
|
||||||
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
|
use rustfs_kms::get_global_encryption_service;
|
||||||
use rustfs_kms::{get_global_encryption_service, types::*};
|
|
||||||
use rustfs_policy::policy::action::{Action, AdminAction};
|
use rustfs_policy::policy::action::{Action, AdminAction};
|
||||||
use s3s::header::CONTENT_TYPE;
|
use s3s::header::CONTENT_TYPE;
|
||||||
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
|
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::collections::HashMap;
|
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct CreateKeyApiRequest {
|
|
||||||
pub key_usage: Option<KeyUsage>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub tags: Option<HashMap<String, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct CreateKeyApiResponse {
|
|
||||||
pub key_id: String,
|
|
||||||
pub key_metadata: KeyMetadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct DescribeKeyApiResponse {
|
|
||||||
pub key_metadata: KeyMetadata,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ListKeysApiResponse {
|
|
||||||
pub keys: Vec<KeyInfo>,
|
|
||||||
pub truncated: bool,
|
|
||||||
pub next_marker: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GenerateDataKeyApiRequest {
|
|
||||||
pub key_id: String,
|
|
||||||
pub key_spec: KeySpec,
|
|
||||||
pub encryption_context: Option<HashMap<String, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct GenerateDataKeyApiResponse {
|
|
||||||
pub key_id: String,
|
|
||||||
pub plaintext_key: String, // Base64 encoded
|
|
||||||
pub ciphertext_blob: String, // Base64 encoded
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct KmsStatusResponse {
|
pub struct KmsStatusResponse {
|
||||||
pub backend_type: String,
|
pub backend_type: String,
|
||||||
@@ -95,292 +53,13 @@ pub struct KmsConfigResponse {
|
|||||||
pub default_key_id: Option<String>,
|
pub default_key_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_query_params(uri: &hyper::Uri) -> HashMap<String, String> {
|
|
||||||
let mut params = HashMap::new();
|
|
||||||
if let Some(query) = uri.query() {
|
|
||||||
query.split('&').for_each(|pair| {
|
|
||||||
if let Some((key, value)) = pair.split_once('=') {
|
|
||||||
params.insert(
|
|
||||||
urlencoding::decode(key).unwrap_or_default().into_owned(),
|
|
||||||
urlencoding::decode(value).unwrap_or_default().into_owned(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
params
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_kms_route(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
pub fn register_kms_route(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
||||||
kms_management::register_kms_management_route(r)?;
|
kms_management::register_kms_management_route(r)?;
|
||||||
kms_dynamic::register_kms_dynamic_route(r)?;
|
kms_dynamic::register_kms_dynamic_route(r)?;
|
||||||
kms_keys::register_kms_key_route(r)?;
|
kms_keys::register_kms_key_route(r)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new KMS master key
|
|
||||||
pub struct CreateKeyHandler {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl Operation for CreateKeyHandler {
|
|
||||||
async fn call(&self, mut req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
|
||||||
let Some(cred) = req.credentials else {
|
|
||||||
return Err(s3_error!(InvalidRequest, "authentication required"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let (cred, owner) =
|
|
||||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
|
||||||
|
|
||||||
validate_admin_request(
|
|
||||||
&req.headers,
|
|
||||||
&cred,
|
|
||||||
owner,
|
|
||||||
false,
|
|
||||||
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], // TODO: Add specific KMS action
|
|
||||||
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let body = req
|
|
||||||
.input
|
|
||||||
.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE)
|
|
||||||
.await
|
|
||||||
.map_err(|e| s3_error!(InvalidRequest, "failed to read request body: {}", e))?;
|
|
||||||
|
|
||||||
let request: CreateKeyApiRequest = if body.is_empty() {
|
|
||||||
CreateKeyApiRequest {
|
|
||||||
key_usage: Some(KeyUsage::EncryptDecrypt),
|
|
||||||
description: None,
|
|
||||||
tags: None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
serde_json::from_slice(&body).map_err(|e| s3_error!(InvalidRequest, "invalid JSON: {}", e))?
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(service) = get_global_encryption_service().await else {
|
|
||||||
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract key name from tags if provided
|
|
||||||
let tags = request.tags.unwrap_or_default();
|
|
||||||
let key_name = tags.get("name").cloned();
|
|
||||||
|
|
||||||
let kms_request = CreateKeyRequest {
|
|
||||||
key_name,
|
|
||||||
key_usage: request.key_usage.unwrap_or(KeyUsage::EncryptDecrypt),
|
|
||||||
description: request.description,
|
|
||||||
tags,
|
|
||||||
origin: Some("AWS_KMS".to_string()),
|
|
||||||
policy: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
match service.create_key(kms_request).await {
|
|
||||||
Ok(response) => {
|
|
||||||
let api_response = CreateKeyApiResponse {
|
|
||||||
key_id: response.key_id,
|
|
||||||
key_metadata: response.key_metadata,
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = serde_json::to_vec(&api_response)
|
|
||||||
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
|
||||||
|
|
||||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to create KMS key: {}", e);
|
|
||||||
Err(s3_error!(InternalError, "failed to create key: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describe a KMS key
|
|
||||||
pub struct DescribeKeyHandler {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl Operation for DescribeKeyHandler {
|
|
||||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
|
||||||
let Some(cred) = req.credentials else {
|
|
||||||
return Err(s3_error!(InvalidRequest, "authentication required"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let (cred, owner) =
|
|
||||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
|
||||||
|
|
||||||
validate_admin_request(
|
|
||||||
&req.headers,
|
|
||||||
&cred,
|
|
||||||
owner,
|
|
||||||
false,
|
|
||||||
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
|
|
||||||
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let query_params = extract_query_params(&req.uri);
|
|
||||||
let Some(key_id) = query_params.get("keyId") else {
|
|
||||||
return Err(s3_error!(InvalidRequest, "missing keyId parameter"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(service) = get_global_encryption_service().await else {
|
|
||||||
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = DescribeKeyRequest { key_id: key_id.clone() };
|
|
||||||
|
|
||||||
match service.describe_key(request).await {
|
|
||||||
Ok(response) => {
|
|
||||||
let api_response = DescribeKeyApiResponse {
|
|
||||||
key_metadata: response.key_metadata,
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = serde_json::to_vec(&api_response)
|
|
||||||
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
|
||||||
|
|
||||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to describe KMS key {}: {}", key_id, e);
|
|
||||||
Err(s3_error!(InternalError, "failed to describe key: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List KMS keys
|
|
||||||
pub struct ListKeysHandler {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl Operation for ListKeysHandler {
|
|
||||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
|
||||||
let Some(cred) = req.credentials else {
|
|
||||||
return Err(s3_error!(InvalidRequest, "authentication required"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let (cred, owner) =
|
|
||||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
|
||||||
|
|
||||||
validate_admin_request(
|
|
||||||
&req.headers,
|
|
||||||
&cred,
|
|
||||||
owner,
|
|
||||||
false,
|
|
||||||
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
|
|
||||||
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let query_params = extract_query_params(&req.uri);
|
|
||||||
let limit = query_params.get("limit").and_then(|s| s.parse::<u32>().ok()).unwrap_or(100);
|
|
||||||
let marker = query_params.get("marker").cloned();
|
|
||||||
|
|
||||||
let Some(service) = get_global_encryption_service().await else {
|
|
||||||
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let request = ListKeysRequest {
|
|
||||||
limit: Some(limit),
|
|
||||||
marker,
|
|
||||||
status_filter: None,
|
|
||||||
usage_filter: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
match service.list_keys(request).await {
|
|
||||||
Ok(response) => {
|
|
||||||
let api_response = ListKeysApiResponse {
|
|
||||||
keys: response.keys,
|
|
||||||
truncated: response.truncated,
|
|
||||||
next_marker: response.next_marker,
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = serde_json::to_vec(&api_response)
|
|
||||||
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
|
||||||
|
|
||||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to list KMS keys: {}", e);
|
|
||||||
Err(s3_error!(InternalError, "failed to list keys: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate data encryption key
|
|
||||||
pub struct GenerateDataKeyHandler {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl Operation for GenerateDataKeyHandler {
|
|
||||||
async fn call(&self, mut req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
|
||||||
let Some(cred) = req.credentials else {
|
|
||||||
return Err(s3_error!(InvalidRequest, "authentication required"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let (cred, owner) =
|
|
||||||
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
|
||||||
|
|
||||||
validate_admin_request(
|
|
||||||
&req.headers,
|
|
||||||
&cred,
|
|
||||||
owner,
|
|
||||||
false,
|
|
||||||
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
|
|
||||||
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let body = req
|
|
||||||
.input
|
|
||||||
.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE)
|
|
||||||
.await
|
|
||||||
.map_err(|e| s3_error!(InvalidRequest, "failed to read request body: {}", e))?;
|
|
||||||
|
|
||||||
let request: GenerateDataKeyApiRequest =
|
|
||||||
serde_json::from_slice(&body).map_err(|e| s3_error!(InvalidRequest, "invalid JSON: {}", e))?;
|
|
||||||
|
|
||||||
let Some(service) = get_global_encryption_service().await else {
|
|
||||||
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let kms_request = GenerateDataKeyRequest {
|
|
||||||
key_id: request.key_id,
|
|
||||||
key_spec: request.key_spec,
|
|
||||||
encryption_context: request.encryption_context.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match service.generate_data_key(kms_request).await {
|
|
||||||
Ok(response) => {
|
|
||||||
let api_response = GenerateDataKeyApiResponse {
|
|
||||||
key_id: response.key_id,
|
|
||||||
plaintext_key: base64::prelude::BASE64_STANDARD.encode(&response.plaintext_key),
|
|
||||||
ciphertext_blob: base64::prelude::BASE64_STANDARD.encode(&response.ciphertext_blob),
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = serde_json::to_vec(&api_response)
|
|
||||||
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
|
||||||
|
|
||||||
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to generate data key: {}", e);
|
|
||||||
Err(s3_error!(InternalError, "failed to generate data key: {}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get KMS service status
|
/// Get KMS service status
|
||||||
pub struct KmsStatusHandler {}
|
pub struct KmsStatusHandler {}
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ use crate::admin::auth::validate_admin_request;
|
|||||||
use crate::admin::router::{AdminOperation, Operation, S3Router};
|
use crate::admin::router::{AdminOperation, Operation, S3Router};
|
||||||
use crate::auth::{check_key_valid, get_session_token};
|
use crate::auth::{check_key_valid, get_session_token};
|
||||||
use crate::server::{ADMIN_PREFIX, RemoteAddr};
|
use crate::server::{ADMIN_PREFIX, RemoteAddr};
|
||||||
|
use base64::Engine;
|
||||||
use hyper::{HeaderMap, Method, StatusCode};
|
use hyper::{HeaderMap, Method, StatusCode};
|
||||||
use matchit::Params;
|
use matchit::Params;
|
||||||
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
|
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
|
||||||
use rustfs_kms::{KmsError, get_global_kms_service_manager, types::*};
|
use rustfs_kms::{KmsError, get_global_encryption_service, get_global_kms_service_manager, types::*};
|
||||||
use rustfs_policy::policy::action::{Action, AdminAction};
|
use rustfs_policy::policy::action::{Action, AdminAction};
|
||||||
use s3s::header::CONTENT_TYPE;
|
use s3s::header::CONTENT_TYPE;
|
||||||
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
|
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
|
||||||
@@ -46,6 +47,45 @@ pub struct CreateKmsKeyResponse {
|
|||||||
pub key_metadata: Option<KeyMetadata>,
|
pub key_metadata: Option<KeyMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CreateKeyApiRequest {
|
||||||
|
pub key_usage: Option<KeyUsage>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub tags: Option<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CreateKeyApiResponse {
|
||||||
|
pub key_id: String,
|
||||||
|
pub key_metadata: KeyMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct DescribeKeyApiResponse {
|
||||||
|
pub key_metadata: KeyMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ListKeysApiResponse {
|
||||||
|
pub keys: Vec<KeyInfo>,
|
||||||
|
pub truncated: bool,
|
||||||
|
pub next_marker: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct GenerateDataKeyApiRequest {
|
||||||
|
pub key_id: String,
|
||||||
|
pub key_spec: KeySpec,
|
||||||
|
pub encryption_context: Option<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct GenerateDataKeyApiResponse {
|
||||||
|
pub key_id: String,
|
||||||
|
pub plaintext_key: String, // Base64 encoded
|
||||||
|
pub ciphertext_blob: String, // Base64 encoded
|
||||||
|
}
|
||||||
|
|
||||||
fn extract_query_params(uri: &hyper::Uri) -> HashMap<String, String> {
|
fn extract_query_params(uri: &hyper::Uri) -> HashMap<String, String> {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
if let Some(query) = uri.query() {
|
if let Some(query) = uri.query() {
|
||||||
@@ -95,6 +135,269 @@ pub fn register_kms_key_route(r: &mut S3Router<AdminOperation>) -> std::io::Resu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new KMS master key (legacy endpoint)
|
||||||
|
pub struct CreateKeyHandler {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for CreateKeyHandler {
|
||||||
|
async fn call(&self, mut req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
let Some(cred) = req.credentials else {
|
||||||
|
return Err(s3_error!(InvalidRequest, "authentication required"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cred, owner) =
|
||||||
|
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
||||||
|
|
||||||
|
validate_admin_request(
|
||||||
|
&req.headers,
|
||||||
|
&cred,
|
||||||
|
owner,
|
||||||
|
false,
|
||||||
|
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)], // TODO: Add specific KMS action
|
||||||
|
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let body = req
|
||||||
|
.input
|
||||||
|
.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE)
|
||||||
|
.await
|
||||||
|
.map_err(|e| s3_error!(InvalidRequest, "failed to read request body: {}", e))?;
|
||||||
|
|
||||||
|
let request: CreateKeyApiRequest = if body.is_empty() {
|
||||||
|
CreateKeyApiRequest {
|
||||||
|
key_usage: Some(KeyUsage::EncryptDecrypt),
|
||||||
|
description: None,
|
||||||
|
tags: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serde_json::from_slice(&body).map_err(|e| s3_error!(InvalidRequest, "invalid JSON: {}", e))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(service) = get_global_encryption_service().await else {
|
||||||
|
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract key name from tags if provided
|
||||||
|
let tags = request.tags.unwrap_or_default();
|
||||||
|
let key_name = tags.get("name").cloned();
|
||||||
|
|
||||||
|
let kms_request = CreateKeyRequest {
|
||||||
|
key_name,
|
||||||
|
key_usage: request.key_usage.unwrap_or(KeyUsage::EncryptDecrypt),
|
||||||
|
description: request.description,
|
||||||
|
tags,
|
||||||
|
origin: Some("AWS_KMS".to_string()),
|
||||||
|
policy: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match service.create_key(kms_request).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let api_response = CreateKeyApiResponse {
|
||||||
|
key_id: response.key_id,
|
||||||
|
key_metadata: response.key_metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = serde_json::to_vec(&api_response)
|
||||||
|
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||||
|
|
||||||
|
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to create KMS key: {}", e);
|
||||||
|
Err(s3_error!(InternalError, "failed to create key: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describe a KMS key (legacy endpoint)
|
||||||
|
pub struct DescribeKeyHandler {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for DescribeKeyHandler {
|
||||||
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
let Some(cred) = req.credentials else {
|
||||||
|
return Err(s3_error!(InvalidRequest, "authentication required"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cred, owner) =
|
||||||
|
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
||||||
|
|
||||||
|
validate_admin_request(
|
||||||
|
&req.headers,
|
||||||
|
&cred,
|
||||||
|
owner,
|
||||||
|
false,
|
||||||
|
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
|
||||||
|
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let query_params = extract_query_params(&req.uri);
|
||||||
|
let Some(key_id) = query_params.get("keyId") else {
|
||||||
|
return Err(s3_error!(InvalidRequest, "missing keyId parameter"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(service) = get_global_encryption_service().await else {
|
||||||
|
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = DescribeKeyRequest { key_id: key_id.clone() };
|
||||||
|
|
||||||
|
match service.describe_key(request).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let api_response = DescribeKeyApiResponse {
|
||||||
|
key_metadata: response.key_metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = serde_json::to_vec(&api_response)
|
||||||
|
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||||
|
|
||||||
|
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to describe KMS key {}: {}", key_id, e);
|
||||||
|
Err(s3_error!(InternalError, "failed to describe key: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List KMS keys (legacy endpoint)
|
||||||
|
pub struct ListKeysHandler {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for ListKeysHandler {
|
||||||
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
let Some(cred) = req.credentials else {
|
||||||
|
return Err(s3_error!(InvalidRequest, "authentication required"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cred, owner) =
|
||||||
|
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
||||||
|
|
||||||
|
validate_admin_request(
|
||||||
|
&req.headers,
|
||||||
|
&cred,
|
||||||
|
owner,
|
||||||
|
false,
|
||||||
|
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
|
||||||
|
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let query_params = extract_query_params(&req.uri);
|
||||||
|
let limit = query_params.get("limit").and_then(|s| s.parse::<u32>().ok()).unwrap_or(100);
|
||||||
|
let marker = query_params.get("marker").cloned();
|
||||||
|
|
||||||
|
let Some(service) = get_global_encryption_service().await else {
|
||||||
|
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = ListKeysRequest {
|
||||||
|
limit: Some(limit),
|
||||||
|
marker,
|
||||||
|
status_filter: None,
|
||||||
|
usage_filter: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match service.list_keys(request).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let api_response = ListKeysApiResponse {
|
||||||
|
keys: response.keys,
|
||||||
|
truncated: response.truncated,
|
||||||
|
next_marker: response.next_marker,
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = serde_json::to_vec(&api_response)
|
||||||
|
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||||
|
|
||||||
|
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to list KMS keys: {}", e);
|
||||||
|
Err(s3_error!(InternalError, "failed to list keys: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate data encryption key (legacy endpoint)
|
||||||
|
pub struct GenerateDataKeyHandler {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Operation for GenerateDataKeyHandler {
|
||||||
|
async fn call(&self, mut req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
|
let Some(cred) = req.credentials else {
|
||||||
|
return Err(s3_error!(InvalidRequest, "authentication required"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cred, owner) =
|
||||||
|
check_key_valid(get_session_token(&req.uri, &req.headers).unwrap_or_default(), &cred.access_key).await?;
|
||||||
|
|
||||||
|
validate_admin_request(
|
||||||
|
&req.headers,
|
||||||
|
&cred,
|
||||||
|
owner,
|
||||||
|
false,
|
||||||
|
vec![Action::AdminAction(AdminAction::ServerInfoAdminAction)],
|
||||||
|
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let body = req
|
||||||
|
.input
|
||||||
|
.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE)
|
||||||
|
.await
|
||||||
|
.map_err(|e| s3_error!(InvalidRequest, "failed to read request body: {}", e))?;
|
||||||
|
|
||||||
|
let request: GenerateDataKeyApiRequest =
|
||||||
|
serde_json::from_slice(&body).map_err(|e| s3_error!(InvalidRequest, "invalid JSON: {}", e))?;
|
||||||
|
|
||||||
|
let Some(service) = get_global_encryption_service().await else {
|
||||||
|
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let kms_request = GenerateDataKeyRequest {
|
||||||
|
key_id: request.key_id,
|
||||||
|
key_spec: request.key_spec,
|
||||||
|
encryption_context: request.encryption_context.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match service.generate_data_key(kms_request).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let api_response = GenerateDataKeyApiResponse {
|
||||||
|
key_id: response.key_id,
|
||||||
|
plaintext_key: base64::prelude::BASE64_STANDARD.encode(&response.plaintext_key),
|
||||||
|
ciphertext_blob: base64::prelude::BASE64_STANDARD.encode(&response.ciphertext_blob),
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = serde_json::to_vec(&api_response)
|
||||||
|
.map_err(|e| s3_error!(InternalError, "failed to serialize response: {}", e))?;
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
|
||||||
|
|
||||||
|
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), headers))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to generate data key: {}", e);
|
||||||
|
Err(s3_error!(InternalError, "failed to generate data key: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new KMS key
|
/// Create a new KMS key
|
||||||
pub struct CreateKmsKeyHandler;
|
pub struct CreateKmsKeyHandler;
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,8 @@
|
|||||||
|
|
||||||
//! KMS management route registration.
|
//! KMS management route registration.
|
||||||
|
|
||||||
use super::kms::{
|
use super::kms::{KmsClearCacheHandler, KmsConfigHandler, KmsStatusHandler};
|
||||||
CreateKeyHandler, DescribeKeyHandler, GenerateDataKeyHandler, KmsClearCacheHandler, KmsConfigHandler, KmsStatusHandler,
|
use super::kms_keys::{CreateKeyHandler, DescribeKeyHandler, GenerateDataKeyHandler, ListKeysHandler};
|
||||||
ListKeysHandler,
|
|
||||||
};
|
|
||||||
use crate::admin::router::{AdminOperation, S3Router};
|
use crate::admin::router::{AdminOperation, S3Router};
|
||||||
use crate::server::ADMIN_PREFIX;
|
use crate::server::ADMIN_PREFIX;
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
|
|||||||
Reference in New Issue
Block a user