mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-08 04:58:12 +00:00
fix(auth): isolate embedded IAM contexts (#5704)
This commit is contained in:
@@ -71,22 +71,27 @@ fn hmac(key: &[u8], value: &str) -> Vec<u8> {
|
||||
fn signed_admin_request(
|
||||
client: &reqwest::Client,
|
||||
endpoint: &str,
|
||||
method: reqwest::Method,
|
||||
request_path: &str,
|
||||
access_key: &str,
|
||||
secret_key: &str,
|
||||
body: &[u8],
|
||||
) -> reqwest::RequestBuilder {
|
||||
let host = endpoint
|
||||
.strip_prefix("http://")
|
||||
.or_else(|| endpoint.strip_prefix("https://"))
|
||||
.expect("embedded endpoint scheme");
|
||||
let payload_hash = sha256_hex(b"");
|
||||
let payload_hash = sha256_hex(body);
|
||||
let now = Utc::now();
|
||||
let amz_date = now.format("%Y%m%dT%H%M%SZ").to_string();
|
||||
let date = now.format("%Y%m%d").to_string();
|
||||
let canonical_headers = format!("host:{host}\nx-amz-content-sha256:{payload_hash}\nx-amz-date:{amz_date}\n");
|
||||
let signed_headers = "host;x-amz-content-sha256;x-amz-date";
|
||||
let (path, query) = request_path.split_once('?').unwrap_or((request_path, ""));
|
||||
let canonical_request = format!("GET\n{path}\n{query}\n{canonical_headers}\n{signed_headers}\n{payload_hash}");
|
||||
let canonical_request = format!(
|
||||
"{}\n{path}\n{query}\n{canonical_headers}\n{signed_headers}\n{payload_hash}",
|
||||
method.as_str()
|
||||
);
|
||||
let scope = format!("{date}/us-east-1/s3/aws4_request");
|
||||
let string_to_sign = format!("AWS4-HMAC-SHA256\n{amz_date}\n{scope}\n{}", sha256_hex(canonical_request.as_bytes()));
|
||||
let date_key = hmac(format!("AWS4{secret_key}").as_bytes(), &date);
|
||||
@@ -99,11 +104,12 @@ fn signed_admin_request(
|
||||
);
|
||||
|
||||
client
|
||||
.get(format!("{endpoint}{request_path}"))
|
||||
.request(method, format!("{endpoint}{request_path}"))
|
||||
.header("host", host)
|
||||
.header("x-amz-content-sha256", payload_hash)
|
||||
.header("x-amz-date", amz_date)
|
||||
.header("authorization", authorization)
|
||||
.body(body.to_vec())
|
||||
}
|
||||
|
||||
// backlog#1052 acceptance: a second embedded server in the same process no
|
||||
@@ -328,6 +334,81 @@ async fn two_embedded_servers_isolate_auth_and_data_planes_body() {
|
||||
server_b.shutdown().await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test-hooks")]
|
||||
#[tokio::test]
|
||||
async fn embedded_servers_isolate_iam_users_and_policies() {
|
||||
let port_a = find_available_port().expect("find free port for server A");
|
||||
let server_a = RustFSServerBuilder::new()
|
||||
.address(format!("127.0.0.1:{port_a}"))
|
||||
.access_key("iam-root-a")
|
||||
.secret_key("iam-root-secret-a")
|
||||
.build()
|
||||
.await
|
||||
.expect("start embedded server A");
|
||||
let port_b = find_available_port().expect("find free port for server B");
|
||||
let server_b = RustFSServerBuilder::new()
|
||||
.address(format!("127.0.0.1:{port_b}"))
|
||||
.access_key("iam-root-b")
|
||||
.secret_key("iam-root-secret-b")
|
||||
.build()
|
||||
.await
|
||||
.expect("start embedded server B");
|
||||
|
||||
let http = reqwest::Client::builder()
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("build local admin client");
|
||||
let user_body = serde_json::json!({"secretKey": "iam-user-secret-a", "status": "enabled"}).to_string();
|
||||
for (path, body) in [
|
||||
("/rustfs/admin/v3/add-user?accessKey=iam-user-a", user_body.as_bytes()),
|
||||
(
|
||||
"/rustfs/admin/v3/set-user-or-group-policy?policyName=readwrite&userOrGroup=iam-user-a&isGroup=false",
|
||||
b"".as_slice(),
|
||||
),
|
||||
] {
|
||||
let response = signed_admin_request(
|
||||
&http,
|
||||
&server_a.endpoint(),
|
||||
reqwest::Method::PUT,
|
||||
path,
|
||||
server_a.access_key(),
|
||||
server_a.secret_key(),
|
||||
body,
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.expect("send server A IAM request");
|
||||
assert!(response.status().is_success(), "server A IAM setup failed: {}", response.status());
|
||||
}
|
||||
|
||||
let client_b = s3_client(&server_b.endpoint(), server_b.access_key(), server_b.secret_key());
|
||||
client_b
|
||||
.create_bucket()
|
||||
.bucket("private-to-b")
|
||||
.send()
|
||||
.await
|
||||
.expect("create server B bucket");
|
||||
client_b
|
||||
.put_object()
|
||||
.bucket("private-to-b")
|
||||
.key("secret.txt")
|
||||
.body(ByteStream::from_static(b"server B data"))
|
||||
.send()
|
||||
.await
|
||||
.expect("write server B object");
|
||||
|
||||
let cross_instance = s3_client(&server_b.endpoint(), "iam-user-a", "iam-user-secret-a")
|
||||
.get_object()
|
||||
.bucket("private-to-b")
|
||||
.key("secret.txt")
|
||||
.send()
|
||||
.await;
|
||||
assert!(cross_instance.is_err(), "server A IAM user must not authorize against server B");
|
||||
|
||||
server_b.shutdown().await;
|
||||
server_a.shutdown().await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-test-hooks")]
|
||||
#[tokio::test]
|
||||
async fn second_embedded_server_fails_closed_until_its_context_slot_is_installed() {
|
||||
@@ -392,10 +473,11 @@ async fn second_embedded_server_fails_closed_until_its_context_slot_is_installed
|
||||
.build()
|
||||
.expect("build local admin client without proxy");
|
||||
let inspect_path = "/rustfs/admin/v3/inspect-data?file=marker.txt&volume=startup-window";
|
||||
let before_install = signed_admin_request(&http, &endpoint_b, inspect_path, b_access_key, b_secret_key)
|
||||
.send()
|
||||
.await
|
||||
.expect("server B HTTP listener must accept the paused request");
|
||||
let before_install =
|
||||
signed_admin_request(&http, &endpoint_b, reqwest::Method::GET, inspect_path, b_access_key, b_secret_key, b"")
|
||||
.send()
|
||||
.await
|
||||
.expect("server B HTTP listener must accept the paused request");
|
||||
let before_install_status = before_install.status();
|
||||
let before_install_body = before_install.text().await.expect("read paused response body");
|
||||
assert_eq!(before_install_status, StatusCode::SERVICE_UNAVAILABLE, "{before_install_body}");
|
||||
@@ -426,10 +508,18 @@ async fn second_embedded_server_fails_closed_until_its_context_slot_is_installed
|
||||
.await
|
||||
.expect("server B writes its marker");
|
||||
|
||||
let after_install = signed_admin_request(&http, &server_b.endpoint(), inspect_path, b_access_key, b_secret_key)
|
||||
.send()
|
||||
.await
|
||||
.expect("server B admin request after context installation");
|
||||
let after_install = signed_admin_request(
|
||||
&http,
|
||||
&server_b.endpoint(),
|
||||
reqwest::Method::GET,
|
||||
inspect_path,
|
||||
b_access_key,
|
||||
b_secret_key,
|
||||
b"",
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.expect("server B admin request after context installation");
|
||||
assert_eq!(after_install.status(), StatusCode::OK);
|
||||
assert_eq!(after_install.bytes().await.expect("read server B marker"), b"from B".as_slice());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user