use super::sigv4::{constant_time_eq, parse_sigv4_auth, verify_sigv4_signature};
use super::TemporaryCredentialResolver;
use crate::error::ProxyError;
use crate::registry::CredentialRegistry;
use crate::types::{AuthenticatedIdentity, ResolvedIdentity};
use http::HeaderMap;
pub async fn resolve_identity<C: CredentialRegistry>(
method: &http::Method,
uri_path: &str,
query_string: &str,
headers: &HeaderMap,
config: &C,
credential_resolver: Option<&dyn TemporaryCredentialResolver>,
) -> Result<ResolvedIdentity, ProxyError> {
let auth_header = match headers.get("authorization").and_then(|v| v.to_str().ok()) {
Some(h) => h,
None => return Ok(ResolvedIdentity::Anonymous),
};
let sig = parse_sigv4_auth(auth_header)?;
let payload_hash = headers
.get("x-amz-content-sha256")
.and_then(|v| v.to_str().ok())
.unwrap_or("UNSIGNED-PAYLOAD");
if let Some(session_token) = headers
.get("x-amz-security-token")
.and_then(|v| v.to_str().ok())
{
let resolver = credential_resolver.ok_or_else(|| {
tracing::warn!("session token present but no credential resolver configured");
ProxyError::AccessDenied
})?;
match resolver.resolve(session_token)? {
Some(creds) => {
if !constant_time_eq(sig.access_key_id.as_bytes(), creds.access_key_id.as_bytes()) {
tracing::warn!(
header_key = %sig.access_key_id,
resolved_key = %creds.access_key_id,
"access key mismatch between auth header and session token"
);
return Err(ProxyError::AccessDenied);
}
if !verify_sigv4_signature(
method,
uri_path,
query_string,
headers,
&sig,
&creds.secret_access_key,
payload_hash,
)? {
return Err(ProxyError::SignatureDoesNotMatch);
}
tracing::debug!(
access_key = %creds.access_key_id,
role = %creds.assumed_role_id,
scopes = ?creds.allowed_scopes,
"temporary credential identity resolved"
);
return Ok(ResolvedIdentity::Authenticated(AuthenticatedIdentity {
principal_name: creds.source_identity.clone(),
allowed_scopes: creds.allowed_scopes.clone(),
}));
}
None => {
tracing::warn!(
access_key_id = %sig.access_key_id,
token_len = session_token.len(),
"session token could not be resolved — possible key mismatch, token corruption, or expired key rotation"
);
return Err(ProxyError::AccessDenied);
}
}
}
if let Some(cred) = config.get_credential(&sig.access_key_id).await? {
if !cred.enabled {
return Err(ProxyError::AccessDenied);
}
if let Some(expires) = cred.expires_at {
if expires <= chrono::Utc::now() {
return Err(ProxyError::ExpiredCredentials);
}
}
if !verify_sigv4_signature(
method,
uri_path,
query_string,
headers,
&sig,
&cred.secret_access_key,
payload_hash,
)? {
return Err(ProxyError::SignatureDoesNotMatch);
}
return Ok(ResolvedIdentity::Authenticated(AuthenticatedIdentity {
principal_name: cred.principal_name.clone(),
allowed_scopes: cred.allowed_scopes,
}));
}
Err(ProxyError::AccessDenied)
}