mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 16:07:05 +00:00
fix(runtime): remove high-impact unwrap paths (#3755)
* fix(runtime): remove high-impact unwrap paths * fix(runtime): propagate managed SSE metadata errors * fix(runtime): add typed OPA config errors * fix(runtime): harden config and credential helpers * fix(runtime): remove SSE hmac unwraps * fix(runtime): complete SSE helper error propagation * fix(trusted-proxies): avoid legacy global init panics * test(credentials): allow deprecated rpc token check * fix(storage): harden object lock retention parsing * chore(checks): refresh layer dependency baseline * chore(checks): refresh layer dependency baseline * Update layer-dependency-baseline.txt Signed-off-by: houseme <housemecn@gmail.com> * test(context): avoid clone on copy boot time --------- Signed-off-by: houseme <housemecn@gmail.com> Co-authored-by: Zhengchao An <anzhengchao@gmail.com> Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -243,7 +243,9 @@ fn derive_rpc_secret(access_key: &str, secret_key: &str) -> Option<String> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mac = <HmacSha256 as KeyInit>::new_from_slice(secret_key.as_bytes()).expect("HMAC can take key of any size");
|
let Ok(mut mac) = <HmacSha256 as KeyInit>::new_from_slice(secret_key.as_bytes()) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
mac.update(RPC_SECRET_DERIVATION_CONTEXT);
|
mac.update(RPC_SECRET_DERIVATION_CONTEXT);
|
||||||
mac.update(&[0]);
|
mac.update(&[0]);
|
||||||
mac.update(access_key.as_bytes());
|
mac.update(access_key.as_bytes());
|
||||||
@@ -284,7 +286,7 @@ pub fn try_get_rpc_token() -> std::io::Result<String> {
|
|||||||
|
|
||||||
#[deprecated(note = "use try_get_rpc_token to handle missing RPC secrets explicitly")]
|
#[deprecated(note = "use try_get_rpc_token to handle missing RPC secrets explicitly")]
|
||||||
pub fn get_rpc_token() -> String {
|
pub fn get_rpc_token() -> String {
|
||||||
try_get_rpc_token().expect(RPC_SECRET_REQUIRED_MESSAGE)
|
try_get_rpc_token().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper struct for masking sensitive strings in Debug implementations.
|
/// A wrapper struct for masking sensitive strings in Debug implementations.
|
||||||
@@ -596,6 +598,15 @@ mod tests {
|
|||||||
assert_string_return(get_rpc_token);
|
assert_string_return(get_rpc_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
#[test]
|
||||||
|
fn test_get_rpc_token_matches_fallible_api_contract() {
|
||||||
|
match try_get_rpc_token() {
|
||||||
|
Ok(secret) => assert_eq!(get_rpc_token(), secret),
|
||||||
|
Err(_) => assert_eq!(get_rpc_token(), ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_rpc_secret_accepts_non_default_secret() {
|
fn test_resolve_rpc_secret_accepts_non_default_secret() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -36,7 +36,25 @@ pub struct AuthZPlugin {
|
|||||||
args: Args,
|
args: Args,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check() -> Result<(), String> {
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum OpaConfigError {
|
||||||
|
#[error("Missing required env var: {0}")]
|
||||||
|
MissingRequiredEnv(&'static str),
|
||||||
|
#[error("Invalid env vars: {0:?}")]
|
||||||
|
InvalidEnvVars(HashMap<String, String>),
|
||||||
|
#[error("Error getting env var {name}: {source:?}")]
|
||||||
|
EnvRead {
|
||||||
|
name: &'static str,
|
||||||
|
#[source]
|
||||||
|
source: env::VarError,
|
||||||
|
},
|
||||||
|
#[error("OPA returned an error: {0}")]
|
||||||
|
InvalidStatus(reqwest::StatusCode),
|
||||||
|
#[error("Error connecting to OPA: {0}")]
|
||||||
|
Connection(reqwest::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check() -> Result<(), OpaConfigError> {
|
||||||
let env_list = env::vars();
|
let env_list = env::vars();
|
||||||
let mut candidate = HashMap::new();
|
let mut candidate = HashMap::new();
|
||||||
let prefix = format!("{ENV_PREFIX}{POLICY_PLUGIN_SUB_SYS}").to_uppercase();
|
let prefix = format!("{ENV_PREFIX}{POLICY_PLUGIN_SUB_SYS}").to_uppercase();
|
||||||
@@ -48,17 +66,17 @@ fn check() -> Result<(), String> {
|
|||||||
|
|
||||||
//check required env vars
|
//check required env vars
|
||||||
if candidate.remove(ENV_POLICY_PLUGIN_OPA_URL).is_none() {
|
if candidate.remove(ENV_POLICY_PLUGIN_OPA_URL).is_none() {
|
||||||
return Err(format!("Missing required env var: {ENV_POLICY_PLUGIN_OPA_URL}"));
|
return Err(OpaConfigError::MissingRequiredEnv(ENV_POLICY_PLUGIN_OPA_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check optional env vars
|
// check optional env vars
|
||||||
candidate.remove(ENV_POLICY_PLUGIN_AUTH_TOKEN);
|
candidate.remove(ENV_POLICY_PLUGIN_AUTH_TOKEN);
|
||||||
if !candidate.is_empty() {
|
if !candidate.is_empty() {
|
||||||
return Err(format!("Invalid env vars: {candidate:?}"));
|
return Err(OpaConfigError::InvalidEnvVars(candidate));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn validate(config: &Args) -> Result<(), String> {
|
async fn validate(config: &Args) -> Result<(), OpaConfigError> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
match client.post(&config.url).send().await {
|
match client.post(&config.url).send().await {
|
||||||
@@ -68,31 +86,34 @@ async fn validate(config: &Args) -> Result<(), String> {
|
|||||||
info!("OPA is ready to accept requests.");
|
info!("OPA is ready to accept requests.");
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("OPA returned an error: {}", resp.status()));
|
return Err(OpaConfigError::InvalidStatus(resp.status()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(format!("Error connecting to OPA: {err}"));
|
return Err(OpaConfigError::Connection(err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn lookup_config() -> Result<Args, String> {
|
pub async fn lookup_config() -> Result<Args, OpaConfigError> {
|
||||||
let args = Args::default();
|
let args = Args::default();
|
||||||
|
|
||||||
let get_cfg =
|
let get_cfg = |cfg: &'static str| -> Result<String, OpaConfigError> {
|
||||||
|cfg: &str| -> Result<String, String> { env::var(cfg).map_err(|e| format!("Error getting env var {cfg}: {e:?}")) };
|
env::var(cfg).map_err(|source| OpaConfigError::EnvRead { name: cfg, source })
|
||||||
|
};
|
||||||
|
|
||||||
let url = get_cfg(ENV_POLICY_PLUGIN_OPA_URL);
|
let url = match get_cfg(ENV_POLICY_PLUGIN_OPA_URL) {
|
||||||
if url.is_err() {
|
Ok(url) => url,
|
||||||
info!("OPA is not enabled.");
|
Err(_) => {
|
||||||
return Ok(args);
|
info!("OPA is not enabled.");
|
||||||
}
|
return Ok(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
check()?;
|
check()?;
|
||||||
let args = Args {
|
let args = Args {
|
||||||
url: url.ok().unwrap(),
|
url,
|
||||||
auth_token: get_cfg(ENV_POLICY_PLUGIN_AUTH_TOKEN).unwrap_or_default(),
|
auth_token: get_cfg(ENV_POLICY_PLUGIN_AUTH_TOKEN).unwrap_or_default(),
|
||||||
};
|
};
|
||||||
validate(&args).await?;
|
validate(&args).await?;
|
||||||
@@ -111,7 +132,10 @@ impl AuthZPlugin {
|
|||||||
.http2_keep_alive_interval(Some(Duration::from_secs(30)))
|
.http2_keep_alive_interval(Some(Duration::from_secs(30)))
|
||||||
.http2_keep_alive_timeout(Duration::from_secs(15))
|
.http2_keep_alive_timeout(Duration::from_secs(15))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap_or_else(|err| {
|
||||||
|
error!("failed to build OPA HTTP client, falling back to default reqwest client: {err}");
|
||||||
|
reqwest::Client::new()
|
||||||
|
});
|
||||||
|
|
||||||
Self { client, args: config }
|
Self { client, args: config }
|
||||||
}
|
}
|
||||||
@@ -235,7 +259,7 @@ mod tests {
|
|||||||
temp_env::with_var("RUSTFS_POLICY_PLUGIN_AUTH_TOKEN", Some("test-token"), || {
|
temp_env::with_var("RUSTFS_POLICY_PLUGIN_AUTH_TOKEN", Some("test-token"), || {
|
||||||
let result = check();
|
let result = check();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.unwrap_err().contains("Missing required env var"));
|
assert!(matches!(result.unwrap_err(), OpaConfigError::MissingRequiredEnv(_)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -250,7 +274,7 @@ mod tests {
|
|||||||
|| {
|
|| {
|
||||||
let result = check();
|
let result = check();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.unwrap_err().contains("Invalid env vars"));
|
assert!(matches!(result.unwrap_err(), OpaConfigError::InvalidEnvVars(_)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -270,6 +294,21 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lookup_config_uses_env_url_without_unwrap_path() {
|
||||||
|
temp_env::with_vars(
|
||||||
|
[
|
||||||
|
("RUSTFS_POLICY_PLUGIN_URL", Some("http://localhost:8181")),
|
||||||
|
("RUSTFS_POLICY_PLUGIN_AUTH_TOKEN", Some("token")),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
let result = rt.block_on(async { lookup_config().await });
|
||||||
|
assert!(result.is_err(), "lookup should fail validation without panicking when OPA is unreachable");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_args_enable() {
|
fn test_args_enable() {
|
||||||
// Test Args enable method
|
// Test Args enable method
|
||||||
|
|||||||
@@ -31,9 +31,30 @@ static METRICS: OnceLock<Option<ProxyMetrics>> = OnceLock::new();
|
|||||||
/// Global instance of the trusted proxy layer.
|
/// Global instance of the trusted proxy layer.
|
||||||
static PROXY_LAYER: OnceLock<LegacyTrustedProxyLayer> = OnceLock::new();
|
static PROXY_LAYER: OnceLock<LegacyTrustedProxyLayer> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Disabled fallback layer used when legacy trusted proxies are not enabled.
|
||||||
|
static DISABLED_PROXY_LAYER: OnceLock<LegacyTrustedProxyLayer> = OnceLock::new();
|
||||||
|
|
||||||
/// Global flag indicating if the trusted proxy middleware is enabled.
|
/// Global flag indicating if the trusted proxy middleware is enabled.
|
||||||
static ENABLED: OnceLock<bool> = OnceLock::new();
|
static ENABLED: OnceLock<bool> = OnceLock::new();
|
||||||
|
|
||||||
|
fn load_config() -> &'static Arc<AppConfig> {
|
||||||
|
CONFIG.get_or_init(|| Arc::new(ConfigLoader::from_env_or_default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_metrics(config: &AppConfig, enabled: bool) -> &'static Option<ProxyMetrics> {
|
||||||
|
METRICS.get_or_init(|| {
|
||||||
|
if config.monitoring.metrics_enabled {
|
||||||
|
Some(default_proxy_metrics(enabled))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disabled_layer() -> &'static LegacyTrustedProxyLayer {
|
||||||
|
DISABLED_PROXY_LAYER.get_or_init(LegacyTrustedProxyLayer::disabled)
|
||||||
|
}
|
||||||
|
|
||||||
/// Initializes the global trusted proxy system.
|
/// Initializes the global trusted proxy system.
|
||||||
///
|
///
|
||||||
/// This function should be called once at the start of the application.
|
/// This function should be called once at the start of the application.
|
||||||
@@ -54,24 +75,11 @@ pub fn init() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = CONFIG.get_or_init(|| Arc::new(ConfigLoader::from_env_or_default())).clone();
|
let config = load_config().clone();
|
||||||
|
let metrics = load_metrics(&config, enabled).clone();
|
||||||
|
|
||||||
METRICS.get_or_init(|| {
|
PROXY_LAYER
|
||||||
if config.monitoring.metrics_enabled {
|
.get_or_init(|| LegacyTrustedProxyLayer::with_cache_config(config.proxy.clone(), config.cache.clone(), metrics, enabled));
|
||||||
Some(default_proxy_metrics(enabled))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
PROXY_LAYER.get_or_init(|| {
|
|
||||||
LegacyTrustedProxyLayer::with_cache_config(
|
|
||||||
config.proxy.clone(),
|
|
||||||
config.cache.clone(),
|
|
||||||
METRICS.get().and_then(|m| m.clone()),
|
|
||||||
enabled,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
event = "trusted_proxies.lifecycle",
|
event = "trusted_proxies.lifecycle",
|
||||||
@@ -90,25 +98,27 @@ pub fn init() {
|
|||||||
/// Returns a reference to the global trusted proxy layer.
|
/// Returns a reference to the global trusted proxy layer.
|
||||||
///
|
///
|
||||||
/// This layer can be used to wrap Axum services or other Tower-compatible services.
|
/// This layer can be used to wrap Axum services or other Tower-compatible services.
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if `init()` has not been called.
|
|
||||||
pub fn layer() -> &'static LegacyTrustedProxyLayer {
|
pub fn layer() -> &'static LegacyTrustedProxyLayer {
|
||||||
PROXY_LAYER
|
if let Some(layer) = PROXY_LAYER.get() {
|
||||||
.get()
|
return layer;
|
||||||
.expect("Trusted proxy system not initialized. Call init() first.")
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
if let Some(layer) = PROXY_LAYER.get() {
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled_layer()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the global configuration.
|
/// Returns a reference to the global configuration.
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if `init()` has not been called.
|
|
||||||
pub fn config() -> &'static AppConfig {
|
pub fn config() -> &'static AppConfig {
|
||||||
CONFIG
|
if CONFIG.get().is_none() {
|
||||||
.get()
|
init();
|
||||||
.expect("Trusted proxy system not initialized. Call init() first.")
|
}
|
||||||
|
|
||||||
|
load_config().as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the global metrics collector, if enabled.
|
/// Returns a reference to the global metrics collector, if enabled.
|
||||||
@@ -120,3 +130,23 @@ pub fn metrics() -> Option<&'static ProxyMetrics> {
|
|||||||
pub fn is_enabled() -> bool {
|
pub fn is_enabled() -> bool {
|
||||||
*ENABLED.get_or_init(|| rustfs_utils::get_env_bool(ENV_TRUSTED_PROXY_ENABLED, DEFAULT_TRUSTED_PROXY_ENABLED))
|
*ENABLED.get_or_init(|| rustfs_utils::get_env_bool(ENV_TRUSTED_PROXY_ENABLED, DEFAULT_TRUSTED_PROXY_ENABLED))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn legacy_layer_is_available_without_explicit_init() {
|
||||||
|
let layer = layer();
|
||||||
|
assert_eq!(layer.is_enabled(), is_enabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn legacy_config_is_available_without_explicit_init() {
|
||||||
|
let expected = ConfigLoader::from_env_or_default();
|
||||||
|
let config = config();
|
||||||
|
|
||||||
|
assert_eq!(config.server_addr, expected.server_addr);
|
||||||
|
assert_eq!(config.monitoring.metrics_enabled, expected.monitoring.metrics_enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ mod tests {
|
|||||||
|
|
||||||
impl BootTimeInterface for TestBootTimeInterface {
|
impl BootTimeInterface for TestBootTimeInterface {
|
||||||
fn get(&self) -> Option<SystemTime> {
|
fn get(&self) -> Option<SystemTime> {
|
||||||
self.boot_time.clone()
|
self.boot_time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -655,7 +655,7 @@ impl DefaultMultipartUsecase {
|
|||||||
let server_side_encryption = Some(material.server_side_encryption.clone());
|
let server_side_encryption = Some(material.server_side_encryption.clone());
|
||||||
let ssekms_key_id = material.kms_key_id.clone();
|
let ssekms_key_id = material.kms_key_id.clone();
|
||||||
|
|
||||||
let mut encryption_metadata = encryption_material_to_metadata(&material);
|
let mut encryption_metadata = encryption_material_to_metadata(&material)?;
|
||||||
if material.key_kind == crate::storage::sse::EncryptionKeyKind::Object {
|
if material.key_kind == crate::storage::sse::EncryptionKeyKind::Object {
|
||||||
mark_encrypted_multipart_metadata(&mut encryption_metadata);
|
mark_encrypted_multipart_metadata(&mut encryption_metadata);
|
||||||
}
|
}
|
||||||
@@ -1473,7 +1473,8 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("prepare multipart encryption")
|
.expect("prepare multipart encryption")
|
||||||
.expect("managed multipart session material");
|
.expect("managed multipart session material");
|
||||||
let mut session_metadata = encryption_material_to_metadata(&session_material);
|
let mut session_metadata =
|
||||||
|
encryption_material_to_metadata(&session_material).expect("multipart session metadata should be generated");
|
||||||
mark_encrypted_multipart_metadata(&mut session_metadata);
|
mark_encrypted_multipart_metadata(&mut session_metadata);
|
||||||
|
|
||||||
let part_one_plaintext = vec![0x31; rustfs_rio::DEFAULT_ENCRYPTION_BLOCK_SIZE + 23];
|
let part_one_plaintext = vec![0x31; rustfs_rio::DEFAULT_ENCRYPTION_BLOCK_SIZE + 23];
|
||||||
|
|||||||
@@ -2298,7 +2298,7 @@ impl DefaultObjectUsecase {
|
|||||||
|
|
||||||
write_plan = write_plan.with_encryption(material.write_encryption(None));
|
write_plan = write_plan.with_encryption(material.write_encryption(None));
|
||||||
|
|
||||||
let encryption_metadata = encryption_material_to_metadata(&material);
|
let encryption_metadata = encryption_material_to_metadata(&material)?;
|
||||||
metadata.extend(encryption_metadata.clone());
|
metadata.extend(encryption_metadata.clone());
|
||||||
opts.user_defined.extend(encryption_metadata);
|
opts.user_defined.extend(encryption_metadata);
|
||||||
}
|
}
|
||||||
@@ -3333,7 +3333,7 @@ impl DefaultObjectUsecase {
|
|||||||
|
|
||||||
write_plan = write_plan.with_encryption(material.write_encryption(None));
|
write_plan = write_plan.with_encryption(material.write_encryption(None));
|
||||||
|
|
||||||
user_defined.extend(encryption_material_to_metadata(&material));
|
user_defined.extend(encryption_material_to_metadata(&material)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
reader = write_plan.apply(reader, actual_size).map_err(ApiError::from)?;
|
reader = write_plan.apply(reader, actual_size).map_err(ApiError::from)?;
|
||||||
@@ -4875,7 +4875,7 @@ impl DefaultObjectUsecase {
|
|||||||
|
|
||||||
write_plan = write_plan.with_encryption(material.write_encryption(None));
|
write_plan = write_plan.with_encryption(material.write_encryption(None));
|
||||||
|
|
||||||
let encryption_metadata = encryption_material_to_metadata(&material);
|
let encryption_metadata = encryption_material_to_metadata(&material)?;
|
||||||
metadata.extend(encryption_metadata.clone());
|
metadata.extend(encryption_metadata.clone());
|
||||||
opts.user_defined.extend(encryption_metadata);
|
opts.user_defined.extend(encryption_metadata);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,7 +381,12 @@ pub(crate) fn parse_object_lock_retention(retention: Option<ObjectLockRetention>
|
|||||||
"The retain until date must be in the future".to_string(),
|
"The retain until date must be in the future".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
retain_until.format(&Rfc3339).unwrap()
|
retain_until.format(&Rfc3339).map_err(|_| {
|
||||||
|
S3Error::with_message(
|
||||||
|
S3ErrorCode::InvalidArgument,
|
||||||
|
"The retain until date is not a supported RFC3339 timestamp".to_string(),
|
||||||
|
)
|
||||||
|
})?
|
||||||
} else {
|
} else {
|
||||||
String::default()
|
String::default()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -829,6 +829,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parse_object_lock_retention() {
|
fn test_parse_object_lock_retention() {
|
||||||
use time::macros::datetime;
|
use time::macros::datetime;
|
||||||
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
// [1] Normal case: No retention specified (empty metadata)
|
// [1] Normal case: No retention specified (empty metadata)
|
||||||
assert!(parse_object_lock_retention(None).is_ok());
|
assert!(parse_object_lock_retention(None).is_ok());
|
||||||
assert!(parse_object_lock_retention(None).unwrap().is_empty());
|
assert!(parse_object_lock_retention(None).unwrap().is_empty());
|
||||||
@@ -894,6 +895,19 @@ mod tests {
|
|||||||
let err = parse_object_lock_retention(Some(past_date_retention)).unwrap_err();
|
let err = parse_object_lock_retention(Some(past_date_retention)).unwrap_err();
|
||||||
assert_eq!(err.code().as_str(), S3ErrorCode::InvalidArgument.as_str());
|
assert_eq!(err.code().as_str(), S3ErrorCode::InvalidArgument.as_str());
|
||||||
assert_eq!(err.message(), Some("The retain until date must be in the future"));
|
assert_eq!(err.message(), Some("The retain until date must be in the future"));
|
||||||
|
|
||||||
|
// [8] Error case: timestamps that cannot be serialized as RFC3339 should return InvalidArgument instead of panicking
|
||||||
|
let unsupported_rfc3339_date = datetime!(2030-01-01 00:00:00 UTC)
|
||||||
|
.to_offset(UtcOffset::from_hms(5, 30, 45).expect("offset with seconds should be constructible"));
|
||||||
|
assert!(unsupported_rfc3339_date > OffsetDateTime::now_utc());
|
||||||
|
|
||||||
|
let unsupported_date_retention = ObjectLockRetention {
|
||||||
|
mode: Some(ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::COMPLIANCE)),
|
||||||
|
retain_until_date: Some(unsupported_rfc3339_date.into()),
|
||||||
|
};
|
||||||
|
let err = parse_object_lock_retention(Some(unsupported_date_retention)).unwrap_err();
|
||||||
|
assert_eq!(err.code().as_str(), S3ErrorCode::InvalidArgument.as_str());
|
||||||
|
assert_eq!(err.message(), Some("The retain until date is not a supported RFC3339 timestamp"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+34
-28
@@ -52,7 +52,7 @@
|
|||||||
//! };
|
//! };
|
||||||
//!
|
//!
|
||||||
//! if let Some(material) = sse_encryption(request).await? {
|
//! if let Some(material) = sse_encryption(request).await? {
|
||||||
//! metadata.extend(encryption_material_to_metadata(&material));
|
//! metadata.extend(encryption_material_to_metadata(&material)?);
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // Unified decryption API
|
//! // Unified decryption API
|
||||||
@@ -250,9 +250,9 @@ async fn prepare_sse_configuration(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
if effective_sse.is_none() {
|
let Some(effective_sse) = effective_sse else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
};
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
component = LOG_COMPONENT_STORAGE,
|
component = LOG_COMPONENT_STORAGE,
|
||||||
@@ -264,7 +264,7 @@ async fn prepare_sse_configuration(
|
|||||||
"Resolved effective SSE configuration"
|
"Resolved effective SSE configuration"
|
||||||
);
|
);
|
||||||
|
|
||||||
let effective_kms_key_id = resolve_effective_kms_key_id(effective_sse.as_ref(), ssekms_key_id, || {
|
let effective_kms_key_id = resolve_effective_kms_key_id(Some(&effective_sse), ssekms_key_id, || {
|
||||||
bucket_sse_config.rules.first().and_then(|rule| {
|
bucket_sse_config.rules.first().and_then(|rule| {
|
||||||
rule.apply_server_side_encryption_by_default
|
rule.apply_server_side_encryption_by_default
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -273,7 +273,7 @@ async fn prepare_sse_configuration(
|
|||||||
});
|
});
|
||||||
|
|
||||||
Ok(Some(SseConfiguration {
|
Ok(Some(SseConfiguration {
|
||||||
effective_sse: effective_sse.unwrap(),
|
effective_sse,
|
||||||
effective_kms_key_id,
|
effective_kms_key_id,
|
||||||
}))
|
}))
|
||||||
} else if let Err(e) = bucket_sse_config_result {
|
} else if let Err(e) = bucket_sse_config_result {
|
||||||
@@ -867,17 +867,18 @@ fn canonical_sse_path(bucket: &str, object: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rio-v2")]
|
#[cfg(feature = "rio-v2")]
|
||||||
fn derive_object_key(external_key: [u8; 32]) -> [u8; 32] {
|
fn derive_object_key(external_key: [u8; 32]) -> Result<[u8; 32], ApiError> {
|
||||||
let mut random = [0u8; 32];
|
let mut random = [0u8; 32];
|
||||||
rand::rng().fill(&mut random);
|
rand::rng().fill(&mut random);
|
||||||
|
|
||||||
let mut mac = HmacSha256::new_from_slice(&external_key).expect("32-byte HMAC key");
|
let mut mac = HmacSha256::new_from_slice(&external_key)
|
||||||
|
.map_err(|err| ApiError::from(StorageError::other(format!("Invalid HMAC key for object-key derivation: {err}"))))?;
|
||||||
mac.update(OBJECT_KEY_DERIVATION_CONTEXT);
|
mac.update(OBJECT_KEY_DERIVATION_CONTEXT);
|
||||||
mac.update(&random);
|
mac.update(&random);
|
||||||
|
|
||||||
let mut object_key = [0u8; 32];
|
let mut object_key = [0u8; 32];
|
||||||
object_key.copy_from_slice(mac.finalize().into_bytes().as_slice());
|
object_key.copy_from_slice(mac.finalize().into_bytes().as_slice());
|
||||||
object_key
|
Ok(object_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rio-v2")]
|
#[cfg(feature = "rio-v2")]
|
||||||
@@ -887,8 +888,9 @@ fn derive_sealing_key(
|
|||||||
domain: &str,
|
domain: &str,
|
||||||
bucket: &str,
|
bucket: &str,
|
||||||
object: &str,
|
object: &str,
|
||||||
) -> [u8; 32] {
|
) -> Result<[u8; 32], ApiError> {
|
||||||
let mut mac = HmacSha256::new_from_slice(&external_key).expect("32-byte HMAC key");
|
let mut mac = HmacSha256::new_from_slice(&external_key)
|
||||||
|
.map_err(|err| ApiError::from(StorageError::other(format!("Invalid HMAC key for sealing-key derivation: {err}"))))?;
|
||||||
mac.update(&iv);
|
mac.update(&iv);
|
||||||
mac.update(domain.as_bytes());
|
mac.update(domain.as_bytes());
|
||||||
mac.update(MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM.as_bytes());
|
mac.update(MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM.as_bytes());
|
||||||
@@ -896,7 +898,7 @@ fn derive_sealing_key(
|
|||||||
|
|
||||||
let mut sealing_key = [0u8; 32];
|
let mut sealing_key = [0u8; 32];
|
||||||
sealing_key.copy_from_slice(mac.finalize().into_bytes().as_slice());
|
sealing_key.copy_from_slice(mac.finalize().into_bytes().as_slice());
|
||||||
sealing_key
|
Ok(sealing_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rio-v2")]
|
#[cfg(feature = "rio-v2")]
|
||||||
@@ -909,7 +911,7 @@ fn seal_object_key(
|
|||||||
) -> Result<ManagedSealedKey, ApiError> {
|
) -> Result<ManagedSealedKey, ApiError> {
|
||||||
let mut iv = [0u8; SEALED_KEY_IV_SIZE];
|
let mut iv = [0u8; SEALED_KEY_IV_SIZE];
|
||||||
rand::rng().fill(&mut iv);
|
rand::rng().fill(&mut iv);
|
||||||
let sealing_key = derive_sealing_key(external_key, iv, managed_sse_domain(sse_type), bucket, object);
|
let sealing_key = derive_sealing_key(external_key, iv, managed_sse_domain(sse_type), bucket, object)?;
|
||||||
|
|
||||||
let mut header = [0u8; DARE_HEADER_SIZE];
|
let mut header = [0u8; DARE_HEADER_SIZE];
|
||||||
header[0] = DARE_VERSION_20;
|
header[0] = DARE_VERSION_20;
|
||||||
@@ -957,7 +959,7 @@ fn unseal_object_key(
|
|||||||
return Err(ApiError::from(StorageError::other("Invalid sealed object-key payload header")));
|
return Err(ApiError::from(StorageError::other("Invalid sealed object-key payload header")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let sealing_key = derive_sealing_key(external_key, sealed.iv, managed_sse_domain(sse_type), bucket, object);
|
let sealing_key = derive_sealing_key(external_key, sealed.iv, managed_sse_domain(sse_type), bucket, object)?;
|
||||||
let plaintext = decrypt_sealed_object_key_payload(sealing_key, header, &sealed.sealed_key)?;
|
let plaintext = decrypt_sealed_object_key_payload(sealing_key, header, &sealed.sealed_key)?;
|
||||||
|
|
||||||
let object_key: [u8; 32] = plaintext
|
let object_key: [u8; 32] = plaintext
|
||||||
@@ -1017,7 +1019,7 @@ pub(crate) fn build_ssec_read_headers(
|
|||||||
headers
|
headers
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> HashMap<String, String> {
|
pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> Result<HashMap<String, String>, ApiError> {
|
||||||
let mut metadata = HashMap::new();
|
let mut metadata = HashMap::new();
|
||||||
|
|
||||||
match material.sse_type {
|
match material.sse_type {
|
||||||
@@ -1054,7 +1056,7 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> HashMap
|
|||||||
let encrypted_data_key = material
|
let encrypted_data_key = material
|
||||||
.encrypted_data_key
|
.encrypted_data_key
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.expect("managed SSE materials must carry an encrypted data key");
|
.ok_or_else(|| ApiError::from(StorageError::other("managed SSE materials must carry an encrypted data key")))?;
|
||||||
metadata.insert(
|
metadata.insert(
|
||||||
"x-amz-server-side-encryption".to_string(),
|
"x-amz-server-side-encryption".to_string(),
|
||||||
material.server_side_encryption.as_str().to_string(),
|
material.server_side_encryption.as_str().to_string(),
|
||||||
@@ -1149,7 +1151,7 @@ pub fn encryption_material_to_metadata(material: &EncryptionMaterial) -> HashMap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata
|
Ok(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1374,7 +1376,7 @@ async fn apply_ssec_prepare_encryption_material(
|
|||||||
key: sse_key,
|
key: sse_key,
|
||||||
key_md5: sse_key_md5.clone(),
|
key_md5: sse_key_md5.clone(),
|
||||||
})?;
|
})?;
|
||||||
let object_key = derive_object_key(validated.key_bytes);
|
let object_key = derive_object_key(validated.key_bytes)?;
|
||||||
let sealed_key = seal_object_key(object_key, validated.key_bytes, SSEType::SseC, bucket, key)?;
|
let sealed_key = seal_object_key(object_key, validated.key_bytes, SSEType::SseC, bucket, key)?;
|
||||||
(object_key, [0; 12], EncryptionKeyKind::Object, Some(sealed_key))
|
(object_key, [0; 12], EncryptionKeyKind::Object, Some(sealed_key))
|
||||||
} else {
|
} else {
|
||||||
@@ -1421,7 +1423,7 @@ async fn apply_ssec_encryption_material(
|
|||||||
|
|
||||||
#[cfg(feature = "rio-v2")]
|
#[cfg(feature = "rio-v2")]
|
||||||
let (key_bytes, base_nonce, key_kind, managed_sealed_key) = {
|
let (key_bytes, base_nonce, key_kind, managed_sealed_key) = {
|
||||||
let object_key = derive_object_key(validated.key_bytes);
|
let object_key = derive_object_key(validated.key_bytes)?;
|
||||||
let sealed_key = seal_object_key(object_key, validated.key_bytes, SSEType::SseC, bucket, key)?;
|
let sealed_key = seal_object_key(object_key, validated.key_bytes, SSEType::SseC, bucket, key)?;
|
||||||
(object_key, [0; 12], EncryptionKeyKind::Object, Some(sealed_key))
|
(object_key, [0; 12], EncryptionKeyKind::Object, Some(sealed_key))
|
||||||
};
|
};
|
||||||
@@ -1557,7 +1559,7 @@ async fn apply_managed_encryption_material(
|
|||||||
let algorithm = server_side_encryption.as_str().to_string();
|
let algorithm = server_side_encryption.as_str().to_string();
|
||||||
#[cfg(feature = "rio-v2")]
|
#[cfg(feature = "rio-v2")]
|
||||||
let (key_bytes, base_nonce, key_kind, managed_sealed_key) = {
|
let (key_bytes, base_nonce, key_kind, managed_sealed_key) = {
|
||||||
let object_key = derive_object_key(data_key.plaintext_key);
|
let object_key = derive_object_key(data_key.plaintext_key)?;
|
||||||
let sealed_key = seal_object_key(object_key, data_key.plaintext_key, encryption_type, bucket, key)?;
|
let sealed_key = seal_object_key(object_key, data_key.plaintext_key, encryption_type, bucket, key)?;
|
||||||
(object_key, [0u8; 12], EncryptionKeyKind::Object, Some(sealed_key))
|
(object_key, [0u8; 12], EncryptionKeyKind::Object, Some(sealed_key))
|
||||||
};
|
};
|
||||||
@@ -2685,7 +2687,8 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("prepare should accept ssec headers");
|
.expect("prepare should accept ssec headers");
|
||||||
assert!(material.is_some());
|
assert!(material.is_some());
|
||||||
let metadata = encryption_material_to_metadata(&material.expect("ssec metadata should be generated"));
|
let metadata = encryption_material_to_metadata(&material.expect("ssec metadata should be generated"))
|
||||||
|
.expect("ssec metadata should be generated");
|
||||||
assert_eq!(metadata.get("x-amz-server-side-encryption").unwrap(), "AES256");
|
assert_eq!(metadata.get("x-amz-server-side-encryption").unwrap(), "AES256");
|
||||||
assert_eq!(metadata.get("x-amz-server-side-encryption-customer-algorithm").unwrap(), "AES256");
|
assert_eq!(metadata.get("x-amz-server-side-encryption-customer-algorithm").unwrap(), "AES256");
|
||||||
}
|
}
|
||||||
@@ -2716,7 +2719,7 @@ mod tests {
|
|||||||
.expect("ssec metadata should be generated");
|
.expect("ssec metadata should be generated");
|
||||||
assert_eq!(material.key_kind, EncryptionKeyKind::Object);
|
assert_eq!(material.key_kind, EncryptionKeyKind::Object);
|
||||||
|
|
||||||
let metadata = encryption_material_to_metadata(&material);
|
let metadata = encryption_material_to_metadata(&material).expect("ssec metadata should serialize");
|
||||||
assert!(metadata.contains_key(MINIO_INTERNAL_ENCRYPTION_IV_HEADER));
|
assert!(metadata.contains_key(MINIO_INTERNAL_ENCRYPTION_IV_HEADER));
|
||||||
assert!(metadata.contains_key(MINIO_INTERNAL_ENCRYPTION_SSEC_SEALED_KEY_HEADER));
|
assert!(metadata.contains_key(MINIO_INTERNAL_ENCRYPTION_SSEC_SEALED_KEY_HEADER));
|
||||||
}
|
}
|
||||||
@@ -2736,7 +2739,8 @@ mod tests {
|
|||||||
key_kind: EncryptionKeyKind::Direct,
|
key_kind: EncryptionKeyKind::Direct,
|
||||||
managed_kms_context: None,
|
managed_kms_context: None,
|
||||||
managed_sealed_key: None,
|
managed_sealed_key: None,
|
||||||
});
|
})
|
||||||
|
.expect("ssec original-size metadata should serialize");
|
||||||
|
|
||||||
assert_eq!(metadata.get(SSEC_ORIGINAL_SIZE_HEADER).map(String::as_str), Some("1024"));
|
assert_eq!(metadata.get(SSEC_ORIGINAL_SIZE_HEADER).map(String::as_str), Some("1024"));
|
||||||
}
|
}
|
||||||
@@ -2842,7 +2846,8 @@ mod tests {
|
|||||||
key_kind: EncryptionKeyKind::Direct,
|
key_kind: EncryptionKeyKind::Direct,
|
||||||
managed_kms_context: None,
|
managed_kms_context: None,
|
||||||
managed_sealed_key: None,
|
managed_sealed_key: None,
|
||||||
});
|
})
|
||||||
|
.expect("managed SSE metadata should serialize");
|
||||||
|
|
||||||
assert_eq!(metadata.get("x-amz-server-side-encryption").map(String::as_str), Some("aws:kms"));
|
assert_eq!(metadata.get("x-amz-server-side-encryption").map(String::as_str), Some("aws:kms"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2900,7 +2905,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("sse-kms encryption")
|
.expect("sse-kms encryption")
|
||||||
.expect("managed sse-kms material");
|
.expect("managed sse-kms material");
|
||||||
let metadata = encryption_material_to_metadata(&material);
|
let metadata = encryption_material_to_metadata(&material).expect("kms metadata should serialize");
|
||||||
let encoded_context = metadata
|
let encoded_context = metadata
|
||||||
.get(MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER)
|
.get(MINIO_INTERNAL_ENCRYPTION_KMS_CONTEXT_HEADER)
|
||||||
.expect("minio kms context header should exist");
|
.expect("minio kms context header should exist");
|
||||||
@@ -2963,7 +2968,8 @@ mod tests {
|
|||||||
key_kind: EncryptionKeyKind::Direct,
|
key_kind: EncryptionKeyKind::Direct,
|
||||||
managed_kms_context: None,
|
managed_kms_context: None,
|
||||||
managed_sealed_key: None,
|
managed_sealed_key: None,
|
||||||
});
|
})
|
||||||
|
.expect("managed SSE metadata should serialize");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metadata.get(MINIO_INTERNAL_ENCRYPTION_IV_HEADER).map(String::as_str),
|
metadata.get(MINIO_INTERNAL_ENCRYPTION_IV_HEADER).map(String::as_str),
|
||||||
@@ -3011,7 +3017,7 @@ mod tests {
|
|||||||
|
|
||||||
let material = sse_encryption(request).await.expect("sse-s3 encryption should succeed");
|
let material = sse_encryption(request).await.expect("sse-s3 encryption should succeed");
|
||||||
let material = material.expect("managed sse-s3 encryption should return material");
|
let material = material.expect("managed sse-s3 encryption should return material");
|
||||||
let metadata = encryption_material_to_metadata(&material);
|
let metadata = encryption_material_to_metadata(&material).expect("managed SSE-S3 metadata should serialize");
|
||||||
|
|
||||||
assert_eq!(material.kms_key_id, None);
|
assert_eq!(material.kms_key_id, None);
|
||||||
assert_eq!(metadata.get("x-amz-server-side-encryption").map(String::as_str), Some("AES256"));
|
assert_eq!(metadata.get("x-amz-server-side-encryption").map(String::as_str), Some("AES256"));
|
||||||
@@ -3104,7 +3110,7 @@ mod tests {
|
|||||||
.expect("managed sse material");
|
.expect("managed sse material");
|
||||||
assert_eq!(material.key_kind, EncryptionKeyKind::Object);
|
assert_eq!(material.key_kind, EncryptionKeyKind::Object);
|
||||||
|
|
||||||
let metadata = encryption_material_to_metadata(&material);
|
let metadata = encryption_material_to_metadata(&material).expect("managed SSE metadata should serialize");
|
||||||
assert!(!metadata.contains_key(INTERNAL_ENCRYPTION_KEY_HEADER));
|
assert!(!metadata.contains_key(INTERNAL_ENCRYPTION_KEY_HEADER));
|
||||||
assert!(!metadata.contains_key(INTERNAL_ENCRYPTION_IV_HEADER));
|
assert!(!metadata.contains_key(INTERNAL_ENCRYPTION_IV_HEADER));
|
||||||
|
|
||||||
@@ -3169,7 +3175,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(material.key_kind, EncryptionKeyKind::Object);
|
assert_eq!(material.key_kind, EncryptionKeyKind::Object);
|
||||||
|
|
||||||
let metadata = encryption_material_to_metadata(&material);
|
let metadata = encryption_material_to_metadata(&material).expect("sse-c metadata should serialize");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metadata.get(MINIO_INTERNAL_ENCRYPTION_ALGORITHM_HEADER).map(String::as_str),
|
metadata.get(MINIO_INTERNAL_ENCRYPTION_ALGORITHM_HEADER).map(String::as_str),
|
||||||
Some(MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM)
|
Some(MINIO_INTERNAL_ENCRYPTION_SEAL_ALGORITHM)
|
||||||
|
|||||||
@@ -15,52 +15,51 @@
|
|||||||
# accepted - reviewed and intentionally allowed
|
# accepted - reviewed and intentionally allowed
|
||||||
# todo - should be resolved in a future refactor
|
# todo - should be resolved in a future refactor
|
||||||
|
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_bucket_meta_hook|bucket use-case calls site replication hooks
|
cycle|app<->infra
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_delete_bucket_hook|bucket use-case calls site replication hooks
|
cycle|app<->interface
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_make_bucket_hook|bucket use-case calls site replication hooks
|
cycle|infra<->interface
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectVersionsParams|bucket use-case uses current S3 API bucket helper types
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_bucket_meta_hook
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectsV2Params|bucket use-case uses current S3 API bucket helper types
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_delete_bucket_hook
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_buckets_output|bucket use-case uses current S3 API bucket response helpers
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::admin::handlers::site_replication::site_replication_make_bucket_hook
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_object_versions_output|bucket use-case uses current S3 API bucket response helpers
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectVersionsParams
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_output|bucket use-case uses current S3 API bucket response helpers
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::ListObjectsV2Params
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_v2_output|bucket use-case uses current S3 API bucket response helpers
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_buckets_output
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_object_versions_params|bucket use-case uses current S3 API bucket query parser
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_object_versions_output
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_objects_v2_params|bucket use-case uses current S3 API bucket query parser
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_output
|
||||||
accepted|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::common::rustfs_owner|bucket use-case uses current S3 API owner helper
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::build_list_objects_v2_output
|
||||||
accepted|rustfs/src/app/lifecycle_transition_api_test.rs|app->interface|crate::storage::ecfs::FS|app test exercises ECFS through current interface path
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_object_versions_params
|
||||||
accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::ListMultipartUploadsParams|multipart use-case uses current S3 API multipart helper types
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::bucket::parse_list_objects_v2_params
|
||||||
accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_multipart_uploads_output|multipart use-case uses current S3 API multipart response helpers
|
dep|rustfs/src/app/bucket_usecase.rs|app->interface|crate::storage::s3_api::common::rustfs_owner
|
||||||
accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_parts_output|multipart use-case uses current S3 API multipart response helpers
|
dep|rustfs/src/app/lifecycle_transition_api_test.rs|app->interface|crate::storage::ecfs::FS
|
||||||
accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_multipart_uploads_params|multipart use-case uses current S3 API multipart query parser
|
dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::ListMultipartUploadsParams
|
||||||
accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params|multipart use-case uses current S3 API multipart query parser
|
dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_multipart_uploads_output
|
||||||
accepted|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_upload_part_number|multipart use-case uses current S3 API multipart part parser
|
dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::build_list_parts_output
|
||||||
accepted|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::ecfs|object use-case uses current ECFS interface helpers
|
dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_multipart_uploads_params
|
||||||
accepted|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params|object use-case uses current S3 API multipart query parser
|
dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params
|
||||||
accepted|rustfs/src/init.rs|infra->interface|crate::admin|init wires the admin module during process setup
|
dep|rustfs/src/app/multipart_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_upload_part_number
|
||||||
accepted|rustfs/src/protocols/client.rs|infra->interface|crate::storage::ecfs::FS|protocol client delegates to the current ECFS S3 implementation
|
dep|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::ecfs
|
||||||
accepted|rustfs/src/server/audit.rs|infra->app|crate::app::context::resolve_server_config|config resolution uses global AppContext
|
dep|rustfs/src/app/object_usecase.rs|app->interface|crate::storage::s3_api::multipart::parse_list_parts_params
|
||||||
accepted|rustfs/src/server/event.rs|infra->app|crate::app::context::resolve_server_config|config resolution uses global AppContext
|
dep|rustfs/src/init.rs|infra->interface|crate::admin
|
||||||
accepted|rustfs/src/server/http.rs|infra->interface|crate::admin|HTTP server routes to admin handlers
|
dep|rustfs/src/protocols/client.rs|infra->interface|crate::storage::ecfs::FS
|
||||||
accepted|rustfs/src/server/layer.rs|infra->app|crate::app::context::resolve_kms_runtime_service_manager|HTTP layer reads KMS runtime through AppContext resolver
|
dep|rustfs/src/server/audit.rs|infra->app|crate::app::context::resolve_server_config
|
||||||
accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::console::is_console_path|pure path predicate for routing
|
dep|rustfs/src/server/event.rs|infra->app|crate::app::context::resolve_server_config
|
||||||
accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::HealthProbe|HTTP layer builds health responses
|
dep|rustfs/src/server/http.rs|infra->interface|crate::admin
|
||||||
accepted|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::build_health_response_parts|HTTP layer builds health responses
|
dep|rustfs/src/server/layer.rs|infra->app|crate::app::context::resolve_kms_runtime_service_manager
|
||||||
accepted|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_endpoints_handle|readiness checks endpoint availability through AppContext resolver
|
dep|rustfs/src/server/layer.rs|infra->interface|crate::admin::console::is_console_path
|
||||||
accepted|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_iam_ready|readiness checks IAM availability through AppContext resolver
|
dep|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::HealthProbe
|
||||||
accepted|rustfs/src/startup_iam.rs|infra->app|crate::app::context::AppContext|startup wires IAM bootstrap through AppContext
|
dep|rustfs/src/server/layer.rs|infra->interface|crate::admin::handlers::health::build_health_response_parts
|
||||||
accepted|rustfs/src/storage/ecfs_extend.rs|infra->app|crate::app::context::resolve_buffer_config|storage buffer sizing reads runtime config through AppContext resolver
|
dep|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_endpoints_handle
|
||||||
accepted|rustfs/src/storage/ecfs_extend.rs|infra->interface|crate::storage::ecfs::ListObjectUnorderedQuery|storage extension uses current ECFS interface query type
|
dep|rustfs/src/server/readiness.rs|infra->app|crate::app::context::resolve_iam_ready
|
||||||
accepted|rustfs/src/storage/ecfs_extend.rs|infra->app|crate::app::context::resolve_buffer_config|buffer config resolution uses global AppContext
|
dep|rustfs/src/startup_iam.rs|infra->app|crate::app::context::AppContext
|
||||||
accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::FS|storage tests exercise current ECFS interface path
|
dep|rustfs/src/storage/ecfs_extend.rs|infra->app|crate::app::context::resolve_buffer_config
|
||||||
accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::validate_object_lock_configuration_input|storage tests exercise current ECFS object-lock validator
|
dep|rustfs/src/storage/ecfs_extend.rs|infra->interface|crate::storage::ecfs::ListObjectUnorderedQuery
|
||||||
accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_initiator|storage tests use current S3 API initiator helper
|
dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::FS
|
||||||
accepted|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_owner|storage tests use current S3 API owner helper
|
dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::ecfs::validate_object_lock_configuration_input
|
||||||
accepted|rustfs/src/storage/rpc/health.rs|infra->app|crate::app::context::resolve_local_node_name|RPC health labels node identity through AppContext resolver
|
dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_initiator
|
||||||
accepted|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_iam_handle|node RPC IAM operations use AppContext resolver fallback
|
dep|rustfs/src/storage/ecfs_test.rs|infra->interface|crate::storage::s3_api::common::rustfs_owner
|
||||||
accepted|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_lock_client|node RPC locking uses AppContext resolver fallback
|
dep|rustfs/src/storage/rpc/health.rs|infra->app|crate::app::context::resolve_local_node_name
|
||||||
accepted|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_dynamic_config_runtime_state|node RPC reloads admin runtime state
|
dep|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_iam_handle
|
||||||
accepted|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_runtime_config_snapshot|node RPC reloads admin runtime state
|
dep|rustfs/src/storage/rpc/node_service.rs|infra->app|crate::app::context::resolve_lock_client
|
||||||
accepted|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::site_replication::reload_site_replication_runtime_state|node RPC reloads admin runtime state
|
dep|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_dynamic_config_runtime_state
|
||||||
accepted|cycle|app<->infra|existing intra-crate cross-layer references
|
dep|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::config::reload_runtime_config_snapshot
|
||||||
accepted|cycle|app<->interface|existing intra-crate cross-layer references
|
dep|rustfs/src/storage/rpc/node_service.rs|infra->interface|crate::admin::service::site_replication::reload_site_replication_runtime_state
|
||||||
accepted|cycle|infra<->interface|existing intra-crate cross-layer references
|
|
||||||
|
|||||||
Reference in New Issue
Block a user