Use `Identity` instead of `Credentials` in signing code (#2913)

This PR replaces the access_key, secret_key, and session token fields of
signing params with the Orchestrator's `Identity` type.

## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the
smithy-rs codegen or runtime crates
- [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS
SDK, generated SDK code, or SDK runtime crates

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
This commit is contained in:
Zelda Hessler 2023-08-18 09:51:03 -05:00 committed by GitHub
parent 87d601b456
commit 39c6476f31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 332 additions and 252 deletions

View File

@ -51,3 +51,12 @@ message = "In sigV4-related code, rename 'signing service' to 'signing name'. Th
references = ["smithy-rs#2911"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "Velfi"
[[aws-sdk-rust]]
message = """
All versions of SigningParams have been updated to contain an [`Identity`](https://docs.rs/aws-smithy-runtime-api/latest/aws_smithy_runtime_api/client/identity/struct.Identity.html)
as opposed to AWS credentials in `&str` form. [Read more](https://github.com/awslabs/aws-sdk-rust/discussions/868).
"""
references = ["smithy-rs#2913"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "Velfi"

View File

@ -14,6 +14,7 @@ test-util = []
[dependencies]
aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" }
aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" }
aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] }
fastrand = "2.0.0"
tokio = { version = "1.23.1", features = ["sync"] }
tracing = "0.1"
@ -28,9 +29,6 @@ env_logger = "0.9.0"
tokio = { version = "1.23.1", features = ["full", "test-util", "rt"] }
tracing-test = "0.2.4"
# TODO(https://github.com/awslabs/smithy-rs/issues/2619): Remove this
# workaround once the fixed is upstreamed.
regex = { version = "1.0", features = ["unicode-case", "unicode-perl"] }
[package.metadata.docs.rs]
all-features = true

View File

@ -10,6 +10,8 @@ use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use zeroize::Zeroizing;
use aws_smithy_runtime_api::client::identity::Identity;
/// AWS SDK Credentials
///
/// An opaque struct representing credentials that may be used in an AWS SDK, modeled on
@ -140,18 +142,6 @@ impl Credentials {
)
}
/// Creates a test `Credentials`.
#[cfg(feature = "test-util")]
pub fn for_tests() -> Self {
Self::new(
"ANOTREAL",
"notrealrnrELgWzOk3IfjzDKtFBhDby",
Some("notarealsessiontoken".to_string()),
None,
"test",
)
}
/// Returns the access key ID.
pub fn access_key_id(&self) -> &str {
&self.0.access_key_id
@ -178,6 +168,38 @@ impl Credentials {
}
}
#[cfg(feature = "test-util")]
impl Credentials {
/// Creates a test `Credentials` with no session token.
pub fn for_tests() -> Self {
Self::new(
"ANOTREAL",
"notrealrnrELgWzOk3IfjzDKtFBhDby",
None,
None,
"test",
)
}
/// Creates a test `Credentials` that include a session token.
pub fn for_tests_with_session_token() -> Self {
Self::new(
"ANOTREAL",
"notrealrnrELgWzOk3IfjzDKtFBhDby",
Some("notarealsessiontoken".to_string()),
None,
"test",
)
}
}
impl From<Credentials> for Identity {
fn from(val: Credentials) -> Self {
let expiry = val.expiry();
Identity::new(val, expiry)
}
}
#[cfg(test)]
mod test {
use crate::Credentials;

View File

@ -89,8 +89,9 @@ fn test_operation() -> Operation<TestOperationParser, AwsResponseRetryClassifier
));
aws_http::auth::set_credentials_cache(
conf,
CredentialsCache::lazy()
.create_cache(SharedCredentialsProvider::new(Credentials::for_tests())),
CredentialsCache::lazy().create_cache(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
)),
);
conf.insert(SigningRegion::from_static("test-region"));
conf.insert(OperationSigningConfig::default_config());

View File

