From f443e8481c8b35b4c969481ac86357e0ecb03b8b Mon Sep 17 00:00:00 2001 From: Nugine Date: Sat, 26 Oct 2024 02:38:20 +0800 Subject: [PATCH 1/2] fix(s3s/sig_v4): support sts signature --- crates/s3s/src/http/aws_chunked_stream.rs | 2 +- crates/s3s/src/ops/signature.rs | 28 ++++++++++++++--------- crates/s3s/src/sig_v4/methods.rs | 24 +++++++++---------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/crates/s3s/src/http/aws_chunked_stream.rs b/crates/s3s/src/http/aws_chunked_stream.rs index 6ce70e4..550b4ef 100644 --- a/crates/s3s/src/http/aws_chunked_stream.rs +++ b/crates/s3s/src/http/aws_chunked_stream.rs @@ -99,7 +99,7 @@ fn check_signature(ctx: &SignatureCtx, expected_signature: &[u8], chunk_data: &[ let string_to_sign = sig_v4::create_chunk_string_to_sign(&ctx.amz_date, &ctx.region, &ctx.service, &ctx.prev_signature, chunk_data); - let chunk_signature = sig_v4::calculate_signature(&string_to_sign, &ctx.secret_key, &ctx.amz_date, &ctx.region); + let chunk_signature = sig_v4::calculate_signature(&string_to_sign, &ctx.secret_key, &ctx.amz_date, &ctx.region, &ctx.service); (chunk_signature.as_bytes() == expected_signature).then(|| chunk_signature.into()) } diff --git a/crates/s3s/src/ops/signature.rs b/crates/s3s/src/ops/signature.rs index 2246c1e..a1a6ab2 100644 --- a/crates/s3s/src/ops/signature.rs +++ b/crates/s3s/src/ops/signature.rs @@ -158,7 +158,7 @@ impl SignatureContext<'_> { let secret_key = auth.get_secret_key(&access_key).await?; let string_to_sign = info.policy; - let signature = sig_v4::calculate_signature(string_to_sign, &secret_key, &amz_date, credential.aws_region); + let signature = sig_v4::calculate_signature(string_to_sign, &secret_key, &amz_date, credential.aws_region, credential.aws_service); let expected_signature = info.x_amz_signature; if signature != expected_signature { @@ -220,7 +220,7 @@ impl SignatureContext<'_> { let amz_date = &presigned_url.amz_date; let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, amz_date, region, service); - sig_v4::calculate_signature(&string_to_sign, &secret_key, amz_date, region) + sig_v4::calculate_signature(&string_to_sign, &secret_key, amz_date, region, service) }; let expected_signature = presigned_url.signature; @@ -243,18 +243,27 @@ impl SignatureContext<'_> { a.signed_headers.sort_unstable(); a }; + let region = authorization.credential.aws_region; + let service = authorization.credential.aws_service; + + if !matches!(service, "s3" | "sts") { + return Err(s3_error!(NotImplemented, "unknown service")); + } let auth = require_auth(self.auth)?; - let amz_content_sha256 = - extract_amz_content_sha256(&self.hs)?.ok_or_else(|| invalid_request!("missing header: x-amz-content-sha256"))?; + let amz_content_sha256 = extract_amz_content_sha256(&self.hs)?; + + if service == "s3" && amz_content_sha256.is_none() { + return Err(invalid_request!("missing header: x-amz-content-sha256")); + } let access_key = authorization.credential.access_key_id; let secret_key = auth.get_secret_key(access_key).await?; let amz_date = extract_amz_date(&self.hs)?.ok_or_else(|| invalid_request!("missing header: x-amz-date"))?; - let is_stream = matches!(amz_content_sha256, AmzContentSha256::MultipleChunks); + let is_stream = matches!(amz_content_sha256, Some(AmzContentSha256::MultipleChunks)); let signature = { let method = &self.req_method; @@ -268,13 +277,13 @@ impl SignatureContext<'_> { let payload = sig_v4::Payload::MultipleChunks; sig_v4::create_canonical_request(method, uri_path, query_strings, &headers, payload) } else if matches!(*self.req_method, Method::GET | Method::HEAD) { - let payload = if matches!(amz_content_sha256, AmzContentSha256::UnsignedPayload) { + let payload = if matches!(amz_content_sha256, Some(AmzContentSha256::UnsignedPayload)) { sig_v4::Payload::Unsigned } else { sig_v4::Payload::Empty }; sig_v4::create_canonical_request(method, uri_path, query_strings, &headers, payload) - } else if matches!(amz_content_sha256, AmzContentSha256::UnsignedPayload) { + } else if matches!(amz_content_sha256, Some(AmzContentSha256::UnsignedPayload)) { sig_v4::create_canonical_request(method, uri_path, query_strings, &headers, sig_v4::Payload::Unsigned) } else { let bytes = super::extract_full_body(self.content_length, self.req_body).await?; @@ -296,11 +305,8 @@ impl SignatureContext<'_> { ) } }; - - let region = authorization.credential.aws_region; - let service = authorization.credential.aws_service; let string_to_sign = sig_v4::create_string_to_sign(&canonical_request, &amz_date, region, service); - sig_v4::calculate_signature(&string_to_sign, &secret_key, &amz_date, region) + sig_v4::calculate_signature(&string_to_sign, &secret_key, &amz_date, region, service) }; let expected_signature = authorization.signature; diff --git a/crates/s3s/src/sig_v4/methods.rs b/crates/s3s/src/sig_v4/methods.rs index 4444786..dc02a77 100644 --- a/crates/s3s/src/sig_v4/methods.rs +++ b/crates/s3s/src/sig_v4/methods.rs @@ -263,7 +263,7 @@ pub fn create_chunk_string_to_sign( /// calculate signature #[must_use] -pub fn calculate_signature(string_to_sign: &str, secret_key: &SecretKey, amz_date: &AmzDate, region: &str) -> String { +pub fn calculate_signature(string_to_sign: &str, secret_key: &SecretKey, amz_date: &AmzDate, region: &str, service: &str) -> String { let mut secret = { let secret_key = secret_key.expose(); let mut buf = >::with_capacity(secret_key.len().saturating_add(4)); @@ -283,7 +283,7 @@ pub fn calculate_signature(string_to_sign: &str, secret_key: &SecretKey, amz_dat let date_region_key = hmac_sha256(date_key, region); // TODO: use a `Region` type // DateRegionServiceKey - let date_region_service_key = hmac_sha256(date_region_key, "s3"); + let date_region_service_key = hmac_sha256(date_region_key, service); // SigningKey let signing_key = hmac_sha256(date_region_service_key, "aws4_request"); @@ -438,7 +438,7 @@ mod tests { ) ); - let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region); + let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region, service); assert_eq!(signature, "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41"); } @@ -495,7 +495,7 @@ mod tests { ) ); - let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region); + let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region, service); assert_eq!(signature, "98ad721746da40c64f1a55b78f14c238d841ea1380cd77a1b5971af0ece108bd"); } @@ -555,7 +555,7 @@ mod tests { ) ); - let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region); + let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region, service); assert_eq!(signature, "4f232c4386841ef735655705268965c44a0e4690baa4adea153f7db9fa80a0a9",); } @@ -583,7 +583,7 @@ mod tests { ) ); - let chunk1_signature = calculate_signature(&chunk1_string_to_sign, &secret_access_key, &date, region); + let chunk1_signature = calculate_signature(&chunk1_string_to_sign, &secret_access_key, &date, region, service); assert_eq!(chunk1_signature, "ad80c730a21e5b8d04586a2213dd63b9a0e99e0e2307b0ade35a65485a288648"); let chunk2_string_to_sign = @@ -600,7 +600,7 @@ mod tests { ) ); - let chunk2_signature = calculate_signature(&chunk2_string_to_sign, &secret_access_key, &date, region); + let chunk2_signature = calculate_signature(&chunk2_string_to_sign, &secret_access_key, &date, region, service); assert_eq!(chunk2_signature, "0055627c9e194cb4542bae2aa5492e3c1575bbb81b612b7d234b86a503ef5497"); let chunk3_string_to_sign = create_chunk_string_to_sign(&date, region, service, &chunk2_signature, &[]); @@ -616,7 +616,7 @@ mod tests { ) ); - let chunk3_signature = calculate_signature(&chunk3_string_to_sign, &secret_access_key, &date, region); + let chunk3_signature = calculate_signature(&chunk3_string_to_sign, &secret_access_key, &date, region, service); assert_eq!(chunk3_signature, "b6c6ea8a5354eaf15b3cb7646744f4275b71ea724fed81ceb9323e279d449df9"); } @@ -668,7 +668,7 @@ mod tests { ) ); - let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region); + let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region, service); assert_eq!(signature, "fea454ca298b7da1c68078a5d1bdbfbbe0d65c699e0f91ac7a200a0136783543"); } @@ -721,7 +721,7 @@ mod tests { ) ); - let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region); + let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region, service); assert_eq!(signature, "34b48302e7b5fa45bde8084f4b7868a86f0a534bc59db6670ed5711ef69dc6f7"); } @@ -787,7 +787,7 @@ mod tests { ) ); - let signature = calculate_signature(&string_to_sign, &secret_access_key, &info.amz_date, info.credential.aws_region); + let signature = calculate_signature(&string_to_sign, &secret_access_key, &info.amz_date, info.credential.aws_region, info.credential.aws_service); assert_eq!(signature, "aeeed9bbccd4d02ee5c0109b86d86835f995330da4c265957d157751f604d404"); assert_eq!(signature, info.signature); } @@ -836,7 +836,7 @@ mod tests { let string_to_sign = create_string_to_sign(&canonical_request, &date, region, service); - let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region); + let signature = calculate_signature(&string_to_sign, &secret_access_key, &date, region, service); assert_eq!(signature, "96ad058ca27352e0fc2bd4efd8973792077570667bdaf749655f42e204bc649c"); } } From 896118407647b1c8c1a3c49df6d363e41caee27d Mon Sep 17 00:00:00 2001 From: Nugine Date: Sat, 26 Oct 2024 02:38:20 +0800 Subject: [PATCH 2/2] test(s3s-e2e): test_assume_role --- crates/s3s-test/Cargo.toml | 1 + crates/s3s-test/e2e/main.rs | 50 ++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/crates/s3s-test/Cargo.toml b/crates/s3s-test/Cargo.toml index 8edc133..dc5d1a3 100644 --- a/crates/s3s-test/Cargo.toml +++ b/crates/s3s-test/Cargo.toml @@ -28,6 +28,7 @@ colored = "2.1.0" regex = "1.11.0" nugine-rust-utils = "0.3.1" backtrace = "0.3.74" +aws-sdk-sts = { version = "1.46.0", features = ["behavior-version-latest"] } [dependencies.aws-config] version = "1.5.8" diff --git a/crates/s3s-test/e2e/main.rs b/crates/s3s-test/e2e/main.rs index eb0cdba..be3a40a 100644 --- a/crates/s3s-test/e2e/main.rs +++ b/crates/s3s-test/e2e/main.rs @@ -19,8 +19,11 @@ use aws_sdk_s3::primitives::ByteStream; use s3s_test::Result; use s3s_test::TestFixture; use s3s_test::TestSuite; +use tracing::debug; +use tracing::warn; use std::fmt; +use std::ops::Not; use std::process::Termination; use std::sync::Arc; @@ -82,6 +85,7 @@ async fn delete_object_strict(s3: &aws_sdk_s3::Client, bucket: &str, key: &str) struct E2E { s3: aws_sdk_s3::Client, + sts: aws_sdk_sts::Client, } impl TestSuite for E2E { @@ -94,7 +98,9 @@ impl TestSuite for E2E { .build(), ); - Ok(Self { s3 }) + let sts = aws_sdk_sts::Client::new(&sdk_conf); + + Ok(Self { s3, sts }) } } @@ -286,6 +292,47 @@ impl Put { } } +#[allow(clippy::upper_case_acronyms)] +struct STS { + sts: aws_sdk_sts::Client, +} + +impl TestFixture for STS { + async fn setup(suite: Arc) -> Result { + Ok(Self { sts: suite.sts.clone() }) + } +} + +impl STS { + async fn test_assume_role(self: Arc) -> Result<()> { + let sts = &self.sts; + + let result = sts.assume_role().role_arn("example").role_session_name("test").send().await; + + // FIXME: NotImplemented + if let Err(SdkError::ServiceError(ref err)) = &result { + if err.raw().status().as_u16() == 501 { + warn!(?err, "STS:AssumeRole is not implemented"); + return Ok(()); + } + } + + let resp = result?; + + let credentials = resp.credentials().unwrap(); + assert!(credentials.access_key_id().is_empty().not(), "Expected non-empty access key ID"); + assert!(credentials.secret_access_key().is_empty().not(), "Expected non-empty secret access key"); + assert!(credentials.session_token().is_empty().not(), "Expected session token in the response"); + + debug!(ak=?credentials.access_key_id()); + debug!(sk=?credentials.secret_access_key()); + debug!(st=?credentials.session_token()); + debug!(exp=?credentials.expiration()); + + Ok(()) + } +} + fn main() -> impl Termination { s3s_test::cli::main(|tcx| { macro_rules! case { @@ -300,5 +347,6 @@ fn main() -> impl Termination { case!(E2E, Basic, test_list_objects); case!(E2E, Basic, test_get_object); case!(E2E, Put, test_put_object_tiny); + case!(E2E, STS, test_assume_role); }) }