Unverified Commit 0ce9679c authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Refactor aws-sigv4 for Event Stream (#648)

* Remove serde dependency from aws-sigv4

* Clean up aws-sigv4 `sign` module

* Move HTTP-specific sigv4 code into its own module

* Clean up `http_request` module

* Eliminate serde dependency

* Move bytes to dev-dependencies

* Fix aws-sig-auth build

* Add Event Stream signing

* Update changelog

* Return signature from signing functions and add it to property bag in requests

* CR feedback

* Further simplify parameter sorting
parent 4b488c22
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ vNext (Month Day, Year)
- Add experimental `dvr` module to smithy-client. This will enable easier testing of HTTP traffic. (#640)
- Add profile file credential provider implementation. This implementation currently does not support credential sources
  for assume role providers other than environment variables. (#640)
- Add Event Stream support to aws-sigv4 (#648)
- :bug: Fix name collision that occurred when a model had both a union and a structure named `Result` (#643)
- Add initial implementation of a default provider chain. (#650)
- Update smithy-client to simplify creating HTTP/HTTPS connectors (#650)
+47 −10
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@

use crate::signer::{OperationSigningConfig, RequestConfig, SigV4Signer, SigningError};
use aws_auth::Credentials;
use aws_sigv4::SignableBody;
use aws_sigv4::http_request::SignableBody;
use aws_types::region::SigningRegion;
use aws_types::SigningService;
use smithy_http::middleware::MapRequest;
@@ -14,6 +14,20 @@ use smithy_http::property_bag::PropertyBag;
use std::time::SystemTime;
use thiserror::Error;

/// Container for the request signature for use in the property bag.
#[non_exhaustive]
pub struct Signature(String);

impl Signature {
    pub fn new(signature: String) -> Self {
        Self(signature)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// Middleware stage to sign requests with SigV4
///
/// SigV4RequestSignerStage will load configuration from the request property bag and add
@@ -94,9 +108,11 @@ impl MapRequest for SigV4SigningStage {
        req.augment(|mut req, config| {
            let (operation_config, request_config, creds) = signing_config(config)?;

            self.signer
            let signature = self
                .signer
                .sign(&operation_config, &request_config, &creds, &mut req)
                .map_err(|err| SigningStageError::SigningFailure(err))?;
            config.insert(signature);
            Ok(req)
        })
    }
@@ -104,12 +120,12 @@ impl MapRequest for SigV4SigningStage {

#[cfg(test)]
mod test {
    use crate::middleware::{SigV4SigningStage, SigningStageError};
    use crate::middleware::{SigV4SigningStage, Signature, SigningStageError};
    use crate::signer::{OperationSigningConfig, SigV4Signer};
    use aws_auth::Credentials;
    use aws_endpoint::partition::endpoint::{Protocol, SignatureVersion};
    use aws_endpoint::{set_endpoint_resolver, AwsEndpointStage};
    use aws_types::region::Region;
    use aws_types::region::{Region, SigningRegion};
    use aws_types::SigningService;
    use http::header::AUTHORIZATION;
    use smithy_http::body::SdkBody;
@@ -119,6 +135,30 @@ mod test {
    use std::sync::Arc;
    use std::time::{Duration, UNIX_EPOCH};

    #[test]
    fn places_signature_in_property_bag() {
        let req = http::Request::new(SdkBody::from(""));
        let region = Region::new("us-east-1");
        let req = operation::Request::new(req)
            .augment(|req, properties| {
                properties.insert(region.clone());
                properties.insert(UNIX_EPOCH + Duration::new(1611160427, 0));
                properties.insert(SigningService::from_static("kinesis"));
                properties.insert(OperationSigningConfig::default_config());
                properties.insert(Credentials::from_keys("AKIAfoo", "bar", None));
                properties.insert(SigningRegion::from(region));
                Result::<_, Infallible>::Ok(req)
            })
            .expect("succeeds");

        let signer = SigV4SigningStage::new(SigV4Signer::new());
        let req = signer.apply(req).unwrap();

        let property_bag = req.properties();
        let signature = property_bag.get::<Signature>();
        assert!(signature.is_some());
    }

    // check that the endpoint middleware followed by signing middleware produce the expected result
    #[test]
    fn endpoint_plus_signer() {
@@ -143,12 +183,9 @@ mod test {
        let endpoint = AwsEndpointStage;
        let signer = SigV4SigningStage::new(SigV4Signer::new());
        let mut req = endpoint.apply(req).expect("add endpoint should succeed");
        let mut errs = vec![];
        errs.push(
            signer
        let mut errs = vec![signer
            .apply(req.try_clone().expect("can clone"))
                .expect_err("no signing config"),
        );
            .expect_err("no signing config")];
        let mut config = OperationSigningConfig::default_config();
        config.signing_options.content_sha256_header = true;
        req.properties_mut().insert(config);
+14 −8
Original line number Diff line number Diff line
@@ -4,7 +4,9 @@
 */

use aws_auth::Credentials;
use aws_sigv4::{PayloadChecksumKind, SigningSettings, UriEncoding};
use aws_sigv4::http_request::{
    calculate_signing_headers, PayloadChecksumKind, SigningSettings, UriEncoding,
};
use aws_types::region::SigningRegion;
use aws_types::SigningService;
use http::header::HeaderName;
@@ -13,7 +15,8 @@ use std::error::Error;
use std::fmt;
use std::time::SystemTime;

pub use aws_sigv4::SignableBody;
use crate::middleware::Signature;
pub use aws_sigv4::http_request::SignableBody;

#[derive(Eq, PartialEq, Clone, Copy)]
pub enum SigningAlgorithm {
@@ -114,7 +117,7 @@ impl SigV4Signer {
        request_config: &RequestConfig<'_>,
        credentials: &Credentials,
        request: &mut http::Request<SdkBody>,
    ) -> Result<(), SigningError> {
    ) -> Result<Signature, SigningError> {
        let mut settings = SigningSettings::default();
        settings.uri_encoding = if operation_config.signing_options.double_uri_encode {
            UriEncoding::Double
@@ -126,13 +129,13 @@ impl SigV4Signer {
        } else {
            PayloadChecksumKind::NoHeader
        };
        let sigv4_config = aws_sigv4::Config {
        let sigv4_config = aws_sigv4::http_request::SigningParams {
            access_key: credentials.access_key_id(),
            secret_key: credentials.secret_access_key(),
            security_token: credentials.session_token(),
            region: request_config.region.as_ref(),
            svc: request_config.service.as_ref(),
            date: request_config.request_ts,
            service_name: request_config.service.as_ref(),
            date_time: request_config.request_ts.into(),
            settings,
        };

@@ -150,12 +153,15 @@ impl SigV4Signer {
                    .map(SignableBody::Bytes)
                    .unwrap_or(SignableBody::UnsignedPayload)
            });
        for (key, value) in aws_sigv4::sign_core(request, signable_body, &sigv4_config)? {

        let (signing_headers, signature) =
            calculate_signing_headers(request, signable_body, &sigv4_config)?.into_parts();
        for (key, value) in signing_headers {
            request
                .headers_mut()
                .append(HeaderName::from_static(key), value);
        }

        Ok(())
        Ok(Signature::new(signature))
    }
}
+16 −16
Original line number Diff line number Diff line
[package]
name = "aws-sigv4"
version = "0.1.0"
authors = ["David Barsky <me@davidbarsky.com>"]
authors = ["David Barsky <me@davidbarsky.com>", "AWS Rust SDK Team <aws-sdk-rust@amazon.com>"]
edition = "2018"
exclude = [
    "aws-sig-v4-test-suite/*"
]
exclude = ["aws-sig-v4-test-suite/*"]
license = "MIT OR Apache-2.0"
description = "An AWS SigV4 request signer."
repository = "https://github.com/davidbarsky/sigv4"
homepage = "https://github.com/davidbarsky/sigv4"
documentation = "https://docs.rs/aws-sigv4"
description = "AWS SigV4 signer"

[features]
sign-http = ["http", "http-body", "percent-encoding", "form_urlencoded"]
sign-eventstream = ["smithy-eventstream"]
default = ["sign-http", "sign-eventstream"]

[dependencies]
http = "0.2"
http-body = "0.4"
ring = "0.16"
serde = { version = "1", features = ["derive"] }
serde_urlencoded = "0.7"
bytes = "1"
hex = "0.4"
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
percent-encoding = "2.1"
form_urlencoded = { version = "1.0", optional = true }
hex = "0.4"
http = { version = "0.2", optional = true }
http-body = { version = "0.4", optional = true }
percent-encoding = { version = "2.1", optional = true }
ring = "0.16"
smithy-eventstream = { path = "../../../rust-runtime/smithy-eventstream", optional = true }

[dev-dependencies]
bytes = "1"
pretty_assertions = "0.6"
httparse = "1"
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

// Some of the functions in this file are unused when disabling certain features
#![allow(dead_code)]
use chrono::{Date, DateTime, NaiveDate, NaiveDateTime, ParseError, Utc};

const DATE_TIME_FORMAT: &str = "%Y%m%dT%H%M%SZ";
const DATE_FORMAT: &str = "%Y%m%d";

/// Formats a chrono `Date<Utc>` in `YYYYMMDD` format.
pub fn format_date(date: &Date<Utc>) -> String {
    date.format(DATE_FORMAT).to_string()
}

/// Parses `YYYYMMDD` formatted dates into a chrono `Date<Utc>`.
pub fn parse_date(date_str: &str) -> Result<Date<Utc>, ParseError> {
    Ok(Date::<Utc>::from_utc(
        NaiveDate::parse_from_str(date_str, "%Y%m%d")?,
        Utc,
    ))
}

/// Formats a chrono `DateTime<Utc>` in `YYYYMMDD'T'HHMMSS'Z'` format.
pub fn format_date_time(date_time: &DateTime<Utc>) -> String {
    date_time.format(DATE_TIME_FORMAT).to_string()
}

/// Parses `YYYYMMDD'T'HHMMSS'Z'` formatted dates into a chrono `DateTime<Utc>`.
pub fn parse_date_time(date_time_str: &str) -> Result<DateTime<Utc>, ParseError> {
    Ok(DateTime::<Utc>::from_utc(
        NaiveDateTime::parse_from_str(date_time_str, DATE_TIME_FORMAT)?,
        Utc,
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn date_time_roundtrip() {
        let date = parse_date_time("20150830T123600Z").unwrap();
        assert_eq!("20150830T123600Z", format_date_time(&date));
    }

    #[test]
    fn date_roundtrip() {
        let date = parse_date("20150830").unwrap();
        assert_eq!("20150830", format_date(&date));
    }
}
Loading