// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use crate::settings::ExternalKeyStore;
use axum::middleware::Next;
use axum::{
body::{Body, Bytes},
http::Request,
response::{IntoResponse, Response},
};
use chrono::Duration;
use lazy_static::lazy_static;
use scratchstack_aws_signature::{
sigv4_verify, Request as Sigv4Request, SigningKey, SigningKeyKind::KSecret,
};
use std::collections::HashMap;
use crate::settings::SETTINGS;
use crate::xks_proxy::ErrorName::{AuthenticationFailedException, InternalException};
use crate::xks_proxy::XksProxyResult;
use crate::{KMS_XKS_V1_PATH, URI_PATH_PING};
lazy_static! {
pub static ref XKSS: HashMap<&'static str, &'static ExternalKeyStore> = {
let map: HashMap<_, _> = SETTINGS
.external_key_stores
.iter()
.map(|external_key_store| {
(
external_key_store.uri_path_prefix.as_str(),
external_key_store,
)
})
.collect();
assert_eq!(
map.len(),
SETTINGS.external_key_stores.len(),
"Check configuration for duplicate uri path prefixes."
);
map
};
}
// https://github.com/tokio-rs/axum/blob/main/examples/print-request-response/src/main.rs#L40-L55
// https://discord.com/channels/500028886025895936/870760546109116496/941987388979310633
pub async fn sigv4_auth(req: Request
, next: Next) -> XksProxyResult {
// Compute the access key id based on the URI path prefix, if any.
let uri_path = &req.uri().path().to_owned();
if uri_path == URI_PATH_PING {
let res: Response = next.run(req).await;
return Ok(res);
}
let (parts, body) = req.into_parts();
let body_as_bytes: Option = hyper::body::to_bytes(body).await.ok();
let body_as_vec_u8: Option> = body_as_bytes.as_ref().map(|bytes| bytes.to_vec());
let sigv4_req = Sigv4Request::from_http_request_parts(&parts, body_as_vec_u8);
let gsk_req = sigv4_req
.to_get_signing_key_request(
KSecret,
SETTINGS.server.region.as_str(),
SETTINGS.server.service.as_str(),
)
.map_err(|signature_err| {
AuthenticationFailedException.as_axum_error(signature_err.to_string())
})?;
let xks = xks_by_uri_path(uri_path)?;
if xks.sigv4_access_key_id != gsk_req.access_key {
return Err(AuthenticationFailedException.as_axum_error(format!(
"Access key id {} not allowed under the URI path {uri_path}",
gsk_req.access_key
)));
}
let signing_key = SigningKey {
kind: KSecret,
key: xks.sigv4_secret_access_key.as_str().as_bytes().to_vec(),
};
let allowed_mismatch = Some(Duration::minutes(5));
if let Err(signature_error) = sigv4_verify(
&sigv4_req,
&signing_key,
allowed_mismatch,
SETTINGS.server.region.as_str(),
SETTINGS.server.service.as_str(),
) {
tracing::warn!("SigV4 failure: {signature_error}");
return Err(AuthenticationFailedException.as_axum_error(signature_error.to_string()));
}
// Recompose the request as needed by the axum framework to run
let bytes = body_as_bytes.unwrap_or_default();
let mut req = Request::from_parts(parts, Body::from(bytes));
req.extensions_mut().insert(xks.uri_path_prefix.clone());
let res: Response = next.run(req).await;
Ok(res)
}
fn xks_by_uri_path(uri_path: &str) -> XksProxyResult<&ExternalKeyStore> {
if let Some(pos) = uri_path.rfind(KMS_XKS_V1_PATH) {
let uri_path_prefix = &uri_path[0..pos];
if let Some(xks) = XKSS.get(uri_path_prefix) {
return Ok(xks);
}
}
// Defend against a theoretically impossible condition: the request should have
// already been rejected by the axum framework before execution ever gets here.
Err(InternalException.as_axum_error(format!(
"Failed to access keystore by the URI path {uri_path}"
)))
}