@ -217,12 +217,16 @@ impl SigV4Signer {
fn signing_params<'a>(
settings: SigningSettings,
credentials: &'a Credentials,
identity: &'a Identity,
operation_config: &'a SigV4OperationSigningConfig,
request_timestamp: SystemTime,
) -> Result<SigningParams<'a>, SigV4SigningError> {
let creds = identity
.data::<Credentials>()
.ok_or_else(|| SigV4SigningError::WrongIdentityType(identity.clone()))?;
if let Some(expires_in) = settings.expires_in {
if let Some(creds_expires_time) = credentials.expiry() {
if let Some(creds_expires_time) = creds.expiry() {
let presigned_expires_time = request_timestamp + expires_in;
if presigned_expires_time > creds_expires_time {
tracing::warn!(EXPIRATION_WARNING);
@ -230,9 +234,8 @@ impl SigV4Signer {
}
}
let mut builder = SigningParams::builder()
.access_key(credentials.access_key_id())
.secret_key(credentials.secret_access_key())
let builder = SigningParams::builder()
.identity(identity)
.region(
operation_config
.region
@ -249,7 +252,6 @@ impl SigV4Signer {
)
.time(request_timestamp)
.settings(settings);
builder.set_security_token(credentials.session_token());
Ok(builder.build().expect("all required fields set"))
}
@ -324,18 +326,18 @@ impl Signer for SigV4Signer {
Self::extract_operation_config(auth_scheme_endpoint_config, config_bag)?;
let request_time = runtime_components.time_source().unwrap_or_default().now();
let credentials = if let Some(creds) = identity.data::<Credentials>() {
creds
} else if operation_config.signing_options.signing_optional {
tracing::debug!("skipped SigV4 signing since signing is optional for this operation and there are no credentials");
return Ok(());
} else {
return Err(SigV4SigningError::WrongIdentityType(identity.clone()).into());
if identity.data::<Credentials>().is_none() {
if operation_config.signing_options.signing_optional {
tracing::debug!("skipped SigV4 signing since signing is optional for this operation and there are no credentials");
return Ok(());
} else {
return Err(SigV4SigningError::WrongIdentityType(identity.clone()).into());
}
};
let settings = Self::settings(&operation_config);
let signing_params =
Self::signing_params(settings, credentials, &operation_config, request_time)?;
Self::signing_params(settings, identity, &operation_config, request_time)?;
let (signing_instructions, _signature) = {
// A body that is already in memory can be signed directly. A body that is not in memory
@ -382,7 +384,7 @@ impl Signer for SigV4Signer {
signer_sender
.send(Box::new(SigV4MessageSigner::new(
_signature,
credentials.clone(),
identity.clone(),
Region::new(signing_params.region().to_string()).into(),
signing_params.name().to_string().into(),
time_source,
@ -397,11 +399,11 @@ impl Signer for SigV4Signer {
#[cfg(feature = "event-stream")]
mod event_stream {
use aws_credential_types::Credentials;
use aws_sigv4::event_stream::{sign_empty_message, sign_message};
use aws_sigv4::SigningParams;
use aws_smithy_async::time::SharedTimeSource;
use aws_smithy_eventstream::frame::{Message, SignMessage, SignMessageError};
use aws_smithy_runtime_api::client::identity::Identity;
use aws_types::region::SigningRegion;
use aws_types::SigningName;
@ -409,7 +411,7 @@ mod event_stream {
#[derive(Debug)]
pub(super) struct SigV4MessageSigner {
last_signature: String,
credentials: Credentials,
identity: Identity,
signing_region: SigningRegion,
signing_name: SigningName,
time: SharedTimeSource,
@ -418,14 +420,14 @@ mod event_stream {
impl SigV4MessageSigner {
pub(super) fn new(
last_signature: String,
credentials: Credentials,
identity: Identity,
signing_region: SigningRegion,
signing_name: SigningName,
time: SharedTimeSource,
) -> Self {
Self {
last_signature,
credentials,
identity,
signing_region,
signing_name,
time,
@ -433,14 +435,12 @@ mod event_stream {
}
fn signing_params(&self) -> SigningParams<'_, ()> {
let mut builder = SigningParams::builder()
.access_key(self.credentials.access_key_id())
.secret_key(self.credentials.secret_access_key())
let builder = SigningParams::builder()
.identity(&self.identity)
.region(self.signing_region.as_ref())
.name(self.signing_name.as_ref())
.time(self.time.now())
.settings(());
builder.set_security_token(self.credentials.session_token());
builder.build().unwrap()
}
}
@ -467,9 +467,11 @@ mod event_stream {
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::sigv4::event_stream::SigV4MessageSigner;
use aws_credential_types::Credentials;
use aws_smithy_async::time::SharedTimeSource;
use aws_smithy_eventstream::frame::{HeaderValue, Message, SignMessage};
use aws_types::region::Region;
use aws_types::region::SigningRegion;
use aws_types::SigningName;
@ -484,7 +486,7 @@ mod event_stream {
let region = Region::new("us-east-1");
let mut signer = check_send_sync(SigV4MessageSigner::new(
"initial-signature".into(),
Credentials::for_tests(),
Credentials::for_tests_with_session_token().into(),
SigningRegion::from(region),
SigningName::from_static("transcribe"),
SharedTimeSource::new(UNIX_EPOCH + Duration::new(1611160427, 0)),
@ -534,13 +536,14 @@ mod tests {
let mut settings = SigningSettings::default();
settings.expires_in = Some(creds_expire_in - Duration::from_secs(10));
let credentials = Credentials::new(
let identity = Credentials::new(
"test-access-key",
"test-secret-key",
Some("test-session-token".into()),
Some(now + creds_expire_in),
"test",
);
)
.into();
let operation_config = SigV4OperationSigningConfig {
region: Some(SigningRegion::from_static("test")),
service: Some(SigningName::from_static("test")),
@ -555,13 +558,13 @@ mod tests {
payload_override: None,
},
};
SigV4Signer::signing_params(settings, &credentials, &operation_config, now).unwrap();
SigV4Signer::signing_params(settings, &identity, &operation_config, now).unwrap();
assert!(!logs_contain(EXPIRATION_WARNING));
let mut settings = SigningSettings::default();
settings.expires_in = Some(creds_expire_in + Duration::from_secs(10));
SigV4Signer::signing_params(settings, &credentials, &operation_config, now).unwrap();
SigV4Signer::signing_params(settings, &identity, &operation_config, now).unwrap();
assert!(logs_contain(EXPIRATION_WARNING));
}

View File

@ -17,6 +17,7 @@ aws-sigv4 = { path = "../aws-sigv4", features = ["http0-compat"] }
aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true }
aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" }
aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" }
aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api" }
aws-types = { path = "../aws-types" }
http = "0.2.2"
tracing = "0.1"

View File

@ -5,6 +5,7 @@ allowed_external_types = [
"aws_smithy_http::*",
"aws_types::*",
"http::request::Request",
"aws_smithy_runtime_api::client::identity::Identity",
# TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `event-stream` feature
"aws_smithy_eventstream::frame::SignMessage",

View File

@ -8,11 +8,11 @@
#![allow(clippy::disallowed_methods)]
use crate::middleware::Signature;
use aws_credential_types::Credentials;
use aws_sigv4::event_stream::{sign_empty_message, sign_message};
use aws_sigv4::SigningParams;
use aws_smithy_eventstream::frame::{Message, SignMessage, SignMessageError};
use aws_smithy_http::property_bag::{PropertyBag, SharedPropertyBag};
use aws_smithy_runtime_api::client::identity::Identity;
use aws_types::region::SigningRegion;
use aws_types::SigningName;
use std::time::SystemTime;
@ -21,7 +21,7 @@ use std::time::SystemTime;
#[derive(Debug)]
pub struct SigV4MessageSigner {
last_signature: String,
credentials: Credentials,
identity: Identity,
signing_region: SigningRegion,
signing_name: SigningName,
time: Option<SystemTime>,
@ -30,14 +30,14 @@ pub struct SigV4MessageSigner {
impl SigV4MessageSigner {
pub fn new(
last_signature: String,
credentials: Credentials,
identity: Identity,
signing_region: SigningRegion,
signing_name: SigningName,
time: Option<SystemTime>,
) -> Self {
Self {
last_signature,
credentials,
identity,
signing_region,
signing_name,
time,
@ -45,14 +45,12 @@ impl SigV4MessageSigner {
}
fn signing_params(&self) -> SigningParams<()> {
let mut builder = SigningParams::builder()
.access_key(self.credentials.access_key_id())
.secret_key(self.credentials.secret_access_key())
let builder = SigningParams::builder()
.identity(&self.identity)
.region(self.signing_region.as_ref())
.name(self.signing_name.as_ref())
.time(self.time.unwrap_or_else(SystemTime::now))
.settings(());
builder.set_security_token(self.credentials.session_token());
builder.build().unwrap()
}
}
@ -82,6 +80,7 @@ mod tests {
use crate::event_stream::SigV4MessageSigner;
use aws_credential_types::Credentials;
use aws_smithy_eventstream::frame::{HeaderValue, Message, SignMessage};
use aws_types::region::Region;
use aws_types::region::SigningRegion;
use aws_types::SigningName;
@ -96,7 +95,7 @@ mod tests {
let region = Region::new("us-east-1");
let mut signer = check_send_sync(SigV4MessageSigner::new(
"initial-signature".into(),
Credentials::for_tests(),
Credentials::for_tests_with_session_token().into(),
SigningRegion::from(region),
SigningName::from_static("transcribe"),
Some(UNIX_EPOCH + Duration::new(1611160427, 0)),
@ -144,21 +143,19 @@ impl SigV4Signer {
fn signing_params(properties: &PropertyBag) -> SigningParams<()> {
// Every single one of these values would have been retrieved during the initial request,
// so we can safely assume they all exist in the property bag at this point.
let credentials = properties.get::<Credentials>().unwrap();
let identity = properties.get::<Identity>().unwrap();
let region = properties.get::<SigningRegion>().unwrap();
let name = properties.get::<SigningName>().unwrap();
let time = properties
.get::<SystemTime>()
.copied()
.unwrap_or_else(SystemTime::now);
let mut builder = SigningParams::builder()
.access_key(credentials.access_key_id())
.secret_key(credentials.secret_access_key())
let builder = SigningParams::builder()
.identity(identity)
.region(region.as_ref())
.name(name.as_ref())
.time(time)
.settings(());
builder.set_security_token(credentials.session_token());
builder.build().unwrap()
}
}
@ -208,6 +205,7 @@ mod old_tests {
use aws_credential_types::Credentials;
use aws_smithy_eventstream::frame::{HeaderValue, Message, SignMessage};
use aws_smithy_http::property_bag::PropertyBag;
use aws_smithy_runtime_api::client::identity::Identity;
use aws_types::region::Region;
use aws_types::region::SigningRegion;
use aws_types::SigningName;
@ -219,8 +217,8 @@ mod old_tests {
let mut properties = PropertyBag::new();
properties.insert(region.clone());
properties.insert(UNIX_EPOCH + Duration::new(1611160427, 0));
properties.insert::<Identity>(Credentials::for_tests_with_session_token().into());
properties.insert(SigningName::from_static("transcribe"));
properties.insert(Credentials::for_tests());
properties.insert(SigningRegion::from(region));
properties.insert(Signature::new("initial-signature".into()));

View File

@ -22,13 +22,14 @@
//! use aws_types::region::{Region, SigningRegion};
//! use std::time::{Duration, SystemTime, UNIX_EPOCH};
//! use aws_sig_auth::signer::{self, SigningError, OperationSigningConfig, HttpSignatureType, RequestConfig};
//! use aws_smithy_runtime_api::client::identity::Identity;
//!
//! fn generate_rds_iam_token(
//! db_hostname: &str,
//! region: Region,
//! port: u16,
//! db_username: &str,
//! credentials: &Credentials,
//! identity: &Identity,
//! timestamp: SystemTime,
//! ) -> Result<String, SigningError> {
//! let signer = signer::SigV4Signer::new();
@ -53,7 +54,7 @@
//! let _signature = signer.sign(
//! &operation_config,
//! &request_config,
//! &credentials,
//! identity,
//! &mut request,
//! )?;
//! let mut uri = request.uri().to_string();
@ -62,14 +63,14 @@
//! Ok(uri)
//! }
//!
//! // You will need to get `credentials` from a credentials provider ahead of time
//! # let credentials = Credentials::new("AKIDEXAMPLE", "secret", None, None, "example");
//! // You will need to get an `identity` from a credentials provider ahead of time
//! # let identity = Credentials::new("AKIDEXAMPLE", "secret", None, None, "example").into();
//! let token = generate_rds_iam_token(
//! "prod-instance.us-east-1.rds.amazonaws.com",
//! Region::from_static("us-east-1"),
//! 3306,
//! "dbuser",
//! &credentials,
//! &identity,
//! // this value is hard coded to create deterministic signature for tests. Generally,
//! // `SystemTime::now()` should be used
//! UNIX_EPOCH + Duration::from_secs(1635257380)
@ -88,6 +89,7 @@
//! use aws_types::SigningName;
//! use std::error::Error;
//! use std::time::SystemTime;
//! use aws_smithy_runtime_api::client::identity::Identity;
//! async fn sign_request(
//! mut request: &mut http::Request<SdkBody>,
//! region: Region,
@ -101,10 +103,11 @@
//! name: &SigningName::from_static("execute-api"),
//! payload_override: None,
//! };
//! let identity = credentials_provider.provide_credentials().await?.into();
//! signer.sign(
//! &OperationSigningConfig::default_config(),
//! &request_config,
//! &credentials_provider.provide_credentials().await?,
//! &identity,
//! &mut request,
//! )?;
//! Ok((()))

View File

@ -49,10 +49,10 @@ impl AsRef<str> for Signature {
/// a signature.
///
/// Prior to signing, the following fields MUST be present in the property bag:
/// - [`SigningRegion`](SigningRegion): The region used when signing the request, e.g. `us-east-1`
/// - [`SigningName`](SigningName): The name of the service to use when signing the request, e.g. `dynamodb`
/// - [`Credentials`](Credentials): Credentials to sign with
/// - [`OperationSigningConfig`](OperationSigningConfig): Operation specific signing configuration, e.g.
/// - [`SigningRegion`]: The region used when signing the request, e.g. `us-east-1`
/// - [`SigningName`]: The name of the service to use when signing the request, e.g. `dynamodb`
/// - [`Credentials`]: Credentials to sign with
/// - [`OperationSigningConfig`]: Operation specific signing configuration, e.g.
/// changes to URL encoding behavior, or headers that must be omitted.
/// - [`SharedTimeSource`]: The time source to use when signing the request.
/// If any of these fields are missing, the middleware will return an error.
@ -180,10 +180,11 @@ impl MapRequest for SigV4SigningStage {
},
SigningRequirements::Required => signing_config(config)?,
};
let identity = creds.into();
let signature = self
.signer
.sign(operation_config, &request_config, &creds, &mut req)
.sign(operation_config, &request_config, &identity, &mut req)
.map_err(SigningStageErrorKind::SigningFailure)?;
// If this is an event stream operation, set up the event stream signer
@ -193,7 +194,7 @@ impl MapRequest for SigV4SigningStage {
signer_sender
.send(Box::new(EventStreamSigV4Signer::new(
signature.as_ref().into(),
creds,
identity,
request_config.region.clone(),
request_config.name.clone(),
time_override,
@ -220,6 +221,7 @@ mod test {
use aws_credential_types::Credentials;
use aws_endpoint::AwsAuthStage;
use aws_smithy_async::time::SharedTimeSource;
use aws_types::region::{Region, SigningRegion};
use aws_types::SigningName;
@ -241,7 +243,7 @@ mod test {
properties.insert(UNIX_EPOCH + Duration::new(1611160427, 0));
properties.insert(SigningName::from_static("kinesis"));
properties.insert(OperationSigningConfig::default_config());
properties.insert(Credentials::for_tests());
properties.insert(Credentials::for_tests_with_session_token());
properties.insert(SigningRegion::from(region));
Result::<_, Infallible>::Ok(req)
})
@ -275,7 +277,7 @@ mod test {
));
properties.insert(SigningName::from_static("kinesis"));
properties.insert(OperationSigningConfig::default_config());
properties.insert(Credentials::for_tests());
properties.insert(Credentials::for_tests_with_session_token());
properties.insert(SigningRegion::from(region.clone()));
properties.insert(deferred_signer_sender);
Result::<_, Infallible>::Ok(req)
@ -288,7 +290,7 @@ mod test {
let mut signer_for_comparison = EventStreamSigV4Signer::new(
// This is the expected SigV4 signature for the HTTP request above
"abac477b4afabf5651079e7b9a0aa6a1a3e356a7418a81d974cdae9d4c8e5441".into(),
Credentials::for_tests(),
Credentials::for_tests_with_session_token().into(),
SigningRegion::from(region),
SigningName::from_static("kinesis"),
Some(UNIX_EPOCH + Duration::new(1611160427, 0)),
@ -337,7 +339,8 @@ mod test {
.apply(req.try_clone().expect("can clone"))
.expect_err("no cred provider"),
);
req.properties_mut().insert(Credentials::for_tests());
req.properties_mut()
.insert(Credentials::for_tests_with_session_token());
let req = signer.apply(req).expect("signing succeeded");
// make sure we got the correct error types in any order
assert!(errs.iter().all(|el| matches!(

View File

@ -3,20 +3,19 @@
* SPDX-License-Identifier: Apache-2.0
*/
use aws_credential_types::Credentials;
use crate::middleware::Signature;
use aws_sigv4::http_request::{
sign, PayloadChecksumKind, PercentEncodingMode, SessionTokenMode, SignableRequest,
SignatureLocation, SigningParams, SigningSettings, UriPathNormalizationMode,
};
use aws_smithy_http::body::SdkBody;
use aws_smithy_runtime_api::client::identity::Identity;
use aws_types::region::SigningRegion;
use aws_types::SigningName;
use std::fmt;
use std::time::{Duration, SystemTime};
use crate::middleware::Signature;
pub use aws_sigv4::http_request::SignableBody;
pub type SigningError = aws_sigv4::http_request::SigningError;
const EXPIRATION_WARNING: &str = "Presigned request will expire before the given \
@ -159,11 +158,11 @@ impl SigV4Signer {
fn signing_params<'a>(
settings: SigningSettings,
credentials: &'a Credentials,
identity: &'a Identity,
request_config: &'a RequestConfig<'a>,
) -> SigningParams<'a> {
if let Some(expires_in) = settings.expires_in {
if let Some(creds_expires_time) = credentials.expiry() {
if let Some(creds_expires_time) = identity.expiration().cloned() {
let presigned_expires_time = request_config.request_ts + expires_in;
if presigned_expires_time > creds_expires_time {
tracing::warn!(EXPIRATION_WARNING);
@ -171,14 +170,12 @@ impl SigV4Signer {
}
}
let mut builder = SigningParams::builder()
.access_key(credentials.access_key_id())
.secret_key(credentials.secret_access_key())
let builder = SigningParams::builder()
.identity(identity)
.region(request_config.region.as_ref())
.name(request_config.name.as_ref())
.time(request_config.request_ts)
.settings(settings);
builder.set_security_token(credentials.session_token());
builder.build().expect("all required fields set")
}
@ -190,11 +187,11 @@ impl SigV4Signer {
&self,
operation_config: &OperationSigningConfig,
request_config: &RequestConfig<'_>,
credentials: &Credentials,
identity: &Identity,
request: &mut http::Request<SdkBody>,
) -> Result<Signature, SigningError> {
let settings = Self::settings(operation_config);
let signing_params = Self::signing_params(settings, credentials, request_config);
let signing_params = Self::signing_params(settings, identity, request_config);
let (signing_instructions, signature) = {
// A body that is already in memory can be signed directly. A body that is not in memory
@ -239,6 +236,7 @@ mod tests {
use super::{RequestConfig, SigV4Signer, EXPIRATION_WARNING};
use aws_credential_types::Credentials;
use aws_sigv4::http_request::SigningSettings;
use aws_types::region::SigningRegion;
use aws_types::SigningName;
use std::time::{Duration, SystemTime};
@ -253,26 +251,27 @@ mod tests {
let mut settings = SigningSettings::default();
settings.expires_in = Some(creds_expire_in - Duration::from_secs(10));
let credentials = Credentials::new(
let identity = Credentials::new(
"test-access-key",
"test-secret-key",
Some("test-session-token".into()),
Some(now + creds_expire_in),
"test",
);
)
.into();
let request_config = RequestConfig {
request_ts: now,
region: &SigningRegion::from_static("test"),
name: &SigningName::from_static("test"),
payload_override: None,
};
SigV4Signer::signing_params(settings, &credentials, &request_config);
SigV4Signer::signing_params(settings, &identity, &request_config);
assert!(!logs_contain(EXPIRATION_WARNING));
let mut settings = SigningSettings::default();
settings.expires_in = Some(creds_expire_in + Duration::from_secs(10));
SigV4Signer::signing_params(settings, &credentials, &request_config);
SigV4Signer::signing_params(settings, &identity, &request_config);
assert!(logs_contain(EXPIRATION_WARNING));
}
}

View File

@ -17,6 +17,8 @@ default = ["sign-http"]
[dependencies]
aws-smithy-eventstream = { path = "../../../rust-runtime/aws-smithy-eventstream", optional = true }
aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" }
aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] }
aws-credential-types = { path = "../aws-credential-types" }
bytes = { version = "1", optional = true }
form_urlencoded = { version = "1.0", optional = true }
hex = "0.4"
@ -30,6 +32,8 @@ hmac = "0.12"
sha2 = "0.10"
[dev-dependencies]
aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client", "test-util"] }
aws-credential-types = { path = "../aws-credential-types", features = ["test-util"] }
criterion = "0.4"
bytes = "1"
httparse = "1.5"

View File

@ -1,4 +1,4 @@
POST /test/@connections/JBDvjfGEIAMCERw%3D HTTP/1.1
X-amz-date:20150830T123600Z
Authorization:AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=6f871eb157f326fa5f7439eb88ca200048635950ce7d6037deda56f0c95d4364
Authorization:AWS4-HMAC-SHA256 Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=57d157672191bac40bae387e48bbe14b15303c001fdbb01f4abf295dccb09705
Host:tj9n5r0m12.execute-api.us-east-1.amazonaws.com

View File

@ -1,2 +1,2 @@
GET /?Param2=value2&Param1=value1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIDEXAMPLE%2F20150830%2Fus-east-1%2Fservice%2Faws4_request&X-Amz-Date=20150830T123600Z&X-Amz-Expires=35&X-Amz-SignedHeaders=host&X-Amz-Signature=f25aea20f8c722ece3b363fc5d60cc91add973f9b64c42ba36fa28d57afe9019 HTTP/1.1
GET /?Param2=value2&Param1=value1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ANOTREAL%2F20150830%2Fus-east-1%2Fservice%2Faws4_request&X-Amz-Date=20150830T123600Z&X-Amz-Expires=35&X-Amz-SignedHeaders=host&X-Amz-Signature=ecce208e4b4f7d7e3a4cc22ced6acc2ad1d170ee8ba87d7165f6fa4b9aff09ab HTTP/1.1
Host:example.amazonaws.com

View File

@ -1,4 +1,4 @@
GET /?Param2=value2&Param1=value1 HTTP/1.1
Host:example.amazonaws.com
X-Amz-Date:20150830T123600Z
Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500
Authorization: AWS4-HMAC-SHA256 Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5557820e7380d585310524bd93d51a08d7757fb5efd7344ee12088f2b0860947

View File

@ -3,4 +3,5 @@ allowed_external_types = [
"http::request::Request",
# TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `event-stream` feature
"aws_smithy_eventstream::frame::Message",
"aws_smithy_runtime_api::client::identity::Identity"
]

View File

@ -11,6 +11,8 @@
//! use aws_sigv4::event_stream::{sign_message, SigningParams};
//! use aws_smithy_eventstream::frame::{Header, HeaderValue, Message};
//! use std::time::SystemTime;
//! use aws_credential_types::Credentials;
//! use aws_smithy_runtime_api::client::identity::Identity;
//!
//! // The `last_signature` argument is the previous message's signature, or
//! // the signature of the initial HTTP request if a message hasn't been signed yet.
@ -21,9 +23,15 @@
//! HeaderValue::String("value".into()),
//! ));
//!
//! let identity = Credentials::new(
//! "AKIDEXAMPLE",
//! "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
//! None,
//! None,
//! "hardcoded-credentials"
//! ).into();
//! let params = SigningParams::builder()
//! .access_key("example access key")
//! .secret_key("example secret key")
//! .identity(&identity)
//! .region("us-east-1")
//! .name("exampleservice")
//! .time(SystemTime::now())
@ -113,11 +121,13 @@ fn sign_payload<'a>(
last_signature: &'a str,
params: &'a SigningParams<'a>,
) -> SigningOutput<Message> {
let creds = params.credentials().expect("AWS credentials are required");
// Truncate the sub-seconds up front since the timestamp written to the signed message header
// needs to exactly match the string formatted timestamp, which doesn't include sub-seconds.
let time = truncate_subsecs(params.time);
let signing_key = generate_signing_key(params.secret_key, time, params.region, params.name);
let signing_key =
generate_signing_key(creds.secret_access_key(), time, params.region, params.name);
let string_to_sign = calculate_string_to_sign(
message_payload.as_ref().map(|v| &v[..]).unwrap_or(&[]),
last_signature,
@ -141,7 +151,11 @@ fn sign_payload<'a>(
#[cfg(test)]
mod tests {
use super::*;
use crate::event_stream::{calculate_string_to_sign, sign_message};
use crate::sign::sha256_hex_string;
use crate::SigningParams;
use aws_credential_types::Credentials;
use aws_smithy_eventstream::frame::{Header, HeaderValue, Message};
use std::time::{Duration, UNIX_EPOCH};
#[test]
@ -154,9 +168,7 @@ mod tests {
message_to_sign.write_to(&mut message_payload).unwrap();
let params = SigningParams {
access_key: "fake access key",
secret_key: "fake secret key",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "testservice",
time: (UNIX_EPOCH + Duration::new(123_456_789_u64, 1234u32)),
@ -192,9 +204,7 @@ mod tests {
HeaderValue::String("value".into()),
));
let params = SigningParams {
access_key: "fake access key",
secret_key: "fake secret key",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "testservice",
time: (UNIX_EPOCH + Duration::new(123_456_789_u64, 1234u32)),

View File

@ -10,6 +10,8 @@
//! **Note**: This requires `http0-compat` to be enabled.
//!
//! ```rust
//! # use aws_credential_types::Credentials;
//! use aws_smithy_runtime_api::client::identity::Identity;
//! # use aws_sigv4::http_request::SignableBody;
//! #[cfg(feature = "http0-compat")]
//! fn test() -> Result<(), aws_sigv4::http_request::SigningError> {
@ -18,10 +20,16 @@
//! use std::time::SystemTime;
//!
//! // Set up information and settings for the signing
//! let identity = Credentials::new(
//! "AKIDEXAMPLE",
//! "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
//! None,
//! None,
//! "hardcoded-credentials"
//! ).into();
//! let signing_settings = SigningSettings::default();
//! let signing_params = SigningParams::builder()
//! .access_key("example access key")
//! .secret_key("example secret key")
//! .identity(&identity)
//! .region("us-east-1")
//! .name("exampleservice")
//! .time(SystemTime::now())

View File

@ -20,7 +20,6 @@ use std::borrow::Cow;
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt;
use std::fmt::Formatter;
use std::str::FromStr;
use std::time::SystemTime;
@ -131,6 +130,9 @@ impl<'a> CanonicalRequest<'a> {
req: &'b SignableRequest<'b>,
params: &'b SigningParams<'b>,
) -> Result<CanonicalRequest<'b>, CanonicalRequestError> {
let creds = params
.credentials()
.ok_or_else(CanonicalRequestError::unsupported_credential_type)?;
// Path encoding: if specified, re-encode % as %25
// Set method and path into CanonicalRequest
let path = req.uri().path();
@ -151,7 +153,7 @@ impl<'a> CanonicalRequest<'a> {
let signed_headers = SignedHeaders::new(signed_headers);
let security_token = match params.settings.session_token_mode {
SessionTokenMode::Include => params.security_token,
SessionTokenMode::Include => creds.session_token(),
SessionTokenMode::Exclude => None,
};
@ -167,7 +169,7 @@ impl<'a> CanonicalRequest<'a> {
content_sha256: payload_hash,
credential: format!(
"{}/{}/{}/{}/aws4_request",
params.access_key,
creds.access_key_id(),
format_date(params.time),
params.region,
params.name,
@ -200,6 +202,9 @@ impl<'a> CanonicalRequest<'a> {
payload_hash: &str,
date_time: &str,
) -> Result<(Vec<CanonicalHeaderName>, HeaderMap), CanonicalRequestError> {
let creds = params
.credentials()
.ok_or_else(CanonicalRequestError::unsupported_credential_type)?;
// Header computation:
// The canonical request will include headers not present in the input. We need to clone and
// normalize the headers from the original request and add:
@ -222,7 +227,7 @@ impl<'a> CanonicalRequest<'a> {
if params.settings.signature_location == SignatureLocation::Headers {
Self::insert_date_header(&mut canonical_headers, date_time);
if let Some(security_token) = params.security_token {
if let Some(security_token) = creds.session_token() {
let mut sec_header = HeaderValue::from_str(security_token)?;
sec_header.set_sensitive(true);
canonical_headers.insert(header::X_AMZ_SECURITY_TOKEN, sec_header);
@ -517,7 +522,7 @@ impl<'a> StringToSign<'a> {
}
impl<'a> fmt::Display for StringToSign<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}\n{}\n{}\n{}",
@ -537,22 +542,21 @@ mod tests {
};
use crate::http_request::test::{test_canonical_request, test_request, test_sts};
use crate::http_request::{
PayloadChecksumKind, SessionTokenMode, SignableBody, SignableRequest, SigningSettings,
PayloadChecksumKind, SessionTokenMode, SignableBody, SignableRequest, SignatureLocation,
SigningParams, SigningSettings,
};
use crate::http_request::{SignatureLocation, SigningParams};
use crate::sign::sha256_hex_string;
use aws_credential_types::Credentials;
use aws_smithy_http::query_writer::QueryWriter;
use aws_smithy_runtime_api::client::identity::Identity;
use http::{HeaderValue, Uri};
use pretty_assertions::assert_eq;
use proptest::{prelude::*, proptest};
use std::time::Duration;
fn signing_params(settings: SigningSettings) -> SigningParams<'static> {
fn signing_params(identity: &Identity, settings: SigningSettings) -> SigningParams<'_> {
SigningParams {
access_key: "test-access-key",
secret_key: "test-secret-key",
security_token: None,
identity,
region: "test-region",
name: "testservicename",
time: parse_date_time("20210511T154045Z").unwrap(),
@ -576,7 +580,8 @@ mod tests {
payload_checksum_kind: PayloadChecksumKind::XAmzSha256,
..Default::default()
};
let signing_params = signing_params(settings);
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, settings);
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
assert_eq!(
@ -597,7 +602,8 @@ mod tests {
payload_checksum_kind: PayloadChecksumKind::XAmzSha256,
..Default::default()
};
let mut signing_params = signing_params(settings);
let identity = Credentials::for_tests().into();
let mut signing_params = signing_params(&identity, settings);
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
assert_eq!(
creq.values.content_sha256(),
@ -624,7 +630,8 @@ mod tests {
payload_checksum_kind: PayloadChecksumKind::XAmzSha256,
..Default::default()
};
let signing_params = signing_params(settings);
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, settings);
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
assert_eq!(creq.values.content_sha256(), "UNSIGNED-PAYLOAD");
assert!(creq.to_string().ends_with("UNSIGNED-PAYLOAD"));
@ -640,7 +647,8 @@ mod tests {
payload_checksum_kind: PayloadChecksumKind::XAmzSha256,
..Default::default()
};
let signing_params = signing_params(settings);
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, settings);
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
assert_eq!(creq.values.content_sha256(), payload_hash);
assert!(creq.to_string().ends_with(payload_hash));
@ -680,7 +688,8 @@ mod tests {
fn test_double_url_encode_path() {
let req = test_request("double-encode-path");
let req = SignableRequest::from(&req);
let signing_params = signing_params(SigningSettings::default());
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, SigningSettings::default());
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
let expected = test_canonical_request("double-encode-path");
@ -692,7 +701,8 @@ mod tests {
fn test_double_url_encode() {
let req = test_request("double-url-encode");
let req = SignableRequest::from(&req);
let signing_params = signing_params(SigningSettings::default());
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, SigningSettings::default());
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
let expected = test_canonical_request("double-url-encode");
@ -705,7 +715,8 @@ mod tests {
let req = http::Request::builder()
.uri("https://s3.us-east-1.amazonaws.com/my-bucket?list-type=2&prefix=~objprefix&single&k=&unreserved=-_.~").body("").unwrap().into();
let req = SignableRequest::from(&req);
let signing_params = signing_params(SigningSettings::default());
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, SigningSettings::default());
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
assert_eq!(
Some("k=&list-type=2&prefix=~objprefix&single=&unreserved=-_.~"),
@ -728,7 +739,8 @@ mod tests {
.unwrap()
.into();
let req = SignableRequest::from(&req);
let signing_params = signing_params(SigningSettings::default());
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, SigningSettings::default());
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
let expected = "list-type=2&prefix=%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~";
@ -744,8 +756,8 @@ mod tests {
session_token_mode: SessionTokenMode::Include,
..Default::default()
};
let mut signing_params = signing_params(settings);
signing_params.security_token = Some("notarealsessiontoken");
let identity = Credentials::for_tests_with_session_token().into();
let mut signing_params = signing_params(&identity, settings);
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
assert_eq!(
@ -787,7 +799,8 @@ mod tests {
..Default::default()
};
let signing_params = signing_params(settings);
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, settings);
let canonical = CanonicalRequest::from(&request, &signing_params).unwrap();
let values = canonical.values.as_headers().unwrap();
@ -819,7 +832,8 @@ mod tests {
..Default::default()
};
let signing_params = signing_params(settings);
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, settings);
let canonical = CanonicalRequest::from(&request, &signing_params).unwrap();
let values = canonical.values.into_query_params().unwrap();
@ -857,7 +871,8 @@ mod tests {
..Default::default()
};
let signing_params = signing_params(settings);
let identity = Credentials::for_tests().into();
let signing_params = signing_params(&identity, settings);
let canonical = CanonicalRequest::from(&request, &signing_params).unwrap();
let values = canonical.values.into_query_params().unwrap();

View File

@ -12,6 +12,7 @@ use std::str::Utf8Error;
#[derive(Debug)]
enum SigningErrorKind {
FailedToCreateCanonicalRequest { source: CanonicalRequestError },
UnsupportedIdentityType,
}
/// Error signing request
@ -20,12 +21,23 @@ pub struct SigningError {
kind: SigningErrorKind,
}
impl SigningError {
pub(crate) fn unsupported_identity_type() -> Self {
Self {
kind: SigningErrorKind::UnsupportedIdentityType,
}
}
}
impl fmt::Display for SigningError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
SigningErrorKind::FailedToCreateCanonicalRequest { .. } => {
write!(f, "failed to create canonical request")
}
SigningErrorKind::UnsupportedIdentityType => {
write!(f, "only 'AWS credentials' are supported for signing")
}
}
}
}
@ -34,6 +46,7 @@ impl Error for SigningError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.kind {
SigningErrorKind::FailedToCreateCanonicalRequest { source } => Some(source),
SigningErrorKind::UnsupportedIdentityType => None,
}
}
}
@ -52,6 +65,7 @@ enum CanonicalRequestErrorKind {
InvalidHeaderValue { source: InvalidHeaderValue },
InvalidUtf8InHeaderValue { source: Utf8Error },
InvalidUri { source: InvalidUri },
UnsupportedIdentityType,
}
#[derive(Debug)]
@ -67,6 +81,9 @@ impl fmt::Display for CanonicalRequestError {
InvalidHeaderValue { .. } => write!(f, "invalid header value"),
InvalidUtf8InHeaderValue { .. } => write!(f, "invalid UTF-8 in header value"),
InvalidUri { .. } => write!(f, "the uri was invalid"),
UnsupportedIdentityType => {
write!(f, "only AWS credentials are supported for signing")
}
}
}
}
@ -79,6 +96,7 @@ impl Error for CanonicalRequestError {
InvalidHeaderValue { source } => Some(source),
InvalidUtf8InHeaderValue { source } => Some(source),
InvalidUri { source } => Some(source),
UnsupportedIdentityType => None,
}
}
}
@ -89,6 +107,12 @@ impl CanonicalRequestError {
kind: CanonicalRequestErrorKind::InvalidUtf8InHeaderValue { source },
}
}
pub(crate) fn unsupported_credential_type() -> Self {
Self {
kind: CanonicalRequestErrorKind::UnsupportedIdentityType,
}
}
}
impl From<InvalidHeaderName> for CanonicalRequestError {

View File

@ -212,13 +212,20 @@ fn calculate_signing_params<'a>(
request: &'a SignableRequest<'a>,
params: &'a SigningParams<'a>,
) -> Result<(CalculatedParams, String), SigningError> {
let creds = params
.credentials()
.ok_or_else(SigningError::unsupported_identity_type)?;
let creq = CanonicalRequest::from(request, params)?;
let encoded_creq = &sha256_hex_string(creq.to_string().as_bytes());
let string_to_sign =
StringToSign::new(params.time, params.region, params.name, encoded_creq).to_string();
let signing_key =
generate_signing_key(params.secret_key, params.time, params.region, params.name);
let signing_key = generate_signing_key(
creds.secret_access_key(),
params.time,
params.region,
params.name,
);
let signature = calculate_signature(signing_key, string_to_sign.as_bytes());
tracing::trace!(canonical_request = %creq, string_to_sign = %string_to_sign, "calculated signing parameters");
@ -235,7 +242,7 @@ fn calculate_signing_params<'a>(
(param::X_AMZ_SIGNATURE, Cow::Owned(signature.clone())),
];
if let Some(security_token) = params.security_token {
if let Some(security_token) = creds.session_token() {
signing_params.push((
param::X_AMZ_SECURITY_TOKEN,
Cow::Owned(security_token.to_string()),
@ -255,6 +262,9 @@ fn calculate_signing_headers<'a>(
request: &'a SignableRequest<'a>,
params: &'a SigningParams<'a>,
) -> Result<SigningOutput<Vec<Header>>, SigningError> {
let creds = params
.credentials()
.ok_or_else(SigningError::unsupported_identity_type)?;
// Step 1: https://docs.aws.amazon.com/en_pv/general/latest/gr/sigv4-create-canonical-request.html.
let creq = CanonicalRequest::from(request, params)?;
tracing::trace!(canonical_request = %creq);
@ -264,8 +274,12 @@ fn calculate_signing_headers<'a>(
let sts = StringToSign::new(params.time, params.region, params.name, encoded_creq);
// Step 3: https://docs.aws.amazon.com/en_pv/general/latest/gr/sigv4-calculate-signature.html
let signing_key =
generate_signing_key(params.secret_key, params.time, params.region, params.name);
let signing_key = generate_signing_key(
creds.secret_access_key(),
params.time,
params.region,
params.name,
);
let signature = calculate_signature(signing_key, sts.to_string().as_bytes());
// Step 4: https://docs.aws.amazon.com/en_pv/general/latest/gr/sigv4-add-signature-to-request.html
@ -274,8 +288,7 @@ fn calculate_signing_headers<'a>(
add_header(&mut headers, header::X_AMZ_DATE, &values.date_time, false);
headers.push(Header {
key: "authorization",
value: build_authorization_header(params.access_key, &creq, sts, &signature),
value: build_authorization_header(creds.access_key_id(), &creq, sts, &signature),
sensitive: false,
});
if params.settings.payload_checksum_kind == PayloadChecksumKind::XAmzSha256 {
@ -287,7 +300,7 @@ fn calculate_signing_headers<'a>(
);
}
if let Some(security_token) = params.security_token {
if let Some(security_token) = creds.session_token() {
add_header(
&mut headers,
header::X_AMZ_SECURITY_TOKEN,
@ -327,7 +340,6 @@ fn build_authorization_header(
#[cfg(test)]
mod tests {
use super::sign;
use crate::date_time::test_parsers::parse_date_time;
use crate::http_request::sign::SignableRequest;
use crate::http_request::test::{
@ -336,6 +348,7 @@ mod tests {
use crate::http_request::{
SessionTokenMode, SignableBody, SignatureLocation, SigningParams, SigningSettings,
};
use aws_credential_types::Credentials;
use http::{HeaderValue, Request};
use pretty_assertions::assert_eq;
use proptest::proptest;
@ -366,9 +379,7 @@ mod tests {
fn test_sign_vanilla_with_headers() {
let settings = SigningSettings::default();
let params = SigningParams {
access_key: "AKIDEXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "service",
time: parse_date_time("20150830T123600Z").unwrap(),
@ -377,9 +388,9 @@ mod tests {
let original = test_request("get-vanilla-query-order-key-case");
let signable = SignableRequest::from(&original);
let out = sign(signable, &params).unwrap();
let out = crate::http_request::sign(signable, &params).unwrap();
assert_eq!(
"b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500",
"5557820e7380d585310524bd93d51a08d7757fb5efd7344ee12088f2b0860947",
out.signature
);
@ -395,9 +406,7 @@ mod tests {
let test = "double-encode-path";
let settings = SigningSettings::default();
let params = SigningParams {
access_key: "AKIDEXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "service",
time: parse_date_time("20150830T123600Z").unwrap(),
@ -406,9 +415,9 @@ mod tests {
let original = test_request(test);
let signable = SignableRequest::from(&original);
let out = sign(signable, &params).unwrap();
let out = crate::http_request::sign(signable, &params).unwrap();
assert_eq!(
"6f871eb157f326fa5f7439eb88ca200048635950ce7d6037deda56f0c95d4364",
"57d157672191bac40bae387e48bbe14b15303c001fdbb01f4abf295dccb09705",
out.signature
);
@ -427,9 +436,7 @@ mod tests {
..Default::default()
};
let params = SigningParams {
access_key: "AKIDEXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "service",
time: parse_date_time("20150830T123600Z").unwrap(),
@ -438,9 +445,9 @@ mod tests {
let original = test_request("get-vanilla-query-order-key-case");
let signable = SignableRequest::from(&original);
let out = sign(signable, &params).unwrap();
let out = crate::http_request::sign(signable, &params).unwrap();
assert_eq!(
"f25aea20f8c722ece3b363fc5d60cc91add973f9b64c42ba36fa28d57afe9019",
"ecce208e4b4f7d7e3a4cc22ced6acc2ad1d170ee8ba87d7165f6fa4b9aff09ab",
out.signature
);
@ -455,9 +462,7 @@ mod tests {
fn test_sign_headers_utf8() {
let settings = SigningSettings::default();
let params = SigningParams {
access_key: "AKIDEXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "service",
time: parse_date_time("20150830T123600Z").unwrap(),
@ -471,9 +476,9 @@ mod tests {
.unwrap()
.into();
let signable = SignableRequest::from(&original);
let out = sign(signable, &params).unwrap();
let out = crate::http_request::sign(signable, &params).unwrap();
assert_eq!(
"4596b207a7fc6bdf18725369bc0cd7022cf20efbd2c19730549f42d1a403648e",
"55e16b31f9bde5fd04f9d3b780dd2b5e5f11a5219001f91a8ca9ec83eaf1618f",
out.signature
);
@ -491,9 +496,9 @@ mod tests {
"authorization",
HeaderValue::from_str(
"AWS4-HMAC-SHA256 \
Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, \
Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, \
SignedHeaders=host;some-header;x-amz-date, \
Signature=4596b207a7fc6bdf18725369bc0cd7022cf20efbd2c19730549f42d1a403648e",
Signature=55e16b31f9bde5fd04f9d3b780dd2b5e5f11a5219001f91a8ca9ec83eaf1618f",
)
.unwrap(),
)
@ -509,9 +514,7 @@ mod tests {
..Default::default()
};
let mut params = SigningParams {
access_key: "AKIDEXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "service",
time: parse_date_time("20150830T123600Z").unwrap(),
@ -523,13 +526,15 @@ mod tests {
.body("")
.unwrap()
.into();
let out_without_session_token = sign(SignableRequest::from(&original), &params).unwrap();
params.security_token = Some("notarealsessiontoken");
let out_without_session_token =
crate::http_request::sign(SignableRequest::from(&original), &params).unwrap();
let identity = Credentials::for_tests_with_session_token().into();
params.identity = &identity;
let out_with_session_token_but_excluded =
sign(SignableRequest::from(&original), &params).unwrap();
crate::http_request::sign(SignableRequest::from(&original), &params).unwrap();
assert_eq!(
"d2445d2d58e01146627c1e498dc0b4749d0cecd2cab05c5349ed132c083914e8",
"ab32de057edf094958d178b3c91f3c8d5c296d526b11da991cd5773d09cea560",
out_with_session_token_but_excluded.signature
);
assert_eq!(
@ -552,9 +557,9 @@ mod tests {
"authorization",
HeaderValue::from_str(
"AWS4-HMAC-SHA256 \
Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, \
Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, \
SignedHeaders=host;x-amz-date, \
Signature=d2445d2d58e01146627c1e498dc0b4749d0cecd2cab05c5349ed132c083914e8",
Signature=ab32de057edf094958d178b3c91f3c8d5c296d526b11da991cd5773d09cea560",
)
.unwrap(),
)
@ -571,9 +576,7 @@ mod tests {
fn test_sign_headers_space_trimming() {
let settings = SigningSettings::default();
let params = SigningParams {
access_key: "AKIDEXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "service",
time: parse_date_time("20150830T123600Z").unwrap(),
@ -590,9 +593,9 @@ mod tests {
.unwrap()
.into();
let signable = SignableRequest::from(&original);
let out = sign(signable, &params).unwrap();
let out = crate::http_request::sign(signable, &params).unwrap();
assert_eq!(
"0bd74dbf6f21161f61a1a3a1c313b6a4bc67ec57bf5ea9ae956a63753ca1d7f7",
"244f2a0db34c97a528f22715fe01b2417b7750c8a95c7fc104a3c48d81d84c08",
out.signature
);
@ -613,9 +616,9 @@ mod tests {
"authorization",
HeaderValue::from_str(
"AWS4-HMAC-SHA256 \
Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, \
Credential=ANOTREAL/20150830/us-east-1/service/aws4_request, \
SignedHeaders=host;some-header;x-amz-date, \
Signature=0bd74dbf6f21161f61a1a3a1c313b6a4bc67ec57bf5ea9ae956a63753ca1d7f7",
Signature=244f2a0db34c97a528f22715fe01b2417b7750c8a95c7fc104a3c48d81d84c08",
)
.unwrap(),
)
@ -633,9 +636,7 @@ mod tests {
) {
let settings = SigningSettings::default();
let params = SigningParams {
access_key: "123",
secret_key: "asdf",
security_token: None,
identity: &Credentials::for_tests().into(),
region: "us-east-1",
name: "foo",
time: std::time::SystemTime::UNIX_EPOCH,

View File

@ -15,7 +15,8 @@
unreachable_pub
)]
use std::fmt;
use aws_credential_types::Credentials;
use aws_smithy_runtime_api::client::identity::Identity;
use std::time::SystemTime;
pub mod sign;
@ -29,14 +30,11 @@ pub mod event_stream;
pub mod http_request;
/// Parameters to use when signing.
// #[derive(Debug)] assumes that any data `Identity` holds is responsible for handling its own redaction.
#[derive(Debug)]
#[non_exhaustive]
pub struct SigningParams<'a, S> {
/// Access Key ID to use.
pub(crate) access_key: &'a str,
/// Secret access key to use.
pub(crate) secret_key: &'a str,
/// (Optional) Security token to use.
pub(crate) security_token: Option<&'a str>,
pub(crate) identity: &'a Identity,
/// Region to sign for.
pub(crate) region: &'a str,
@ -61,19 +59,10 @@ impl<'a, S> SigningParams<'a, S> {
pub fn name(&self) -> &str {
self.name
}
}
impl<'a, S: fmt::Debug> fmt::Debug for SigningParams<'a, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SigningParams")
.field("access_key", &"** redacted **")
.field("secret_key", &"** redacted **")
.field("security_token", &"** redacted **")
.field("region", &self.region)
.field("name", &self.name)
.field("time", &self.time)
.field("settings", &self.settings)
.finish()
/// If the identity in params contains AWS credentials, return them. Otherwise, return `None`.
pub(crate) fn credentials(&self) -> Option<&Credentials> {
self.identity.data::<Credentials>()
}
}
@ -86,7 +75,7 @@ impl<'a, S: Default> SigningParams<'a, S> {
/// Builder and error for creating [`SigningParams`]
pub mod signing_params {
use super::SigningParams;
use super::{Identity, SigningParams};
use std::error::Error;
use std::fmt;
use std::time::SystemTime;
@ -113,9 +102,7 @@ pub mod signing_params {
/// Builder that can create new [`SigningParams`]
#[derive(Debug, Default)]
pub struct Builder<'a, S> {
access_key: Option<&'a str>,
secret_key: Option<&'a str>,
security_token: Option<&'a str>,
identity: Option<&'a Identity>,
region: Option<&'a str>,
name: Option<&'a str>,
time: Option<SystemTime>,
@ -123,34 +110,14 @@ pub mod signing_params {
}
impl<'a, S> Builder<'a, S> {
/// Sets the access key (required).
pub fn access_key(mut self, access_key: &'a str) -> Self {
self.access_key = Some(access_key);
/// Sets the identity (required).
pub fn identity(mut self, identity: &'a Identity) -> Self {
self.identity = Some(identity);
self
}
/// Sets the access key (required)
pub fn set_access_key(&mut self, access_key: Option<&'a str>) {
self.access_key = access_key;
}
/// Sets the secret key (required)
pub fn secret_key(mut self, secret_key: &'a str) -> Self {
self.secret_key = Some(secret_key);
self
}
/// Sets the secret key (required)
pub fn set_secret_key(&mut self, secret_key: Option<&'a str>) {
self.secret_key = secret_key;
}
/// Sets the security token (optional)
pub fn security_token(mut self, security_token: &'a str) -> Self {
self.security_token = Some(security_token);
self
}
/// Sets the security token (optional)
pub fn set_security_token(&mut self, security_token: Option<&'a str>) {
self.security_token = security_token;
/// Sets the identity (required)
pub fn set_identity(&mut self, identity: Option<&'a Identity>) {
self.identity = identity;
}
/// Sets the region (required)
@ -197,13 +164,9 @@ pub mod signing_params {
/// a required argument was not given.
pub fn build(self) -> Result<SigningParams<'a, S>, BuildError> {
Ok(SigningParams {
access_key: self
.access_key
.ok_or_else(|| BuildError::new("access key is required"))?,
secret_key: self
.secret_key
.ok_or_else(|| BuildError::new("secret key is required"))?,
security_token: self.security_token,
identity: self
.identity
.ok_or_else(|| BuildError::new("an identity is required"))?,
region: self
.region
.ok_or_else(|| BuildError::new("region is required"))?,

View File

@ -66,7 +66,7 @@ async fn generate_random() {
let conf = Config::builder()
.http_connector(conn.clone())
.region(Region::new("us-east-1"))
.credentials_provider(Credentials::for_tests())
.credentials_provider(Credentials::for_tests_with_session_token())
.build();
let client = kms::Client::from_conf(conf);
let resp = client
@ -148,7 +148,7 @@ async fn generate_random_keystore_not_found() {
let conf = Config::builder()
.http_connector(conn.clone())
.region(Region::new("us-east-1"))
.credentials_provider(Credentials::for_tests())
.credentials_provider(Credentials::for_tests_with_session_token())
.build();
let client = kms::Client::from_conf(conf);

View File

@ -12,7 +12,7 @@ use std::time::{Duration, SystemTime};
#[tokio::test]
async fn test_presigning() {
let config = Config::builder()
.credentials_provider(Credentials::for_tests())
.credentials_provider(Credentials::for_tests_with_session_token())
.region(Region::new("us-east-1"))
.build();
let client = polly::Client::from_conf(config);

View File

@ -37,7 +37,7 @@ async fn signv4_use_correct_service_name() {
let conf = Config::builder()
.http_connector(conn.clone())
.region(Region::new("us-east-1"))
.credentials_provider(Credentials::for_tests())
.credentials_provider(Credentials::for_tests_with_session_token())
.build();
let client = Client::from_conf(conf);

View File

@ -15,7 +15,9 @@ use std::time::{Duration, UNIX_EPOCH};
async fn test_s3_ops_are_customizable() {
let (conn, rcvr) = capture_request(None);
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.region(Region::new("us-east-1"))
.http_connector(conn.clone())
.build();

View File

@ -42,7 +42,9 @@ async fn ignore_invalid_xml_body_root() {
]);
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.region(Region::new("us-east-1"))
.http_connector(conn.clone())
.build();

View File

@ -50,7 +50,9 @@ const NAUGHTY_STRINGS: &str = include_str!("blns/blns.txt");
async fn test_s3_signer_with_naughty_string_metadata() {
let (conn, rcvr) = capture_request(None);
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.region(Region::new("us-east-1"))
.http_connector(conn.clone())
.build();

View File

@ -14,7 +14,9 @@ use std::time::{Duration, UNIX_EPOCH};
async fn test_operation_should_not_normalize_uri_path() {
let (conn, rx) = capture_request(None);
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.region(Region::new("us-east-1"))
.http_connector(conn.clone())
.build();

View File

@ -49,7 +49,7 @@ where
O: FnOnce(s3::Client) -> F,
F: TestOperation,
{
let creds = Credentials::for_tests();
let creds = Credentials::for_tests_with_session_token();
let config = s3::Config::builder()
.credentials_provider(creds)
.region(Region::new("us-east-1"))

View File

@ -14,7 +14,9 @@ use std::time::{Duration, UNIX_EPOCH};
async fn test_s3_signer_query_string_with_all_valid_chars() {
let (conn, rcvr) = capture_request(None);
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.region(Region::new("us-east-1"))
.http_connector(conn.clone())
.build();

View File

@ -74,7 +74,7 @@ async fn three_retries_and_then_success() {
let path = "tests/data/request-information-headers/three-retries_and-then-success.json";
let conn = dvr::ReplayingConnection::from_file(path).unwrap();
let config = aws_sdk_s3::Config::builder()
.credentials_provider(Credentials::for_tests())
.credentials_provider(Credentials::for_tests_with_session_token())
.region(Region::new("us-east-1"))
.http_connector(DynConnector::new(conn.clone()))
.time_source(SharedTimeSource::new(time_source.clone()))

View File

@ -22,7 +22,9 @@ async fn test_signer() {
http::Response::builder().status(200).body("").unwrap(),
)]);
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.region(Region::new("us-east-1"))
.http_connector(conn.clone())
.build();

View File

@ -25,7 +25,9 @@ async fn test_signer() {
http::Response::builder().status(200).body("").unwrap(),
)]);
let sdk_config = SdkConfig::builder()
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.http_connector(conn.clone())
.region(Region::new("us-east-1"))
.build();

View File

@ -26,7 +26,9 @@ async fn do_endpoint_discovery() {
.http_connector(conn.clone())
.region(Region::from_static("us-west-2"))
.sleep_impl(SharedAsyncSleep::new(sleep))
.credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
.credentials_provider(SharedCredentialsProvider::new(
Credentials::for_tests_with_session_token(),
))
.time_source(SharedTimeSource::new(ts.clone()))
.build();
let conf = query::config::Builder::from(&config)