Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom credential chain not respecting profile_name property #788

Closed
xrl opened this issue Apr 14, 2023 · 7 comments
Closed

Custom credential chain not respecting profile_name property #788

xrl opened this issue Apr 14, 2023 · 7 comments
Labels
feature-request A feature should be added or improved. p3 This is a minor priority issue

Comments

@xrl
Copy link

xrl commented Apr 14, 2023

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_
@xrl xrl added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Apr 14, 2023
@Velfi
Copy link
Contributor

Velfi commented Apr 17, 2023

Hey @xrl, thanks for submitting this issue. We'll add it to our backlog.

@Velfi Velfi removed the needs-triage This issue or PR still needs to be triaged. label Apr 17, 2023
@rcoh
Copy link
Contributor

rcoh commented Apr 17, 2023

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.

@xrl
Copy link
Author

xrl commented Apr 17, 2023

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,
}

@rcoh
Copy link
Contributor

rcoh commented Aug 8, 2023

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.

@rcoh rcoh added feature-request A feature should be added or improved. and removed bug This issue is a bug. labels Aug 8, 2023
@jmklix jmklix added the p3 This is a minor priority issue label Jan 19, 2024
@anatoli-iliev
Copy link

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!

@jmklix
Copy link
Member

jmklix commented Apr 26, 2024

Closing this for now, as this isn't something that we plan on supporting in the near future.

@jmklix jmklix closed this as not planned Won't fix, can't repro, duplicate, stale Apr 26, 2024
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request A feature should be added or improved. p3 This is a minor priority issue
Projects
None yet
Development

No branches or pull requests

5 participants