mirror of https://github.com/smithy-lang/smithy-rs
Add support for omitting session token in canonical requests (#2473)
* Add support for omitting session token in canonical request
* Add tests to cover session token exclusion in signed headers
* Remove redundant session token insertion
* Drop mut canonical_headers
* Skip adding x-amz-security-token to signed headers if excluded
* 📎
* cargofmt
* Update changelog
* Update CHANGELOG.next.toml
Co-authored-by: John DiSanti <johndisanti@gmail.com>
---------
Co-authored-by: Russell Cohen <rcoh@amazon.com>
Co-authored-by: John DiSanti <johndisanti@gmail.com>
This commit is contained in:
parent
d14357c2e2
commit
b023426d1c
|
@ -68,3 +68,9 @@ message = "`AppName` is now configurable from within `ConfigLoader`."
|
|||
references = ["smithy-rs#2513"]
|
||||
meta = { "breaking" = false, "tada" = false, "bug" = true }
|
||||
author = "ysaito1001"
|
||||
|
||||
[[aws-sdk-rust]]
|
||||
message = "Add support for omitting session token in canonical requests for SigV4 signing."
|
||||
references = ["smithy-rs#2473"]
|
||||
meta = { "breaking" = false, "tada" = false, "bug" = false }
|
||||
author = "martinjlowm"
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
use crate::middleware::Signature;
|
||||
use aws_credential_types::Credentials;
|
||||
use aws_sigv4::http_request::{
|
||||
sign, PayloadChecksumKind, PercentEncodingMode, SignableRequest, SignatureLocation,
|
||||
SigningParams, SigningSettings, UriPathNormalizationMode,
|
||||
sign, PayloadChecksumKind, PercentEncodingMode, SessionTokenMode, SignableRequest,
|
||||
SignatureLocation, SigningParams, SigningSettings, UriPathNormalizationMode,
|
||||
};
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_types::region::SigningRegion;
|
||||
|
@ -63,6 +63,7 @@ impl OperationSigningConfig {
|
|||
double_uri_encode: true,
|
||||
content_sha256_header: false,
|
||||
normalize_uri_path: true,
|
||||
omit_session_token: false,
|
||||
},
|
||||
signing_requirements: SigningRequirements::Required,
|
||||
expires_in: None,
|
||||
|
@ -90,10 +91,7 @@ pub struct SigningOptions {
|
|||
pub double_uri_encode: bool,
|
||||
pub content_sha256_header: bool,
|
||||
pub normalize_uri_path: bool,
|
||||
/*
|
||||
Currently unsupported:
|
||||
pub omit_session_token: bool,
|
||||
*/
|
||||
}
|
||||
|
||||
/// Signing Configuration for an individual Request
|
||||
|
@ -145,6 +143,11 @@ impl SigV4Signer {
|
|||
} else {
|
||||
UriPathNormalizationMode::Disabled
|
||||
};
|
||||
settings.session_token_mode = if operation_config.signing_options.omit_session_token {
|
||||
SessionTokenMode::Exclude
|
||||
} else {
|
||||
SessionTokenMode::Include
|
||||
};
|
||||
settings.signature_location = match operation_config.signature_type {
|
||||
HttpSignatureType::HttpRequestHeaders => SignatureLocation::Headers,
|
||||
HttpSignatureType::HttpRequestQueryParams => SignatureLocation::QueryParams,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
use crate::date_time::{format_date, format_date_time};
|
||||
use crate::http_request::error::CanonicalRequestError;
|
||||
use crate::http_request::settings::SessionTokenMode;
|
||||
use crate::http_request::settings::UriPathNormalizationMode;
|
||||
use crate::http_request::sign::SignableRequest;
|
||||
use crate::http_request::uri_path_normalization::normalize_uri_path;
|
||||
|
@ -122,6 +123,8 @@ impl<'a> CanonicalRequest<'a> {
|
|||
/// - If `settings.percent_encoding_mode` specifies double encoding, `%` in the URL will be re-encoded as `%25`
|
||||
/// - If `settings.payload_checksum_kind` is XAmzSha256, add a x-amz-content-sha256 with the body
|
||||
/// checksum. This is the same checksum used as the "payload_hash" in the canonical request
|
||||
/// - If `settings.session_token_mode` specifies X-Amz-Security-Token to be
|
||||
/// included before calculating the signature, add it, otherwise omit it.
|
||||
/// - `settings.signature_location` determines where the signature will be placed in a request,
|
||||
/// and also alters the kinds of signing values that go along with it in the request.
|
||||
pub(super) fn from<'b>(
|
||||
|
@ -146,11 +149,17 @@ impl<'a> CanonicalRequest<'a> {
|
|||
let (signed_headers, canonical_headers) =
|
||||
Self::headers(req, params, &payload_hash, &date_time)?;
|
||||
let signed_headers = SignedHeaders::new(signed_headers);
|
||||
|
||||
let security_token = match params.settings.session_token_mode {
|
||||
SessionTokenMode::Include => params.security_token,
|
||||
SessionTokenMode::Exclude => None,
|
||||
};
|
||||
|
||||
let values = match params.settings.signature_location {
|
||||
SignatureLocation::Headers => SignatureValues::Headers(HeaderValues {
|
||||
content_sha256: payload_hash,
|
||||
date_time,
|
||||
security_token: params.security_token,
|
||||
security_token,
|
||||
signed_headers,
|
||||
}),
|
||||
SignatureLocation::QueryParams => SignatureValues::QueryParams(QueryParamValues {
|
||||
|
@ -170,10 +179,11 @@ impl<'a> CanonicalRequest<'a> {
|
|||
.expect("presigning requires expires_in")
|
||||
.as_secs()
|
||||
.to_string(),
|
||||
security_token: params.security_token,
|
||||
security_token,
|
||||
signed_headers,
|
||||
}),
|
||||
};
|
||||
|
||||
let creq = CanonicalRequest {
|
||||
method: req.method(),
|
||||
path,
|
||||
|
@ -232,6 +242,12 @@ impl<'a> CanonicalRequest<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
if params.settings.session_token_mode == SessionTokenMode::Exclude
|
||||
&& name == HeaderName::from_static(header::X_AMZ_SECURITY_TOKEN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if params.settings.signature_location == SignatureLocation::QueryParams {
|
||||
// The X-Amz-User-Agent header should not be signed if this is for a presigned URL
|
||||
if name == HeaderName::from_static(header::X_AMZ_USER_AGENT) {
|
||||
|
@ -240,6 +256,7 @@ impl<'a> CanonicalRequest<'a> {
|
|||
}
|
||||
signed_headers.push(CanonicalHeaderName(name.clone()));
|
||||
}
|
||||
|
||||
Ok((signed_headers, canonical_headers))
|
||||
}
|
||||
|
||||
|
@ -280,6 +297,7 @@ impl<'a> CanonicalRequest<'a> {
|
|||
param::X_AMZ_SIGNED_HEADERS,
|
||||
values.signed_headers.as_str(),
|
||||
);
|
||||
|
||||
if let Some(security_token) = values.security_token {
|
||||
add_param(&mut params, param::X_AMZ_SECURITY_TOKEN, security_token);
|
||||
}
|
||||
|
@ -521,7 +539,7 @@ mod tests {
|
|||
};
|
||||
use crate::http_request::test::{test_canonical_request, test_request, test_sts};
|
||||
use crate::http_request::{
|
||||
PayloadChecksumKind, SignableBody, SignableRequest, SigningSettings,
|
||||
PayloadChecksumKind, SessionTokenMode, SignableBody, SignableRequest, SigningSettings,
|
||||
};
|
||||
use crate::http_request::{SignatureLocation, SigningParams};
|
||||
use crate::sign::sha256_hex_string;
|
||||
|
@ -726,6 +744,36 @@ mod tests {
|
|||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_omit_session_token() {
|
||||
let req = test_request("get-vanilla-query-order-key-case");
|
||||
let req = SignableRequest::from(&req);
|
||||
let settings = SigningSettings {
|
||||
session_token_mode: SessionTokenMode::Include,
|
||||
..Default::default()
|
||||
};
|
||||
let mut signing_params = signing_params(settings);
|
||||
signing_params.security_token = Some("notarealsessiontoken");
|
||||
|
||||
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
|
||||
assert_eq!(
|
||||
creq.values.signed_headers().as_str(),
|
||||
"host;x-amz-date;x-amz-security-token"
|
||||
);
|
||||
assert_eq!(
|
||||
creq.headers.get("x-amz-security-token").unwrap(),
|
||||
"notarealsessiontoken"
|
||||
);
|
||||
|
||||
signing_params.settings.session_token_mode = SessionTokenMode::Exclude;
|
||||
let creq = CanonicalRequest::from(&req, &signing_params).unwrap();
|
||||
assert_eq!(
|
||||
creq.headers.get("x-amz-security-token").unwrap(),
|
||||
"notarealsessiontoken"
|
||||
);
|
||||
assert_eq!(creq.values.signed_headers().as_str(), "host;x-amz-date");
|
||||
}
|
||||
|
||||
// It should exclude user-agent and x-amz-user-agent headers from presigning
|
||||
#[test]
|
||||
fn presigning_header_exclusion() {
|
||||
|
|
|
@ -53,7 +53,7 @@ pub(crate) mod test;
|
|||
|
||||
pub use error::SigningError;
|
||||
pub use settings::{
|
||||
PayloadChecksumKind, PercentEncodingMode, SignatureLocation, SigningParams, SigningSettings,
|
||||
UriPathNormalizationMode,
|
||||
PayloadChecksumKind, PercentEncodingMode, SessionTokenMode, SignatureLocation, SigningParams,
|
||||
SigningSettings, UriPathNormalizationMode,
|
||||
};
|
||||
pub use sign::{sign, SignableBody, SignableRequest};
|
||||
|
|
|
@ -32,6 +32,11 @@ pub struct SigningSettings {
|
|||
|
||||
/// Specifies whether the absolute path component of the URI should be normalized during signing.
|
||||
pub uri_path_normalization_mode: UriPathNormalizationMode,
|
||||
|
||||
/// Some services require X-Amz-Security-Token to be included in the
|
||||
/// canonical request. Other services require only it to be added after
|
||||
/// calculating the signature.
|
||||
pub session_token_mode: SessionTokenMode,
|
||||
}
|
||||
|
||||
/// HTTP payload checksum type
|
||||
|
@ -78,6 +83,18 @@ pub enum UriPathNormalizationMode {
|
|||
Disabled,
|
||||
}
|
||||
|
||||
/// Config value to specify whether X-Amz-Security-Token should be part of the canonical request.
|
||||
/// <http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html#temporary-security-credentials>
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum SessionTokenMode {
|
||||
/// Include in the canonical request before calculating the signature.
|
||||
Include,
|
||||
|
||||
/// Exclude in the canonical request.
|
||||
Exclude,
|
||||
}
|
||||
|
||||
impl Default for SigningSettings {
|
||||
fn default() -> Self {
|
||||
// The user agent header should not be signed because it may be altered by proxies
|
||||
|
@ -90,6 +107,7 @@ impl Default for SigningSettings {
|
|||
expires_in: None,
|
||||
excluded_headers: Some(EXCLUDED_HEADERS.to_vec()),
|
||||
uri_path_normalization_mode: UriPathNormalizationMode::Enabled,
|
||||
session_token_mode: SessionTokenMode::Include,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -210,12 +210,14 @@ fn calculate_signing_params<'a>(
|
|||
),
|
||||
(param::X_AMZ_SIGNATURE, Cow::Owned(signature.clone())),
|
||||
];
|
||||
|
||||
if let Some(security_token) = params.security_token {
|
||||
signing_params.push((
|
||||
param::X_AMZ_SECURITY_TOKEN,
|
||||
Cow::Owned(security_token.to_string()),
|
||||
));
|
||||
}
|
||||
|
||||
Ok((signing_params, signature))
|
||||
}
|
||||
|
||||
|
@ -266,9 +268,11 @@ fn calculate_signing_headers<'a>(
|
|||
&values.content_sha256,
|
||||
);
|
||||
}
|
||||
if let Some(security_token) = values.security_token {
|
||||
|
||||
if let Some(security_token) = params.security_token {
|
||||
add_header(&mut headers, header::X_AMZ_SECURITY_TOKEN, security_token);
|
||||
}
|
||||
|
||||
Ok(SigningOutput::new(headers, signature))
|
||||
}
|
||||
|
||||
|
@ -306,7 +310,9 @@ mod tests {
|
|||
make_headers_comparable, test_request, test_signed_request,
|
||||
test_signed_request_query_params,
|
||||
};
|
||||
use crate::http_request::{SignatureLocation, SigningParams, SigningSettings};
|
||||
use crate::http_request::{
|
||||
SessionTokenMode, SignatureLocation, SigningParams, SigningSettings,
|
||||
};
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use pretty_assertions::assert_eq;
|
||||
use proptest::proptest;
|
||||
|
@ -460,6 +466,70 @@ mod tests {
|
|||
assert_req_eq!(expected, signed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_headers_excluding_session_token() {
|
||||
let settings = SigningSettings {
|
||||
session_token_mode: SessionTokenMode::Exclude,
|
||||
..Default::default()
|
||||
};
|
||||
let mut params = SigningParams {
|
||||
access_key: "AKIDEXAMPLE",
|
||||
secret_key: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||
security_token: None,
|
||||
region: "us-east-1",
|
||||
service_name: "service",
|
||||
time: parse_date_time("20150830T123600Z").unwrap(),
|
||||
settings,
|
||||
};
|
||||
|
||||
let original = http::Request::builder()
|
||||
.uri("https://some-endpoint.some-region.amazonaws.com")
|
||||
.body("")
|
||||
.unwrap();
|
||||
let out_without_session_token = sign(SignableRequest::from(&original), ¶ms).unwrap();
|
||||
params.security_token = Some("notarealsessiontoken");
|
||||
|
||||
let out_with_session_token_but_excluded =
|
||||
sign(SignableRequest::from(&original), ¶ms).unwrap();
|
||||
assert_eq!(
|
||||
"d2445d2d58e01146627c1e498dc0b4749d0cecd2cab05c5349ed132c083914e8",
|
||||
out_with_session_token_but_excluded.signature
|
||||
);
|
||||
assert_eq!(
|
||||
out_with_session_token_but_excluded.signature,
|
||||
out_without_session_token.signature
|
||||
);
|
||||
|
||||
let mut signed = original;
|
||||
out_with_session_token_but_excluded
|
||||
.output
|
||||
.apply_to_request(&mut signed);
|
||||
|
||||
let mut expected = http::Request::builder()
|
||||
.uri("https://some-endpoint.some-region.amazonaws.com")
|
||||
.header(
|
||||
"x-amz-date",
|
||||
HeaderValue::from_str("20150830T123600Z").unwrap(),
|
||||
)
|
||||
.header(
|
||||
"authorization",
|
||||
HeaderValue::from_str(
|
||||
"AWS4-HMAC-SHA256 \
|
||||
Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, \
|
||||
SignedHeaders=host;x-amz-date, \
|
||||
Signature=d2445d2d58e01146627c1e498dc0b4749d0cecd2cab05c5349ed132c083914e8",
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.header(
|
||||
"x-amz-security-token",
|
||||
HeaderValue::from_str("notarealsessiontoken").unwrap(),
|
||||
)
|
||||
.body("")
|
||||
.unwrap();
|
||||
assert_req_eq!(expected, signed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_headers_space_trimming() {
|
||||
let settings = SigningSettings::default();
|
||||
|
|
Loading…
Reference in New Issue