Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions services/aws-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ repository.workspace = true
rust-version.workspace = true

[dependencies]
asyncband = { workspace = true }
bytes = { workspace = true }
futures = { workspace = true }
futures-timer = { version = "3", features = ["wasm-bindgen"] }
Expand Down
54 changes: 31 additions & 23 deletions services/aws-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,24 +99,27 @@ impl SharedConfig {
.or_else(|| ctx.env_var("AWS_DEFAULT_REGION"))
}

pub(crate) async fn load_config_file(&self, ctx: &Context) -> Result<Ini> {
read_ini(
pub(crate) fn config_file_path(&self, ctx: &Context) -> Option<String> {
file_path(
ctx,
self.config_file.as_deref(),
"AWS_CONFIG_FILE",
"~/.aws/config",
)
.await
}

pub(crate) async fn load_config_file(&self, ctx: &Context) -> Result<Ini> {
read_ini(ctx, self.config_file_path(ctx)).await
}

pub(crate) async fn load_credentials_file(&self, ctx: &Context) -> Result<Ini> {
read_ini(
let path = file_path(
ctx,
self.credentials_file.as_deref(),
"AWS_SHARED_CREDENTIALS_FILE",
"~/.aws/credentials",
)
.await
);
read_ini(ctx, path).await
}

/// Read and merge the selected profile. This never contacts IMDS, even when enabled.
Expand Down Expand Up @@ -221,28 +224,33 @@ pub(crate) fn config_section(profile: &str) -> String {
}
}

async fn read_ini(ctx: &Context, explicit: Option<&str>, env: &str, default: &str) -> Result<Ini> {
let path = explicit
.map(str::to_owned)
.or_else(|| ctx.env_var(env))
.unwrap_or_else(|| default.into());
let Some(path) = ctx.expand_home_dir(&path) else {
fn aws_parse_options() -> ParseOption {
// Preserve literal commands and keep nested settings distinct from profile properties.
ParseOption {
enabled_quote: false,
enabled_escape: false,
enabled_indented_mutiline_value: true,
..Default::default()
}
}

async fn read_ini(ctx: &Context, path: Option<String>) -> Result<Ini> {
let Some(path) = path else {
return Ok(Ini::new());
};
let Ok(content) = ctx.file_read(&path).await else {
return Ok(Ini::new());
};
let content = std::str::from_utf8(&content)
.map_err(|_| Error::config_invalid("AWS shared configuration is not UTF-8"))?;
// Commands contain literal quotes/backslashes; nested settings belong to their parent value.
Ini::load_from_str_opt(
content,
ParseOption {
enabled_quote: false,
enabled_escape: false,
enabled_indented_mutiline_value: true,
..Default::default()
},
)
.map_err(|_| Error::config_invalid("failed to parse AWS shared configuration"))
Ini::load_from_str_opt(content, aws_parse_options())
.map_err(|_| Error::config_invalid("failed to parse AWS shared configuration"))
}

fn file_path(ctx: &Context, explicit: Option<&str>, env: &str, default: &str) -> Option<String> {
let path = explicit
.map(str::to_owned)
.or_else(|| ctx.env_var(env))
.unwrap_or_else(|| default.into());
ctx.expand_home_dir(&path)
}
49 changes: 35 additions & 14 deletions services/aws-core/src/provide_credential/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ use reqsign_core::{Context, ProvideCredential, ProvideCredentialChain, Result};
/// 5. Process credentials
/// 6. ECS (IAM Roles for Tasks) & Container credentials
/// 7. EC2 IMDSv2
///
/// Once SSO configuration is selected, SSO errors stop resolution to avoid
/// silently switching identities. An absent SSO configuration continues the chain.
#[derive(Debug)]
pub struct DefaultCredentialProvider {
chain: ProvideCredentialChain<Credential>,
#[cfg(not(target_arch = "wasm32"))]
sso: Option<SSOCredentialProvider>,
fallback: ProvideCredentialChain<Credential>,
}

impl Default for DefaultCredentialProvider {
Expand All @@ -59,7 +65,12 @@ impl DefaultCredentialProvider {

/// Create with a custom credential chain.
pub fn with_chain(chain: ProvideCredentialChain<Credential>) -> Self {
Self { chain }
Self {
chain,
#[cfg(not(target_arch = "wasm32"))]
sso: None,
fallback: ProvideCredentialChain::new(),
}
}

/// Add a credential provider to the front of the default chain.
Expand Down Expand Up @@ -250,40 +261,50 @@ impl DefaultCredentialProviderBuilder {
chain = chain.push(p);
}

#[cfg(not(target_arch = "wasm32"))]
{
if let Some(p) = self.sso {
chain = chain.push(p);
}
}
let mut fallback = ProvideCredentialChain::new();

if let Some(p) = self.web_identity {
chain = chain.push(p);
fallback = fallback.push(p);
}

#[cfg(not(target_arch = "wasm32"))]
{
if let Some(p) = self.process {
chain = chain.push(p);
fallback = fallback.push(p);
}
}

if let Some(p) = self.ecs {
chain = chain.push(p);
fallback = fallback.push(p);
}

if let Some(p) = self.imds {
chain = chain.push(p);
fallback = fallback.push(p);
}

DefaultCredentialProvider::with_chain(chain)
DefaultCredentialProvider {
chain,
#[cfg(not(target_arch = "wasm32"))]
sso: self.sso,
fallback,
}
}
}
impl ProvideCredential for DefaultCredentialProvider {
type Credential = Credential;

async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
self.chain.provide_credential(ctx).await
if let Some(credential) = self.chain.provide_credential(ctx).await? {
return Ok(Some(credential));
}
// SSO selects an identity. Its failure must not switch to a later source.
#[cfg(not(target_arch = "wasm32"))]
if let Some(sso) = &self.sso {
if let Some(credential) = sso.provide_credential(ctx).await? {
return Ok(Some(credential));
}
}
self.fallback.provide_credential(ctx).await
}
}

Expand Down Expand Up @@ -641,7 +662,7 @@ mod tests {
.expect_err("selected SSO profile must be loaded before cache lookup");
assert_eq!(ErrorKind::ConfigInvalid, error.kind());
assert_eq!(
"No valid SSO token found. Please run 'aws sso login' first",
"SSO token cache not found. Please run 'aws sso login' for the selected profile",
error.to_string()
);

Expand Down
Loading
Loading