Custom credential chain not respecting profile_name property
Describe the bug
I build a workflow tool for my team -- one tool which is used in CI and on our individual machines. Our permissioning system is a little unfortunate, the CI system is trusted by multiple accounts with a cross account trust policy but our individual machines needs N tokens for the N AWS accounts we interact with.
I need to activate multiple profiles inside my code when it runs on a dev's computer. And when this code is run on our servers it needs to use IMDSv1 -- we are still using kube2iam. So I wanted to write a credential provider chain which prefers IMDSv1 but can also activate multiple profiles:
let mut ecr_clients = vec![];
for (stripe, default_region, aws_account_id) in stripe_details {
log::info!(
"aws_config::from_env() with overrides: profile_name={stripe} region={default_region}"
);
let config_builder = aws_config::from_env()
.profile_name(stripe)
.region(default_region)
.credentials_provider(imdsv1_chain().await);
let config = config_builder.load().await;
let ecr_client = aws_sdk_ecr::Client::new(&config);
ecr_clients.push((ecr_client, default_region.into(), stripe, aws_account_id))
}
Ok(ecr_clients)
And the chain is defined like:
pub async fn imdsv1_chain() -> impl ProvideCredentials {
let chain = CredentialsProviderChain::first_try("IMDSv1", IMDSv1Provider)
.or_default_provider()
.await;
chain
}
which I understand builds a nested chain like:
[imdsv1, [env, profile, ...]]
the issue is that setting profile_name on the root chain doesn't send the profile_name in to the nested chain.
When I run the code above:
% RUST_LOG=trace cargo run -- ecr docker-login --stripe-group=ids --only=ids-dev
[[[ SNIP ]]]
[2023-04-14T15:38:48Z DEBUG aws_config::meta::credentials::chain] provider in chain did not provide credentials provider=DefaultProviderChain context=the credential provider was not enabled: no providers in chain provided credentials (CredentialsNotLoaded(CredentialsNotLoaded { source: "no providers in chain provided credentials" }))
but if I force the profile as an ENV var:
% AWS_PROFILE=ids-dev RUST_LOG=trace cargo run -- ecr docker-login --stripe-group=ids --only=ids-dev
[2023-04-14T15:43:10Z INFO aws_config::profile::credentials] constructed abstract provider from config file chain=ProfileChain { base: AccessKey(Credentials { provider_name: "ProfileFile", access_key_id: "** redacted **", secret_access_key: "** redacted **" }), chain: [] }
so the ENV var punches through the layers and things work. How can I get the profile_name property to inherit in to the default chain?
Expected Behavior
I would expect the profile_name to go in to the fall-through, wrapped chain.
Current Behavior
The chain fails because profile_name isn't propagate. Setting AWS_PROFILE proves it could work if propagated.
Reproduction Steps
See first note.
Possible Solution
No response
Additional Information/Context
No response
Version
% cargo tree | grep aws-
├── aws-arn v0.2.1
├── aws-config v0.55.1
│ ├── aws-credential-types v0.55.1
│ │ ├── aws-smithy-async v0.55.1
│ │ ├── aws-smithy-types v0.55.1
│ ├── aws-http v0.55.1
│ │ ├── aws-credential-types v0.55.1 (*)
│ │ ├── aws-smithy-http v0.55.1
│ │ │ ├── aws-smithy-types v0.55.1 (*)
│ │ ├── aws-smithy-types v0.55.1 (*)
│ │ ├── aws-types v0.55.1
│ │ │ ├── aws-credential-types v0.55.1 (*)
│ │ │ ├── aws-smithy-async v0.55.1 (*)
│ │ │ ├── aws-smithy-client v0.55.1
│ │ │ │ ├── aws-smithy-async v0.55.1 (*)
│ │ │ │ ├── aws-smithy-http v0.55.1 (*)
│ │ │ │ ├── aws-smithy-http-tower v0.55.1
│ │ │ │ │ ├── aws-smithy-http v0.55.1 (*)
│ │ │ │ │ ├── aws-smithy-types v0.55.1 (*)
│ │ │ │ ├── aws-smithy-types v0.55.1 (*)
│ │ │ ├── aws-smithy-http v0.55.1 (*)
│ │ │ ├── aws-smithy-types v0.55.1 (*)
│ ├── aws-sdk-sso v0.26.0
│ │ ├── aws-credential-types v0.55.1 (*)
│ │ ├── aws-endpoint v0.55.1
│ │ │ ├── aws-smithy-http v0.55.1 (*)
│ │ │ ├── aws-smithy-types v0.55.1 (*)
│ │ │ ├── aws-types v0.55.1 (*)
│ │ ├── aws-http v0.55.1 (*)
│ │ ├── aws-sig-auth v0.55.1
│ │ │ ├── aws-credential-types v0.55.1 (*)
│ │ │ ├── aws-sigv4 v0.55.1
│ │ │ │ ├── aws-smithy-http v0.55.1 (*)
│ │ │ ├── aws-smithy-http v0.55.1 (*)
│ │ │ ├── aws-types v0.55.1 (*)
│ │ ├── aws-smithy-async v0.55.1 (*)
│ │ ├── aws-smithy-client v0.55.1 (*)
│ │ ├── aws-smithy-http v0.55.1 (*)
│ │ ├── aws-smithy-http-tower v0.55.1 (*)
│ │ ├── aws-smithy-json v0.55.1
│ │ │ └── aws-smithy-types v0.55.1 (*)
│ │ ├── aws-smithy-types v0.55.1 (*)
│ │ ├── aws-types v0.55.1 (*)
│ ├── aws-sdk-sts v0.26.0
│ │ ├── aws-credential-types v0.55.1 (*)
│ │ ├── aws-endpoint v0.55.1 (*)
│ │ ├── aws-http v0.55.1 (*)
│ │ ├── aws-sig-auth v0.55.1 (*)
│ │ ├── aws-smithy-async v0.55.1 (*)
│ │ ├── aws-smithy-client v0.55.1 (*)
│ │ ├── aws-smithy-http v0.55.1 (*)
│ │ ├── aws-smithy-http-tower v0.55.1 (*)
│ │ ├── aws-smithy-json v0.55.1 (*)
│ │ ├── aws-smithy-query v0.55.1
│ │ │ ├── aws-smithy-types v0.55.1 (*)
│ │ ├── aws-smithy-types v0.55.1 (*)
│ │ ├── aws-smithy-xml v0.55.1
│ │ ├── aws-types v0.55.1 (*)
│ ├── aws-smithy-async v0.55.1 (*)
│ ├── aws-smithy-client v0.55.1 (*)
│ ├── aws-smithy-http v0.55.1 (*)
│ ├── aws-smithy-http-tower v0.55.1 (*)
│ ├── aws-smithy-json v0.55.1 (*)
│ ├── aws-smithy-types v0.55.1 (*)
│ ├── aws-types v0.55.1 (*)
├── aws-credential-types v0.55.1 (*)
├── aws-sdk-ecr v0.25.1
│ ├── aws-credential-types v0.55.1 (*)
│ ├── aws-endpoint v0.55.1 (*)
│ ├── aws-http v0.55.1 (*)
│ ├── aws-sig-auth v0.55.1 (*)
│ ├── aws-smithy-async v0.55.1 (*)
│ ├── aws-smithy-client v0.55.1 (*)
│ ├── aws-smithy-http v0.55.1 (*)
│ ├── aws-smithy-http-tower v0.55.1 (*)
│ ├── aws-smithy-json v0.55.1 (*)
│ ├── aws-smithy-types v0.55.1 (*)
│ ├── aws-types v0.55.1 (*)
├── aws-sdk-sts v0.25.1
│ ├── aws-credential-types v0.55.1 (*)
│ ├── aws-endpoint v0.55.1 (*)
│ ├── aws-http v0.55.1 (*)
│ ├── aws-sig-auth v0.55.1 (*)
│ ├── aws-smithy-async v0.55.1 (*)
│ ├── aws-smithy-client v0.55.1 (*)
│ ├── aws-smithy-http v0.55.1 (*)
│ ├── aws-smithy-http-tower v0.55.1 (*)
│ ├── aws-smithy-json v0.55.1 (*)
│ ├── aws-smithy-query v0.55.1 (*)
│ ├── aws-smithy-types v0.55.1 (*)
│ ├── aws-smithy-xml v0.55.1 (*)
│ ├── aws-types v0.55.1 (*)
├── aws-smithy-async v0.55.1 (*)
### Environment details (OS name and version, etc.)
OS X 13.3
### Logs
_No response_
Hey @xrl, thanks for submitting this issue. We'll add it to our backlog.
How does your IMDSv1 provider read from the profile? In general, since you're providing a black-box provider, there's no way we could override the profile within it.
Really hacky, just using reqwest:
use aws_config::meta::credentials::CredentialsProviderChain;
use aws_credential_types::provider::error::CredentialsError;
use aws_credential_types::provider::{self, future, ProvideCredentials};
use aws_credential_types::Credentials;
pub async fn imdsv1_chain() -> impl ProvideCredentials {
let chain = CredentialsProviderChain::first_try("IMDSv1", IMDSv1Provider)
.or_default_provider()
.await;
chain
}
#[derive(Debug)]
struct IMDSv1Provider;
impl IMDSv1Provider {
async fn load_credentials(&self) -> provider::Result {
let client = reqwest::ClientBuilder::default()
.timeout(std::time::Duration::from_millis(500))
.build()
.expect("build reqwest client");
let role_name = self
.get(
&client,
"http://169.254.169.254/latest/meta-data/iam/security-credentials/".into(),
)
.await?;
log::info!("fetched role name: {}", role_name);
let security_credentials_result = self
.get(
&client,
format!(
"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}"
),
)
.await?;
log::info!(
"fetched security credentials: {}",
security_credentials_result
);
let security_credentials =
serde_json::from_str::<IMDSv1Credentials>(&security_credentials_result)
.map_err(|err| CredentialsError::not_loaded(Box::new(err)))?;
log::info!("{:#?}", security_credentials);
Ok(Credentials::new(
security_credentials.access_key_id,
security_credentials.secret_access_key,
Some(security_credentials.token),
None,
"IMDSv1",
))
}
async fn get(&self, client: &reqwest::Client, url: String) -> Result<String, CredentialsError> {
let result = client.get(url).send().await;
let response = match result {
Ok(response) => response,
Err(err) => return Err(CredentialsError::not_loaded(Box::new(err))),
};
if !response.status().is_success() {
return Err(CredentialsError::not_loaded(eyre::eyre!(
"expected 200, got {}",
response.status()
)));
}
let body_bytes_result = response.bytes().await;
let body_bytes = match body_bytes_result {
Ok(bytes) => bytes,
Err(err) => return Err(CredentialsError::not_loaded(Box::new(err))),
};
Ok(String::from_utf8(body_bytes.to_vec()).unwrap())
}
}
impl ProvideCredentials for IMDSv1Provider {
fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials
where
Self: 'a,
{
future::ProvideCredentials::new(self.load_credentials())
}
}
#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct IMDSv1Credentials {
pub access_key_id: String,
pub code: String,
pub expiration: String,
pub last_updated: String,
pub secret_access_key: String,
pub token: String,
#[serde(rename = "Type")]
pub type_: String,
}
Got it—I think what we actually need in this case is more flexible ways of modifying the default credentials provider. The provider_config that's being instantiated isn't getting passed through to your provider since you're overridding the provider wholesale. What you can do in your case is call .configure(...) on your credentials provider chain so that you can set the profile name.
Hi team,
Kudos to you for the amazing work you do on the AWS SDK for Rust. There is one use case that is a fundamental for my team to use this SDK and it seems that it is not supported by SDK library for Rust at this time. It is supported by AWS SDK for Java and we use it extensively, but unfortunately it is missing the Rust SDK.
We are working with a custom Kubernetes provider we have. Our resources within this k8s provider communicate and utilize various AWS services (S3, SQS, etc). So in order for our applications deployed in k8s to authenticate and use the AWS resources we are using kube2iam communication. We annotate our pods with certain specific AWS roles and the AWS SDK for Java works the authentication out of the box. Unfortunately that is not the case with the AWS SDK for Rust. It seems that it expect we to pass aws key id and secret to it either in profile or in env variables in order to work.
We have tried executing AWS S3 operations right from our kube2iam (annotated) pods using the AWS CLI and that works as expected without the need to do any login or sigin actions.
Please introduce kube2iam authentication out of the box support in the AWS SDK for Rust!
Closing this for now, as this isn't something that we plan on supporting in the near future.
Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.