#[cfg(feature = "clap")]
pub mod clap;
use aws_config::{BehaviorVersion, Region};
use aws_credential_types::provider::error::CredentialsError;
use aws_sdk_s3::config::{Credentials, ProvideCredentials};
use rattler_networking::{Authentication, AuthenticationStorage};
use url::Url;
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum S3AddressingStyle {
#[default]
VirtualHost,
Path,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct S3Credentials {
pub endpoint_url: Url,
pub region: String,
#[cfg_attr(feature = "serde", serde(default))]
pub addressing_style: S3AddressingStyle,
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
pub session_token: Option<String>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResolvedS3Credentials {
pub endpoint_url: Url,
pub region: String,
pub addressing_style: S3AddressingStyle,
pub access_key_id: String,
pub secret_access_key: String,
pub session_token: Option<String>,
}
#[derive(Debug, thiserror::Error)]
pub enum FromSDKError {
#[error("No credentials provider found in AWS SDK configuration")]
NoCredentialsProvider,
#[error("Could not determine region from AWS SDK configuration")]
MissingRegion,
#[error("Could not determine endpoint from AWS SDK configuration")]
MissingEndpoint,
#[error("Failed to parse endpoint from AWS SDK configuration")]
InvalidEndpoint(#[source] url::ParseError),
#[error(transparent)]
CredentialsError(CredentialsError),
}
impl ResolvedS3Credentials {
pub async fn from_sdk() -> Result<Self, FromSDKError> {
let config = aws_config::defaults(BehaviorVersion::latest()).load().await;
let s3_config = aws_sdk_s3::config::Builder::from(&config).build();
let region = s3_config
.region()
.map(Region::to_string)
.ok_or(FromSDKError::MissingRegion)?;
let endpoint_url_str = config.endpoint_url().unwrap_or("https://s3.amazonaws.com");
let endpoint_url = Url::parse(endpoint_url_str).map_err(FromSDKError::InvalidEndpoint)?;
let Some(credentials_provider) = config.credentials_provider() else {
return Err(FromSDKError::NoCredentialsProvider);
};
let credentials: Credentials = credentials_provider
.provide_credentials()
.await
.map_err(FromSDKError::CredentialsError)?;
let access_key_id = credentials.access_key_id().to_string();
let secret_access_key = credentials.secret_access_key().to_string();
let session_token = credentials.session_token().map(ToString::to_string);
let addressing_style = S3AddressingStyle::default();
Ok(Self {
endpoint_url,
region,
addressing_style,
access_key_id,
secret_access_key,
session_token,
})
}
}
impl S3Credentials {
pub fn resolve(
self,
bucket_url: &Url,
auth_storage: &AuthenticationStorage,
) -> Option<ResolvedS3Credentials> {
let (access_key_id, secret_access_key, session_token) =
if let (Some(access_key_id), Some(secret_access_key)) =
(self.access_key_id, self.secret_access_key)
{
(access_key_id, secret_access_key, self.session_token)
} else if let Some((access_key_id, secret_access_key, session_token)) =
load_s3_credentials_from_auth_storage(auth_storage, bucket_url.clone())
{
(access_key_id, secret_access_key, session_token)
} else {
return None;
};
Some(ResolvedS3Credentials {
endpoint_url: self.endpoint_url,
region: self.region,
access_key_id,
secret_access_key,
session_token,
addressing_style: self.addressing_style,
})
}
}
fn load_s3_credentials_from_auth_storage(
auth_storage: &AuthenticationStorage,
channel: Url,
) -> Option<(String, String, Option<String>)> {
let auth = auth_storage.get_by_url(channel).ok()?;
if let (
_,
Some(Authentication::S3Credentials {
access_key_id,
secret_access_key,
session_token,
}),
) = auth
{
Some((access_key_id, secret_access_key, session_token))
} else {
None
}
}