Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(s3s/sig_v4): support sts signature #208

Merged
merged 2 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/s3s-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
50 changes: 49 additions & 1 deletion crates/s3s-test/e2e/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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 })
}
}

Expand Down Expand Up @@ -286,6 +292,47 @@ impl Put {
}
}

#[allow(clippy::upper_case_acronyms)]
struct STS {
sts: aws_sdk_sts::Client,
}

impl TestFixture<E2E> for STS {
async fn setup(suite: Arc<E2E>) -> Result<Self> {
Ok(Self { sts: suite.sts.clone() })
}
}

impl STS {
async fn test_assume_role(self: Arc<Self>) -> 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 {
Expand All @@ -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);
})
}
2 changes: 1 addition & 1 deletion crates/s3s/src/http/aws_chunked_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
28 changes: 17 additions & 11 deletions crates/s3s/src/ops/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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?;
Expand All @@ -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;
Expand Down
24 changes: 12 additions & 12 deletions crates/s3s/src/sig_v4/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <SmallVec<[u8; 128]>>::with_capacity(secret_key.len().saturating_add(4));
Expand All @@ -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");
Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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",);
}

Expand Down Expand Up @@ -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 =
Expand All @@ -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, &[]);
Expand All @@ -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");
}

Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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");
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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");
}
}
Expand Down
Loading