diff --git a/object_store/src/aws/credential.rs b/object_store/src/aws/credential.rs index 173c94e2df8..b8b46aa1182 100644 --- a/object_store/src/aws/credential.rs +++ b/object_store/src/aws/credential.rs @@ -194,6 +194,64 @@ impl<'a> AwsAuthorizer<'a> { request.headers_mut().insert(AUTH_HEADER, authorization_val); } + /// Authorize a request via `method` to `url` by attaching the relevant [AWS SigV4] query parameters. + /// + /// [AWS SigV4]: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html + pub fn sign(&self, method: Method, url: &mut Url) { + let date = self.date.unwrap_or_else(Utc::now); + + let digest = UNSIGNED_PAYLOAD; + + let canonical_uri = match self.service { + "s3" => url.path().to_string(), + _ => utf8_percent_encode(url.path(), &STRICT_PATH_ENCODE_SET) + .to_string(), + }; + + let mut headers = HeaderMap::new(); + + if let Some(ref token) = self.credential.token { + let token_val = HeaderValue::from_str(token).unwrap(); + headers.insert(TOKEN_HEADER, token_val); + } + let host = &url[url::Position::BeforeHost..url::Position::AfterPort]; + let host_val = HeaderValue::from_str(host).unwrap(); + headers.insert("host", host_val); + + let date_str = date.format("%Y%m%dT%H%M%SZ").to_string(); + let date_val = HeaderValue::from_str(&date_str).unwrap(); + headers.insert(DATE_HEADER, date_val); + + let header_digest = HeaderValue::from_str(digest).unwrap(); + headers.insert(HASH_HEADER, header_digest); + + let (signed_headers, canonical_headers) = canonicalize_headers(&headers); + let canonical_query = canonicalize_query(url); + + let scope = self.scope(date); + + let string_to_sign = self.string_to_sign( + date, + &scope, + &method, + &canonical_uri, + &canonical_query, + &canonical_headers, + &signed_headers, + digest, + ); + + let signature = self.credential + .sign(&string_to_sign, date, self.region, self.service); + + url.query_pairs_mut() + .append_pair("X-Amz-Algorithm", ALGORITHM) + .append_pair("X-Amz-Credential", &format!("{}/{}", self.credential.key_id, scope)) + .append_pair("X-Amz-Date", &date_str) + .append_pair("X-Amz-SignedHeaders", "host;x-amz-date") + .append_pair("X-Amz-Signature", &signature); + } + fn string_to_sign( &self, date: DateTime, @@ -697,7 +755,15 @@ mod tests { }; authorizer.authorize(&mut request, None); - assert_eq!(request.headers().get(AUTH_HEADER).unwrap(), "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220806/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=653c3d8ea261fd826207df58bc2bb69fbb5003e9eb3c0ef06e4a51f2a81d8699") + assert_eq!(request.headers().get(AUTH_HEADER).unwrap(), "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20220806/us-east-1/ec2/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=653c3d8ea261fd826207df58bc2bb69fbb5003e9eb3c0ef06e4a51f2a81d8699"); + + let mut url = Url::parse("https://ec2.amazon.com/").unwrap(); + authorizer.sign(Method::GET, &mut url); + assert_eq!(url.query().unwrap(), "X-Amz-Algorithm=AWS4-HMAC-SHA256&\ +X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20220806%2Fus-east-1%2Fec2%2Faws4_request&\ +X-Amz-Date=20220806T180134Z&\ +X-Amz-SignedHeaders=host%3Bx-amz-date&\ +X-Amz-Signature=653c3d8ea261fd826207df58bc2bb69fbb5003e9eb3c0ef06e4a51f2a81d8699"); } #[test]