Skip to content

Commit

Permalink
refactor: Migrate aws-v4 to new design (#502)
Browse files Browse the repository at this point in the history
  • Loading branch information
Xuanwo authored Nov 4, 2024
1 parent 4577deb commit 0a630dc
Show file tree
Hide file tree
Showing 24 changed files with 1,409 additions and 1,263 deletions.
3 changes: 1 addition & 2 deletions core/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use super::SigningRequest;
use crate::Context;
use std::fmt::Debug;
use std::time::Duration;
Expand Down Expand Up @@ -61,5 +60,5 @@ pub trait Build: Debug + Send + Sync + Unpin + 'static {
req: &mut http::request::Parts,
key: Option<&Self::Key>,
expires_in: Option<Duration>,
) -> anyhow::Result<SigningRequest>;
) -> anyhow::Result<()>;
}
16 changes: 16 additions & 0 deletions core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,28 @@ impl Context {
self.fs.file_read(path).await
}

/// Read the file content entirely in `String`.
pub async fn file_read_as_string(&self, path: &str) -> Result<String> {
let bytes = self.file_read(path).await?;
Ok(String::from_utf8_lossy(&bytes).to_string())
}

/// Send http request and return the response.
#[inline]
pub async fn http_send(&self, req: http::Request<Bytes>) -> Result<http::Response<Bytes>> {
self.http.http_send(req).await
}

/// Send http request and return the response as string.
pub async fn http_send_as_string(
&self,
req: http::Request<Bytes>,
) -> Result<http::Response<String>> {
let (parts, body) = self.http.http_send(req).await?.into_parts();
let body = String::from_utf8_lossy(&body).to_string();
Ok(http::Response::from_parts(parts, body))
}

/// Get the home directory of the current user.
#[inline]
pub fn home_dir(&self) -> Option<PathBuf> {
Expand Down
2 changes: 1 addition & 1 deletion core/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt::Debug;
use std::path::PathBuf;

/// Permits parameterizing the home functions via the _from variants
pub trait Env: Debug + 'static {
pub trait Env: Debug + Send + Sync + 'static {
/// Get an environment variable.
///
/// - Returns `Some(v)` if the environment variable is found and is valid utf-8.
Expand Down
2 changes: 1 addition & 1 deletion core/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::fmt::Debug;
///
/// This could be used by `Load` to load the credential from the file.
#[async_trait::async_trait]
pub trait FileRead: Debug + 'static {
pub trait FileRead: Debug + Send + Sync + 'static {
/// Read the file content entirely in `Vec<u8>`.
async fn file_read(&self, path: &str) -> Result<Vec<u8>>;
}
2 changes: 1 addition & 1 deletion core/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::fmt::Debug;
/// For example, fetch IMDS token from AWS or OAuth2 refresh token. This trait is designed
/// especially for the signer, please don't use it as a general http client.
#[async_trait::async_trait]
pub trait HttpSend: Debug + 'static {
pub trait HttpSend: Debug + Send + Sync + 'static {
/// Send http request and return the response.
async fn http_send(&self, req: http::Request<Bytes>) -> Result<http::Response<Bytes>>;
}
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod hash;
pub mod time;
pub mod utils;

mod context;
pub use context::Context;
Expand Down
1 change: 1 addition & 0 deletions core/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use http::Uri;
use std::str::FromStr;

/// Signing context for request.
#[derive(Debug)]
pub struct SigningRequest {
/// HTTP method.
pub method: Method,
Expand Down
6 changes: 2 additions & 4 deletions core/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ impl<K: Key> Signer<K> {
ctx
};

let signing = self
.builder
self.builder
.build(&self.ctx, req, key.as_ref(), expires_in)
.await?;
signing.apply(req)
.await
}
}
73 changes: 73 additions & 0 deletions core/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! Utility functions and types.
use std::fmt::Debug;

/// Redacts a string by replacing all but the first and last three characters with asterisks.
///
/// - If the input string has fewer than 12 characters, it should be entirely redacted.
/// - If the input string has 12 or more characters, only the first three and the last three.
///
/// This design is to allow users to distinguish between different redacted strings but avoid
/// leaking sensitive information.
pub struct Redact<'a>(&'a str);

impl<'a> From<&'a str> for Redact<'a> {
fn from(value: &'a str) -> Self {
Redact(value)
}
}

impl<'a> From<&'a String> for Redact<'a> {
fn from(value: &'a String) -> Self {
Redact(value.as_str())
}
}

impl<'a> From<&'a Option<String>> for Redact<'a> {
fn from(value: &'a Option<String>) -> Self {
match value {
None => Redact(""),
Some(v) => Redact(v),
}
}
}

impl<'a> Debug for Redact<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let length = self.0.len();
if length == 0 {
f.write_str("EMPTY")
} else if length < 12 {
f.write_str("***")
} else {
f.write_str(&self.0[..3])?;
f.write_str("***")?;
f.write_str(&self.0[length - 3..])
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_redact() {
let cases = vec![
("Short", "***"),
("Hello World!", "Hel***ld!"),
("This is a longer string", "Thi***ing"),
("", "EMPTY"),
("HelloWorld", "***"),
];

for (input, expected) in cases {
assert_eq!(
format!("{:?}", Redact(input)),
expected,
"Failed on input: {}",
input
);
}
}
}
1 change: 1 addition & 0 deletions services/aws-v4/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ reqwest.workspace = true
rust-ini.workspace = true
serde.workspace = true
serde_json.workspace = true
bytes = "1.7.2"

[dev-dependencies]
aws-credential-types = "1.1.8"
Expand Down
23 changes: 19 additions & 4 deletions services/aws-v4/benches/aws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,24 @@ use aws_sigv4::sign::v4::SigningParams;
use criterion::criterion_group;
use criterion::criterion_main;
use criterion::Criterion;
use once_cell::sync::Lazy;
use reqsign_aws_v4::Builder as AwsV4Builder;
use reqsign_aws_v4::Credential as AwsCredential;
use reqsign_aws_v4::Signer as AwsV4Signer;
use reqsign_core::{Build, Context};
use reqsign_file_read_tokio::TokioFileRead;
use reqsign_http_send_reqwest::ReqwestHttpSend;

criterion_group!(benches, bench);
criterion_main!(benches);

static RUNTIME: Lazy<tokio::runtime::Runtime> = Lazy::new(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.expect("must success")
});

pub fn bench(c: &mut Criterion) {
let mut group = c.benchmark_group("aws_v4");

Expand All @@ -25,17 +37,20 @@ pub fn bench(c: &mut Criterion) {
..Default::default()
};

let s = AwsV4Signer::new("s3", "test");
let s = AwsV4Builder::new("s3", "test");
let ctx = Context::new(TokioFileRead, ReqwestHttpSend::default());

b.iter(|| {
b.to_async(&*RUNTIME).iter(|| async {
let mut req = http::Request::new("");
*req.method_mut() = http::Method::GET;
*req.uri_mut() = "http://127.0.0.1:9000/hello"
.parse()
.expect("url must be valid");

let (mut parts, _) = req.into_parts();
s.sign(&mut parts, &cred).expect("must success")
s.build(&ctx, &mut parts, Some(&cred), None)
.await
.expect("must success")
})
});

Expand Down
Loading

0 comments on commit 0a630dc

Please sign in to comment.