#![cfg(feature = "aws-auth")]
pub mod common;
use aws_credential_types::{provider::SharedCredentialsProvider, Credentials as AwsCredentials};
use aws_smithy_async::time::StaticTimeSource;
use aws_types::region::Region;
use common::{server::MockServer, tracing_init};
use opensearch::{
http::{headers::HOST, transport::TransportBuilder},
indices::IndicesCreateParts,
};
use reqwest::header::HeaderValue;
use serde_json::json;
use test_case::test_case;
fn sigv4_config(transport: TransportBuilder, service_name: &str) -> TransportBuilder {
let aws_creds = AwsCredentials::new("test-access-key", "test-secret-key", None, None, "test");
let region = Region::new("ap-southeast-2");
let time_source = StaticTimeSource::from_secs(1673626117);
transport
.auth(opensearch::auth::Credentials::AwsSigV4(
SharedCredentialsProvider::new(aws_creds),
region,
))
.service_name(service_name)
.sigv4_time_source(time_source.into())
}
const LOCALHOST: HeaderValue = HeaderValue::from_static("localhost");
#[test_case("es", "10c9be415f4b9f15b12abbb16bd3e3730b2e6c76e0cf40db75d08a44ed04a3a1"; "when service name is es")]
#[test_case("aoss", "34903aef90423aa7dd60575d3d45316c6ef2d57bbe564a152b41bf8f5917abf6"; "when service name is aoss")]
#[test_case("arbitrary", "156e65c504ea2b2722a481b7515062e7692d27217b477828854e715f507e6a36"; "when service name is arbitrary")]
#[tokio::test]
async fn aws_auth_signs_correctly(
service_name: &str,
expected_signature: &str,
) -> anyhow::Result<()> {
tracing_init();
let mut server = MockServer::start()?;
let host = format!("aaabbbcccddd111222333.ap-southeast-2.{service_name}.amazonaws.com");
let client =
server.client_with(|b| sigv4_config(b, service_name).header(HOST, host.parse().unwrap()));
let _ = client
.indices()
.create(IndicesCreateParts::Index("sample-index1"))
.body(json!({
"aliases": {
"sample-alias1": {}
},
"mappings": {
"properties": {
"age": {
"type": "integer"
}
}
},
"settings": {
"index.number_of_replicas": 1,
"index.number_of_shards": 2
}
}))
.send()
.await?;
let sent_req = server.received_request().await?;
assert_eq!(sent_req.header("accept"), Some("application/json"));
assert_eq!(sent_req.header("content-type"), Some("application/json"));
assert_eq!(sent_req.header("host"), Some(host.as_str()));
assert_eq!(sent_req.header("x-amz-date"), Some("20230113T160837Z"));
assert_eq!(
sent_req.header("x-amz-content-sha256"),
Some("4c770eaed349122a28302ff73d34437cad600acda5a9dd373efc7da2910f8564")
);
assert_eq!(sent_req.header("authorization"), Some(format!("AWS4-HMAC-SHA256 Credential=test-access-key/20230113/ap-southeast-2/{service_name}/aws4_request, SignedHeaders=accept;content-type;host;x-amz-content-sha256;x-amz-date, Signature={expected_signature}").as_str()));
Ok(())
}
#[tokio::test]
async fn aws_auth_get() -> anyhow::Result<()> {
tracing_init();
let mut server = MockServer::start()?;
let client = server.client_with(|b| sigv4_config(b, "custom").header(HOST, LOCALHOST));
let _ = client.ping().send().await?;
let sent_req = server.received_request().await?;
assert_eq!(sent_req.header("authorization"), Some("AWS4-HMAC-SHA256 Credential=test-access-key/20230113/ap-southeast-2/custom/aws4_request, SignedHeaders=accept;content-type;host;x-amz-content-sha256;x-amz-date, Signature=e5aa6e5d9e1b86b86ed31fbb10dd62b4e93423b77830f8189701421d3e9f65bd"));
assert_eq!(
sent_req.header("x-amz-content-sha256"),
Some("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
);
Ok(())
}
#[tokio::test]
async fn aws_auth_post() -> anyhow::Result<()> {
tracing_init();
let mut server = MockServer::start()?;
let client = server.client_with(|b| sigv4_config(b, "custom").header(HOST, LOCALHOST));
let _ = client
.index(opensearch::IndexParts::Index("movies"))
.body(serde_json::json!({
"title": "Moneyball",
"director": "Bennett Miller",
"year": 2011
}
))
.send()
.await?;
let sent_req = server.received_request().await?;
assert_eq!(
sent_req.header("x-amz-content-sha256"),
Some("f3a842f988a653a734ebe4e57c45f19293a002241a72f0b3abbff71e4f5297b9")
);
Ok(())
}