Unverified Commit fe711eee authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Support signing requests to S3 (#399)

* Support signing requests to S3

* Refactor to include support for signing streaming bodies (tested)

* Update stale comments

* Remove left-over `dbg!` invocation
parent 84c25865
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -128,9 +128,9 @@ where
            .retry(self.retry_handler.new_handler())
            .layer(ParseResponseLayer::<O, Retry>::new())
            .layer(endpoint_resolver)
            .layer(user_agent)
            .layer(signer)
            // Apply the user agent _after signing_. We should not sign the user-agent header
            .layer(user_agent)
            .layer(DispatchLayer::new())
            .service(inner);
        svc.ready().await?.call(input).await
+1 −1
Original line number Diff line number Diff line
@@ -107,7 +107,7 @@ async fn e2e_test() {
        .header(USER_AGENT, "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0")
        .header("x-amz-user-agent", "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0")
        .header(HOST, "test-service.test-region.amazonaws.com")
        .header(AUTHORIZATION, "AWS4-HMAC-SHA256 Credential=access_key/20210215/test-region/test-service-signing/aws4_request, SignedHeaders=host, Signature=5ebafc2fc4a104b63a1f87ffe829e6eb860a48db8c105a7921b82ee3dc02f1b8")
        .header(AUTHORIZATION, "AWS4-HMAC-SHA256 Credential=access_key/20210215/test-region/test-service-signing/aws4_request, SignedHeaders=host;x-amz-date;x-amz-user-agent, Signature=da249491d7fe3da22c2e09cbf910f37aa5b079a3cedceff8403d0b18a7bfab75")
        .header("x-amz-date", "20210215T184017Z")
        .uri(Uri::from_static("https://test-service.test-region.amazonaws.com/"))
        .body(SdkBody::from("request body")).unwrap();
+1 −1
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ license = "Apache-2.0"
[dependencies]
http = "0.2.2"
# Renaming to clearly indicate that this is not a permanent signing solution
aws-sigv4-poc = { package = "aws-sigv4", git = "https://github.com/rcoh/sigv4", rev = "8faba4281244fc284aba9b67830d6a4c1ed4385a"}
aws-sigv4-poc = { package = "aws-sigv4", git = "https://github.com/rcoh/sigv4", rev = "1854c5f5728c80b0970fcca86c2431bf288f6997"}
aws-auth = { path = "../aws-auth" }
aws-types = { path = "../aws-types" }
smithy-http = { path = "../../../rust-runtime/smithy-http" }
+7 −20
Original line number Diff line number Diff line
@@ -91,27 +91,13 @@ impl MapRequest for SigV4SigningStage {
    type Error = SigningStageError;

    fn apply(&self, req: Request) -> Result<Request, Self::Error> {
        req.augment(|req, config| {
        req.augment(|mut req, config| {
            let (operation_config, request_config, creds) = signing_config(config)?;

            // A short dance is required to extract a signable body from an SdkBody, which
            // amounts to verifying that it a strict body based on a `Bytes` and not a stream.
            // Streams must be signed with a different signing mode. Separate support will be added for
            // this at a later date.
            let (parts, body) = req.into_parts();
            let signable_body = body.bytes().ok_or(SigningStageError::InvalidBodyType)?;
            let mut signable_request = http::Request::from_parts(parts, signable_body);

            self.signer
                .sign(
                    &operation_config,
                    &request_config,
                    &creds,
                    &mut signable_request,
                )
                .sign(&operation_config, &request_config, &creds, &mut req)
                .map_err(|err| SigningStageError::SigningFailure(err))?;
            let (signed_parts, _) = signable_request.into_parts();
            Ok(http::Request::from_parts(signed_parts, body))
            Ok(req)
        })
    }
}
@@ -157,8 +143,9 @@ mod test {
                .apply(req.try_clone().expect("can clone"))
                .expect_err("no signing config"),
        );
        req.config_mut()
            .insert(OperationSigningConfig::default_config());
        let mut config = OperationSigningConfig::default_config();
        config.signing_options.content_sha256_header = true;
        req.config_mut().insert(config);
        errs.push(
            signer
                .apply(req.try_clone().expect("can clone"))
@@ -185,6 +172,6 @@ mod test {
            .headers()
            .get(AUTHORIZATION)
            .expect("auth header must be present");
        assert_eq!(auth_header, "AWS4-HMAC-SHA256 Credential=AKIAfoo/20210120/us-east-1/kinesis/aws4_request, SignedHeaders=host, Signature=c59f1b9040fe229bf924254d9ad71adaf0495db2ccda5eb6b1565529cdc2c120");
        assert_eq!(auth_header, "AWS4-HMAC-SHA256 Credential=AKIAfoo/20210120/us-east-1/kinesis/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=af71a409f0229dfd6e88409cd1b11f5c2803868d6869888e53bbf9ee12a97ea0");
    }
}
+33 −17
Original line number Diff line number Diff line
@@ -4,9 +4,11 @@
 */

use aws_auth::Credentials;
use aws_sigv4_poc::{SigningSettings, UriEncoding};
use aws_sigv4_poc::{SignableBody, SignedBodyHeaderType, SigningSettings, UriEncoding};
use aws_types::region::SigningRegion;
use aws_types::SigningService;
use http::header::HeaderName;
use smithy_http::body::SdkBody;
use std::error::Error;
use std::time::SystemTime;

@@ -48,6 +50,7 @@ impl OperationSigningConfig {
            signature_type: HttpSignatureType::HttpRequestHeaders,
            signing_options: SigningOptions {
                double_uri_encode: true,
                content_sha256_header: false,
            },
        }
    }
@@ -57,6 +60,7 @@ impl OperationSigningConfig {
#[non_exhaustive]
pub struct SigningOptions {
    pub double_uri_encode: bool,
    pub content_sha256_header: bool,
    /*
    Currently unsupported:
    pub normalize_uri_path: bool,
@@ -93,17 +97,24 @@ impl SigV4Signer {
    ///
    /// Although the direct signing implementation MAY be used directly. End users will not typically
    /// interact with this code. It is generally used via middleware in the request pipeline. See [`SigV4SigningStage`](crate::middleware::SigV4SigningStage).
    pub fn sign<B>(
    pub fn sign(
        &self,
        // There is currently only 1 way to sign, so operation level configuration is unused
        operation_config: &OperationSigningConfig,
        request_config: &RequestConfig<'_>,
        credentials: &Credentials,
        request: &mut http::Request<B>,
    ) -> Result<(), SigningError>
    where
        B: AsRef<[u8]>,
    {
        request: &mut http::Request<SdkBody>,
    ) -> Result<(), SigningError> {
        let mut settings = SigningSettings::default();
        settings.uri_encoding = if operation_config.signing_options.double_uri_encode {
            UriEncoding::Double
        } else {
            UriEncoding::Single
        };
        settings.signed_body_header = if operation_config.signing_options.content_sha256_header {
            SignedBodyHeaderType::XAmzSha256
        } else {
            SignedBodyHeaderType::NoHeader
        };
        let sigv4_config = aws_sigv4_poc::Config {
            access_key: credentials.access_key_id(),
            secret_key: credentials.secret_access_key(),
@@ -111,18 +122,23 @@ impl SigV4Signer {
            region: request_config.region.as_ref(),
            svc: request_config.service.as_ref(),
            date: request_config.request_ts,
            settings: SigningSettings {
                uri_encoding: if operation_config.signing_options.double_uri_encode {
                    UriEncoding::Double
                } else {
                    UriEncoding::Single
                },
            },
            settings,
        };
        for (key, value) in aws_sigv4_poc::sign_core(request, sigv4_config) {

        // A body that is already in memory can be signed directly. A  body that is not in memory
        // (any sort of streaming body) will be signed via UNSIGNED-PAYLOAD.
        // The final enhancement that will come a bit later is writing a `SignableBody::Precomputed`
        // into the property bag when we have a sha 256 middleware that can compute a streaming checksum
        // for replayable streams but currently even replayable streams will result in `UNSIGNED-PAYLOAD`
        let signable_body = request
            .body()
            .bytes()
            .map(SignableBody::Bytes)
            .unwrap_or(SignableBody::UnsignedPayload);
        for (key, value) in aws_sigv4_poc::sign_core(request, signable_body, &sigv4_config)? {
            request
                .headers_mut()
                .append(key.header_name(), value.parse()?);
                .append(HeaderName::from_static(key), value);
        }

        Ok(())
Loading