use crate::nodes::function::v2::module::http::auth::{AwsIamAuth, AzureIamAuth, GcpIamAuth};
use ::http::Request as HttpRequest;
use anyhow::Context;
use http::HeaderValue;
use reqsign::{aws, azure, google};
use reqwest::{Body, Request};
use sha2::{Digest, Sha256};
use std::ops::Deref;
use std::sync::{Arc, OnceLock};
impl AwsIamAuth {
pub async fn build_request(&self, http_request: HttpRequest<Body>) -> anyhow::Result<Request> {
static CACHED_PROVIDER: OnceLock<Arc<aws::DefaultCredentialProvider>> = OnceLock::new();
let provider = CACHED_PROVIDER
.get_or_init(|| Arc::new(aws::DefaultCredentialProvider::new()))
.clone();
let signer = aws::default_signer(self.service.deref(), self.region.0.deref())
.with_credential_provider(provider);
let (mut parts, body) = http_request.into_parts();
let payload_hash_opt = body
.as_bytes()
.map(|body_bytes| format!("{:x}", Sha256::digest(&body_bytes)));
if let Some(payload_hash) = payload_hash_opt {
parts.headers.insert(
"x-amz-content-sha256",
HeaderValue::from_str(payload_hash.as_str())?,
);
}
signer
.sign(&mut parts, None)
.await
.context("Failed to sign request body")?;
let new_http_request = HttpRequest::from_parts(parts, body);
Request::try_from(new_http_request).context("Failed to create request")
}
}
impl GcpIamAuth {
pub async fn build_request(&self, http_request: HttpRequest<Body>) -> anyhow::Result<Request> {
static CACHED_PROVIDER: OnceLock<Arc<google::DefaultCredentialProvider>> = OnceLock::new();
let provider = CACHED_PROVIDER
.get_or_init(|| Arc::new(google::DefaultCredentialProvider::new()))
.clone();
let signer =
google::default_signer(self.service.deref()).with_credential_provider(provider);
let (mut parts, body) = http_request.into_parts();
signer
.sign(&mut parts, None)
.await
.context("Failed to sign request body")?;
let new_http_request = HttpRequest::from_parts(parts, body);
Request::try_from(new_http_request).context("Failed to create request")
}
}
impl AzureIamAuth {
pub async fn build_request(&self, http_request: HttpRequest<Body>) -> anyhow::Result<Request> {
static CACHED_PROVIDER: OnceLock<Arc<azure::DefaultCredentialProvider>> = OnceLock::new();
let provider = CACHED_PROVIDER
.get_or_init(|| Arc::new(azure::DefaultCredentialProvider::new()))
.clone();
let signer = azure::default_signer().with_credential_provider(provider);
let (mut parts, body) = http_request.into_parts();
signer
.sign(&mut parts, None)
.await
.context("Failed to sign request body")?;
let new_http_request = HttpRequest::from_parts(parts, body);
Request::try_from(new_http_request).context("Failed to create request")
}
}
#[cfg(all(test, not(miri)))]
mod tests {
use super::*;
use crate::nodes::function::v2::module::http::auth::AwsRegion;
use ::http::Request as HttpRequest;
use reqwest::Body;
#[tokio::test]
async fn aws_iam_produces_sigv4_authorization_header() {
std::env::set_var("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE");
std::env::set_var(
"AWS_SECRET_ACCESS_KEY",
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
);
std::env::set_var("AWS_REGION", "us-east-1");
let auth = AwsIamAuth {
region: AwsRegion("us-east-1".into()),
service: "s3".into(),
};
let req = HttpRequest::builder()
.method("GET")
.uri("https://example-bucket.s3.amazonaws.com/key")
.body(Body::from("hello"))
.expect("build request");
let signed = auth
.build_request(req)
.await
.expect("signing should succeed");
assert!(
signed.headers().contains_key("authorization"),
"expected Authorization header"
);
assert!(signed.headers().contains_key("x-amz-content-sha256"));
assert!(signed.headers().contains_key("x-amz-date"));
let auth_hdr = signed
.headers()
.get("authorization")
.expect("auth header present")
.to_str()
.expect("auth header is ascii");
assert!(
auth_hdr.starts_with("AWS4-HMAC-SHA256"),
"unexpected auth scheme: {auth_hdr}"
);
}
#[tokio::test]
async fn gcp_iam_build_request_does_not_panic() {
let auth = GcpIamAuth {
service: "storage".into(),
};
let req = HttpRequest::builder()
.method("GET")
.uri("https://storage.googleapis.com/b/foo/o/bar")
.body(Body::from(""))
.expect("build request");
let _ = auth.build_request(req).await;
}
#[tokio::test]
#[ignore]
async fn azure_iam_build_request_does_not_panic() {
let auth = AzureIamAuth;
let req = HttpRequest::builder()
.method("GET")
.uri("https://account.blob.core.windows.net/container/blob")
.body(Body::from(""))
.expect("build request");
let _ = auth.build_request(req).await;
}
}