mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-18 10:43:15 +00:00
refactor(admin): route kms management auth through shared gate (#6194)
This commit is contained in:
@@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
use super::kms_dynamic::current_kms_config_fingerprint;
|
use super::kms_dynamic::current_kms_config_fingerprint;
|
||||||
use super::kms_keys::{CreateKeyHandler, DescribeKeyHandler, GenerateDataKeyHandler, ListKeysHandler};
|
use super::kms_keys::{CreateKeyHandler, DescribeKeyHandler, GenerateDataKeyHandler, ListKeysHandler};
|
||||||
use crate::admin::auth::validate_admin_request;
|
use crate::admin::auth::authorize_admin_request;
|
||||||
use crate::admin::router::{AdminOperation, Operation, S3Router};
|
use crate::admin::router::{AdminOperation, Operation, S3Router};
|
||||||
use crate::admin::runtime_sources::{
|
use crate::admin::runtime_sources::{
|
||||||
current_kms_runtime_service_manager, current_notification_system, current_or_init_kms_runtime_service_manager,
|
current_kms_runtime_service_manager, current_notification_system, current_or_init_kms_runtime_service_manager,
|
||||||
};
|
};
|
||||||
use crate::auth::{check_key_valid, get_session_token};
|
use crate::server::ADMIN_PREFIX;
|
||||||
use crate::server::{ADMIN_PREFIX, RemoteAddr};
|
|
||||||
use hyper::{HeaderMap, Method, StatusCode};
|
use hyper::{HeaderMap, Method, StatusCode};
|
||||||
use matchit::Params;
|
use matchit::Params;
|
||||||
use rustfs_kms::KmsBackend;
|
use rustfs_kms::KmsBackend;
|
||||||
@@ -69,6 +68,18 @@ fn kms_clear_cache_actions() -> Vec<Action> {
|
|||||||
vec![Action::KmsAction(KmsAction::ClearCacheAction)]
|
vec![Action::KmsAction(KmsAction::ClearCacheAction)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Admin gate for the KMS management endpoints, none of which act on a key.
|
||||||
|
///
|
||||||
|
/// The pre-check keeps these endpoints' historical missing-credentials message;
|
||||||
|
/// the shared gate reports "get cred failed".
|
||||||
|
async fn authorize_kms_management_request(req: &S3Request<Body>, actions: Vec<Action>) -> S3Result<()> {
|
||||||
|
if req.credentials.is_none() {
|
||||||
|
return Err(s3_error!(InvalidRequest, "authentication required"));
|
||||||
|
}
|
||||||
|
authorize_admin_request(req, actions).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Response of `POST /kms/clear-cache`.
|
/// Response of `POST /kms/clear-cache`.
|
||||||
///
|
///
|
||||||
/// Declared rather than built inline so the shape the console already depends
|
/// Declared rather than built inline so the shape the console already depends
|
||||||
@@ -260,22 +271,7 @@ pub struct KmsStatusHandler {}
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Operation for KmsStatusHandler {
|
impl Operation for KmsStatusHandler {
|
||||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
let Some(cred) = req.credentials else {
|
authorize_kms_management_request(&req, kms_service_control_actions()).await?;
|
||||||
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,
|
|
||||||
kms_service_control_actions(),
|
|
||||||
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(service) = kms_encryption_service_from_context().await else {
|
let Some(service) = kms_encryption_service_from_context().await else {
|
||||||
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
||||||
@@ -326,22 +322,7 @@ pub struct KmsConfigHandler {}
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Operation for KmsConfigHandler {
|
impl Operation for KmsConfigHandler {
|
||||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
let Some(cred) = req.credentials else {
|
authorize_kms_management_request(&req, kms_configure_actions()).await?;
|
||||||
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,
|
|
||||||
kms_configure_actions(),
|
|
||||||
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(service) = kms_encryption_service_from_context().await else {
|
let Some(service) = kms_encryption_service_from_context().await else {
|
||||||
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
||||||
@@ -375,22 +356,7 @@ pub struct KmsClearCacheHandler {}
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Operation for KmsClearCacheHandler {
|
impl Operation for KmsClearCacheHandler {
|
||||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||||
let Some(cred) = req.credentials else {
|
authorize_kms_management_request(&req, kms_clear_cache_actions()).await?;
|
||||||
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,
|
|
||||||
kms_clear_cache_actions(),
|
|
||||||
req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(service) = kms_encryption_service_from_context().await else {
|
let Some(service) = kms_encryption_service_from_context().await else {
|
||||||
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
return Err(s3_error!(InternalError, "KMS service not initialized"));
|
||||||
@@ -422,9 +388,14 @@ impl Operation for KmsClearCacheHandler {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{KmsClearCacheResponse, kms_clear_cache_actions, kms_configure_actions, kms_service_control_actions};
|
use super::{
|
||||||
|
KmsClearCacheResponse, authorize_kms_management_request, kms_clear_cache_actions, kms_configure_actions,
|
||||||
|
kms_service_control_actions,
|
||||||
|
};
|
||||||
use crate::admin::handlers::kms_keys::stable_json_value;
|
use crate::admin::handlers::kms_keys::stable_json_value;
|
||||||
|
use hyper::HeaderMap;
|
||||||
use rustfs_policy::policy::action::{Action, AdminAction, KmsAction};
|
use rustfs_policy::policy::action::{Action, AdminAction, KmsAction};
|
||||||
|
use s3s::{Body, S3Request};
|
||||||
|
|
||||||
fn assert_has_action(actions: &[Action], action: Action) {
|
fn assert_has_action(actions: &[Action], action: Action) {
|
||||||
assert!(actions.contains(&action), "expected action list to contain {action:?}");
|
assert!(actions.contains(&action), "expected action list to contain {action:?}");
|
||||||
@@ -434,6 +405,58 @@ mod tests {
|
|||||||
assert!(!actions.contains(&action), "expected action list not to contain {action:?}");
|
assert!(!actions.contains(&action), "expected action list not to contain {action:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// These endpoints authorize through the shared admin gate, which reports
|
||||||
|
/// "get cred failed" for a credential-less request. The pre-check keeps the
|
||||||
|
/// message these endpoints have always returned (rustfs/backlog#1829).
|
||||||
|
#[tokio::test]
|
||||||
|
async fn kms_management_gate_keeps_its_missing_credentials_message() {
|
||||||
|
let req = S3Request {
|
||||||
|
input: Body::from(String::new()),
|
||||||
|
method: http::Method::GET,
|
||||||
|
uri: "/rustfs/admin/v3/kms/status".parse().expect("uri should parse"),
|
||||||
|
headers: HeaderMap::new(),
|
||||||
|
extensions: http::Extensions::new(),
|
||||||
|
credentials: None,
|
||||||
|
region: None,
|
||||||
|
service: None,
|
||||||
|
trailing_headers: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let err = authorize_kms_management_request(&req, kms_service_control_actions())
|
||||||
|
.await
|
||||||
|
.expect_err("a request without credentials must be rejected");
|
||||||
|
assert_eq!(err.code(), &s3s::S3ErrorCode::InvalidRequest);
|
||||||
|
assert_eq!(err.message(), Some("authentication required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every management endpoint must reach the shared gate, each with its own
|
||||||
|
/// action set. The action lists are pinned above, but nothing else checks
|
||||||
|
/// which handler asks for which, and a handler that lost its gate entirely
|
||||||
|
/// would still serve its response.
|
||||||
|
#[test]
|
||||||
|
fn management_handlers_authorize_with_their_dedicated_actions() {
|
||||||
|
let src = include_str!("kms_management.rs");
|
||||||
|
|
||||||
|
for (handler, actions) in [
|
||||||
|
("KmsStatusHandler", "kms_service_control_actions()"),
|
||||||
|
("KmsConfigHandler", "kms_configure_actions()"),
|
||||||
|
("KmsClearCacheHandler", "kms_clear_cache_actions()"),
|
||||||
|
] {
|
||||||
|
let block = src
|
||||||
|
.split_once(&format!("impl Operation for {handler}"))
|
||||||
|
.unwrap_or_else(|| panic!("{handler} impl should exist"))
|
||||||
|
.1;
|
||||||
|
let end = block
|
||||||
|
.find("\nimpl Operation for")
|
||||||
|
.or_else(|| block.find("\n#[cfg(test)]"))
|
||||||
|
.unwrap_or(block.len());
|
||||||
|
assert!(
|
||||||
|
block[..end].contains(&format!("authorize_kms_management_request(&req, {actions})")),
|
||||||
|
"{handler} must authorize through the shared gate with {actions}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn kms_management_auth_actions_use_dedicated_kms_actions() {
|
fn kms_management_auth_actions_use_dedicated_kms_actions() {
|
||||||
assert_has_action(&kms_service_control_actions(), Action::KmsAction(KmsAction::ServiceControlAction));
|
assert_has_action(&kms_service_control_actions(), Action::KmsAction(KmsAction::ServiceControlAction));
|
||||||
|
|||||||
Reference in New Issue
Block a user