mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 08:38:58 +00:00
fix(credentials): fail closed when deriving RPC secret from default credentials (#4402)
The internode RPC HMAC secret is derived from the S3 credential pair via `derive_rpc_secret` when `RUSTFS_RPC_SECRET` is unset. The derivation uses the secret key as the HMAC key, so when the default secret key (`rustfsadmin`) is in effect the derived RPC secret is a fixed, publicly computable value. Any network peer can then forge valid `x-rustfs-signature` headers and invoke internode RPC routes (e.g. `read_file_stream`), bypassing S3 IAM entirely. `normalize_rpc_secret` already rejected the literal default when it was supplied directly, but `resolve_rpc_secret` still derived a secret from the default credential pair. Make the derivation path fail closed: refuse to derive while the default secret key is in effect, forcing operators to set `RUSTFS_RPC_SECRET` (or a non-default `RUSTFS_SECRET_KEY`). A default access key paired with a non-default secret key remains safe and is still allowed. Addresses GHSA-68cw-96m3-h2cf (incomplete-fix follow-up to CVE-2026-45039).
This commit is contained in:
@@ -259,7 +259,18 @@ fn resolve_rpc_secret(env_secret: Option<&str>, global_access: Option<&str>, glo
|
|||||||
}
|
}
|
||||||
|
|
||||||
match (global_access, global_secret) {
|
match (global_access, global_secret) {
|
||||||
(Some(access_key), Some(secret_key)) => derive_rpc_secret(access_key, secret_key),
|
(Some(access_key), Some(secret_key)) => {
|
||||||
|
// Fail closed: never derive the RPC secret while the default secret
|
||||||
|
// key is in effect. The derivation uses `secret_key` as the HMAC key,
|
||||||
|
// so a public default secret yields a publicly computable RPC secret
|
||||||
|
// that any network peer can use to forge internode RPC signatures.
|
||||||
|
// Operators running with default credentials must configure
|
||||||
|
// RUSTFS_RPC_SECRET (or set a non-default RUSTFS_SECRET_KEY) instead.
|
||||||
|
if secret_key.trim() == DEFAULT_SECRET_KEY {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
derive_rpc_secret(access_key, secret_key)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,12 +586,19 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_rpc_secret_allows_default_credentials_for_derivation() {
|
fn test_resolve_rpc_secret_rejects_default_credentials_for_derivation() {
|
||||||
assert!(resolve_rpc_secret(None, None, None).is_none());
|
assert!(resolve_rpc_secret(None, None, None).is_none());
|
||||||
|
|
||||||
let expected = derive_rpc_secret(DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY).expect("secret should derive");
|
// Fail closed: the default secret key must not yield a derivable RPC
|
||||||
|
// secret, otherwise the derived value is publicly computable and any
|
||||||
|
// network peer can forge internode RPC signatures.
|
||||||
|
assert!(resolve_rpc_secret(None, Some(DEFAULT_ACCESS_KEY), Some(DEFAULT_SECRET_KEY)).is_none());
|
||||||
|
|
||||||
|
// A default access key paired with a non-default secret key is still
|
||||||
|
// safe to derive: the HMAC key (the secret key) is not public.
|
||||||
|
let expected = derive_rpc_secret(DEFAULT_ACCESS_KEY, "custom-global-secret").expect("secret should derive");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve_rpc_secret(None, Some(DEFAULT_ACCESS_KEY), Some(DEFAULT_SECRET_KEY)).as_deref(),
|
resolve_rpc_secret(None, Some(DEFAULT_ACCESS_KEY), Some("custom-global-secret")).as_deref(),
|
||||||
Some(expected.as_str())
|
Some(expected.as_str())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user