Closed rcoh closed 6 months ago
Tracking implementation of this in https://github.com/awslabs/smithy-rs/issues/1792
Previously (before 0.57.x) I was signing requests for API Gateway as follows:
// Sign a given HTTP request. Signs the request in place.
// Returning the Signature here is to enable testing, we don't actually need
// that in normal use of the program.
pub async fn v4(
request: &mut Request<SdkBody>,
timestamp: SystemTime,
credentials_provider: &(impl ProvideCredentials + ?Sized),
region: &Region,
service_name: &SigningService,
) -> Result<Signature> {
let request_config = RequestConfig {
payload_override: None,
region: &SigningRegion::from(region.clone()),
request_ts: timestamp,
service: service_name,
};
let signer = SigV4Signer::new();
let signature = signer.sign(
&OperationSigningConfig::default_config(),
&request_config,
&credentials_provider.provide_credentials().await?,
request,
)?;
Ok(signature)
}
As an example of how that was called, we have the test:
#[tokio::test]
async fn test_v4() {
// The signature generated here was checked against the AWS SigV4
// generator with the same inputs to ensure correctness.
// The headers signed are:
// - Cache-Control
// - Content-Type
// - Host
// - X-Amz-Date
// http://aws-signature.com.s3-website-us-west-2.amazonaws.com
let region = Region::from_static("eu-west-1");
let service_name = SigningService::from_static("execute-api");
let uri = Uri::from_str("https://localhost/").unwrap();
let shared_credentials_provider = SharedCredentialsProvider::new(
Credentials::from_keys("TESTKEY", "TESTSECRET", None),
);
let timestamp = SystemTime::from(
DateTime::parse_from_rfc3339("2023-07-06T17:39:57Z").unwrap(),
);
let mut request = Request::builder()
.method(Method::GET)
.uri(uri)
.header(CACHE_CONTROL, "no-cache")
.header(CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(SdkBody::empty())
.unwrap();
let result = v4(
&mut request,
timestamp,
&shared_credentials_provider,
®ion,
&service_name,
).await.unwrap();
let expected = "1c0857d14a9994d21fce85452e206e28c3c205a51423fa03a18bc622862afea0";
assert_eq!(result.as_ref(), expected);
}
With 0.57.x lots of things have moved around, and the documentation is harder to read in this area. Before, it was easy to figure out signing from the types, but less so now.
Could an example of signing a request be added? Thanks.
Edit: Perhaps read the above as the Nov 2nd release, where lots of things moved around, since version numbers given don't apply to all crates. The changes breaking signing may have happened before then, but there are no changelogs for a while leading up to Nov 2nd.
I've actually figured this out now. I'm not sure which version I was looking at before, but when I returned a few days later, it was fairly obvious what I needed to do. The above function became the following, and passes the old tests.
// Sign a given HTTP request. Signs the request in place.
// Returning the Signature here is to enable testing, we don't actually need
// that in normal use of the program.
pub async fn v4(
request: &mut Request<SdkBody>,
timestamp: SystemTime,
credentials_provider: &(impl ProvideCredentials + ?Sized),
region: &Region,
service_name: &str,
) -> Result<String> {
let credentials = credentials_provider.provide_credentials().await?;
let identity = Identity::from(credentials);
let region = region.to_string();
let signing_settings = SigningSettings::default();
let signing_params = SigningParams::builder()
.identity(&identity)
.region(®ion)
.name(service_name)
.time(timestamp)
.settings(signing_settings)
.build()
.context("signing params")?;
// Get the headers into a format passable to the SignableRequest.
// If a header value fails to be represented as a string here, the header
// will be skipped. This could result in the request signature being
// incorrect compared to what AWS expected.
let headers: Vec<(&str, &str)> = request
.headers()
.iter()
.filter_map(|(key, value)| {
let key = key.as_str();
let Ok(value) = value.to_str() else {
event!(
Level::DEBUG,
"failed to get value as str for header: {}",
key,
);
return None;
};
Some((key, value))
})
.collect();
let signable_body = match request.body().bytes() {
None => SignableBody::UnsignedPayload,
Some(bytes) => SignableBody::Bytes(bytes),
};
let signable_request = SignableRequest::new(
request.method().as_str(),
request.uri().to_string(),
headers.into_iter(),
signable_body,
).context("signable request")?;
let (signing_instructions, signature) = sign(
signable_request,
&signing_params.into(),
).context("signing")?.into_parts();
signing_instructions.apply_to_request_http0x(request);
Ok(signature)
}
Closing as duplicate: https://github.com/awslabs/aws-sdk-rust/issues/948
Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.
Discussed in https://github.com/awslabs/aws-sdk-rust/discussions/133
Self contained signing module with documentation in the guide