Skip to main content

entelix_cloud/bedrock/
signer.rs

1//! SigV4 request signer for AWS Bedrock.
2//!
3//! Wraps `aws-sigv4`'s low-level signing API into a single
4//! `sign_request` call that takes a method/URL/headers/body and
5//! returns the signed `HeaderMap` (with `authorization`,
6//! `x-amz-date`, optional `x-amz-security-token` injected).
7//!
8//! The signer is stateless — credentials are passed per call so
9//! callers can refresh the credential snapshot before each
10//! invocation without rebuilding the signer.
11
12use aws_credential_types::Credentials;
13use aws_sigv4::http_request::{SignableBody, SignableRequest, SigningSettings, sign};
14use aws_sigv4::sign::v4;
15
16use crate::CloudError;
17
18const SERVICE_NAME: &str = "bedrock";
19
20/// Stateless SigV4 signer for Bedrock requests.
21#[derive(Clone, Debug)]
22pub struct BedrockSigner {
23    region: String,
24}
25
26impl BedrockSigner {
27    /// Build a signer for the given AWS region (`us-east-1`,
28    /// `eu-west-1`, etc.).
29    pub fn new(region: impl Into<String>) -> Self {
30        Self {
31            region: region.into(),
32        }
33    }
34
35    /// Borrow the configured region.
36    pub fn region(&self) -> &str {
37        &self.region
38    }
39
40    /// Sign a request. Returns header `(name, value)` pairs that the
41    /// caller appends to the outgoing request — the signer never
42    /// owns the underlying HTTP client.
43    pub fn sign_request(
44        &self,
45        creds: &Credentials,
46        method: &str,
47        url: &str,
48        headers: &[(String, String)],
49        body: &[u8],
50    ) -> Result<Vec<(String, String)>, CloudError> {
51        let identity = creds.clone().into();
52        let signing_settings = SigningSettings::default();
53        let signing_params = v4::SigningParams::builder()
54            .identity(&identity)
55            .region(&self.region)
56            .name(SERVICE_NAME)
57            .time(std::time::SystemTime::now())
58            .settings(signing_settings)
59            .build()
60            .map_err(|e| CloudError::Signing {
61                message: format!("build signing params: {e}"),
62                source: Some(Box::new(e)),
63            })?
64            .into();
65
66        let header_pairs: Vec<(&str, &str)> = headers
67            .iter()
68            .map(|(n, v)| (n.as_str(), v.as_str()))
69            .collect();
70        let signable = SignableRequest::new(
71            method,
72            url,
73            header_pairs.into_iter(),
74            SignableBody::Bytes(body),
75        )
76        .map_err(|e| CloudError::Signing {
77            message: format!("build signable request: {e}"),
78            source: Some(Box::new(e)),
79        })?;
80
81        let (instructions, _signature) = sign(signable, &signing_params)
82            .map_err(|e| CloudError::Signing {
83                message: format!("sign: {e}"),
84                source: Some(Box::new(e)),
85            })?
86            .into_parts();
87
88        let mut out = Vec::new();
89        let (sigv4_headers, _) = instructions.into_parts();
90        for header in sigv4_headers {
91            out.push((header.name().to_owned(), header.value().to_owned()));
92        }
93        Ok(out)
94    }
95}