diff --git a/aws/rust-runtime/aws-config/Cargo.lock b/aws/rust-runtime/aws-config/Cargo.lock index 7a83f9c8edc..43aeda20cc5 100644 --- a/aws/rust-runtime/aws-config/Cargo.lock +++ b/aws/rust-runtime/aws-config/Cargo.lock @@ -51,7 +51,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-config" -version = "1.6.2" +version = "1.7.0" dependencies = [ "aws-credential-types", "aws-runtime", diff --git a/aws/rust-runtime/aws-config/Cargo.toml b/aws/rust-runtime/aws-config/Cargo.toml index 4de94928927..512df8ccfb8 100644 --- a/aws/rust-runtime/aws-config/Cargo.toml +++ b/aws/rust-runtime/aws-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-config" -version = "1.6.2" +version = "1.7.0" authors = [ "AWS Rust SDK Team ", "Russell Cohen ", diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index 6f38e63029d..75016fc6b10 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -733,8 +733,17 @@ pub(crate) mod test { .unwrap() } + pub(crate) fn imds_response_404() -> HttpResponse { + HttpResponse::try_from( + http::Response::builder() + .status(404) + .body(SdkBody::empty()) + .unwrap(), + ) + .unwrap() + } + pub(crate) fn make_imds_client(http_client: &StaticReplayClient) -> super::Client { - tokio::time::pause(); super::Client::builder() .configure( &ProviderConfig::no_configuration() diff --git a/aws/rust-runtime/aws-config/src/imds/credentials.rs b/aws/rust-runtime/aws-config/src/imds/credentials.rs index 07a28818103..8548edf4e6e 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -9,13 +9,17 @@ //! This credential provider will NOT fallback to IMDSv1. Ensure that IMDSv2 is enabled on your instances. use super::client::error::ImdsError; +use crate::environment::parse_bool; use crate::imds::{self, Client}; use crate::json_credentials::{parse_json_credentials, JsonCredentials, RefreshableCredentials}; use crate::provider_config::ProviderConfig; +use aws_credential_types::attributes::AccountId; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; use aws_credential_types::Credentials; +use aws_runtime::env_config::EnvConfigValue; use aws_smithy_async::time::SharedTimeSource; -use aws_types::os_shim_internal::Env; +use aws_smithy_types::error::display::DisplayErrorContext; +use aws_types::origin::Origin; use std::borrow::Cow; use std::error::Error as StdError; use std::fmt; @@ -44,16 +48,34 @@ impl StdError for ImdsCommunicationError { } } +// Enum representing the type of IMDS endpoint that the credentials provider should access +// when retrieving the IMDS profile name or credentials. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +enum ApiVersion { + #[default] + Unknown, + Extended, + Legacy, +} + +// A state maintained by the IMDS credentials provider to manage the retrieval of the IMDS profile name or credentials. +#[derive(Clone, Debug, Default)] +struct ProviderState { + api_version: ApiVersion, + resolved_profile: Option, +} + /// IMDSv2 Credentials Provider /// /// _Note: This credentials provider will NOT fallback to the IMDSv1 flow._ #[derive(Debug)] pub struct ImdsCredentialsProvider { client: Client, - env: Env, + provider_config: ProviderConfig, profile: Option, time_source: SharedTimeSource, last_retrieved_credentials: Arc>>, + provider_state: Arc>, } /// Builder for [`ImdsCredentialsProvider`] @@ -106,16 +128,16 @@ impl Builder { /// Create an [`ImdsCredentialsProvider`] from this builder. pub fn build(self) -> ImdsCredentialsProvider { let provider_config = self.provider_config.unwrap_or_default(); - let env = provider_config.env(); let client = self .imds_override .unwrap_or_else(|| imds::Client::builder().configure(&provider_config).build()); ImdsCredentialsProvider { client, - env, profile: self.profile_override, time_source: provider_config.time_source(), + provider_config, last_retrieved_credentials: Arc::new(RwLock::new(self.last_retrieved_credentials)), + provider_state: Arc::new(RwLock::new(ProviderState::default())), } } } @@ -143,21 +165,105 @@ impl ImdsCredentialsProvider { Builder::default() } - fn imds_disabled(&self) -> bool { - match self.env.get(super::env::EC2_METADATA_DISABLED) { - Ok(value) => value.eq_ignore_ascii_case("true"), - _ => false, + // Retrieve the value for "disable ec2 metadata". If the value is `true`, the method also returns + // the source that set it to `true`. + // + // This checks the following sources: + // 1. The environment variable `AWS_EC2_METADATA_DISABLED=true/false` + // 2. The profile key `disable_ec2_metadata=true/false` + async fn imds_disabled(&self) -> (bool, Origin) { + EnvConfigValue::new() + .env(super::env::EC2_METADATA_DISABLED) + .profile(super::profile_key::EC2_METADATA_DISABLED) + .validate_and_return_origin( + &self.provider_config.env(), + self.provider_config.profile().await, + parse_bool, + ) + .map_err( + |err| tracing::warn!(err = %DisplayErrorContext(&err), "invalid value for `disable ec2 metadata` setting"), + ) + .map(|(disabled, origin)| (disabled.unwrap_or_default(), origin)) + .unwrap_or_default() + } + + // Return a configured instance profile name. If the profile name is blank, the method returns + // a `CredentialsError`. + // + // This checks the following sources: + // 1. The profile name configured via [`Builder::profile`] + // 2. The environment variable `AWS_EC2_INSTANCE_PROFILE_NAME` + // 3. The profile key `ec2_instance_profile_name` + async fn configured_instance_profile_name( + &self, + ) -> Result>, CredentialsError> { + let configured = match &self.profile { + Some(profile) => Some(profile.into()), + None => EnvConfigValue::new() + .env(super::env::EC2_INSTANCE_PROFILE_NAME) + .profile(super::profile_key::EC2_INSTANCE_PROFILE_NAME) + .validate( + &self.provider_config.env(), + self.provider_config.profile().await, + |s| Ok::(s.to_owned()), + ) + .expect("validator is infallible") + .map(Cow::Owned), + }; + + match configured { + Some(configured) if configured.trim().is_empty() => Err(CredentialsError::not_loaded( + "blank profile name is not supported", + )), + otherwise => Ok(otherwise), + } + } + + fn uri_base(&self) -> &str { + let api_version = &self + .provider_state + .read() + .expect("write critical section does not cause panic") + .api_version; + use ApiVersion::*; + match api_version { + Legacy => "/latest/meta-data/iam/security-credentials/", + _ => "/latest/meta-data/iam/security-credentials-extended/", } } - /// Retrieve the instance profile from IMDS - async fn get_profile_uncached(&self) -> Result { - match self - .client - .get("/latest/meta-data/iam/security-credentials/") - .await + // Retrieve the instance profile from IMDS + // + // Starting with `ApiVersion::Unknown`, the method first attempts to retrive it using the extended API. + // If the call is successful, it updates `ProviderState` to remember that the extended API is functional and moves on. + // Otherwise, it updates `ProviderState` to the legacy mode and tries again. + // In the end, if the legacy API does not work either, the method gives up and returns a `CredentialsError`. + async fn resolve_profile_name(&self) -> Result, CredentialsError> { + if let Some(profile) = self.configured_instance_profile_name().await? { + return Ok(profile); + } + + if let Some(profile) = &self + .provider_state + .read() + .expect("write critical section does not cause panic") + .resolved_profile { - Ok(profile) => Ok(profile.as_ref().into()), + return Ok(profile.clone().into()); + } + + match self.client.get(self.uri_base()).await { + Ok(profile) => { + let state = &mut self + .provider_state + .write() + .expect("write critical section does not cause panic"); + state.resolved_profile = Some(profile.clone().into()); + if state.api_version == ApiVersion::Unknown { + state.api_version = ApiVersion::Extended; + } + Ok(Cow::Owned(profile.into())) + } Err(ImdsError::ErrorResponse(context)) if context.response().status().as_u16() == 404 => { @@ -165,7 +271,21 @@ impl ImdsCredentialsProvider { "received 404 from IMDS when loading profile information. \ Hint: This instance may not have an IAM role associated." ); - Err(CredentialsError::not_loaded("received 404 from IMDS")) + + { + let state = &mut self + .provider_state + .write() + .expect("write critical section does not cause panic"); + if state.api_version == ApiVersion::Unknown { + tracing::debug!("retrieving an IMDS profile name failed using the extended API, switching to the legacy API and trying again"); + state.api_version = ApiVersion::Legacy; + } else { + return Err(CredentialsError::not_loaded("received 404 from IMDS")); + } + } + + Box::pin(self.resolve_profile_name()).await } Err(ImdsError::FailedToLoadToken(context)) if context.is_dispatch_failure() => { Err(CredentialsError::not_loaded(ImdsCommunicationError { @@ -207,28 +327,50 @@ impl ImdsCredentialsProvider { } async fn retrieve_credentials(&self) -> provider::Result { - if self.imds_disabled() { - let err = format!( - "IMDS disabled by {} env var set to `true`", - super::env::EC2_METADATA_DISABLED - ); + if let (true, origin) = self.imds_disabled().await { + let err = format!("IMDS disabled by {origin} set to `true`",); tracing::debug!(err); return Err(CredentialsError::not_loaded(err)); } + tracing::debug!("loading credentials from IMDS"); - let profile: Cow<'_, str> = match &self.profile { - Some(profile) => profile.into(), - None => self.get_profile_uncached().await?.into(), - }; + + let profile = self.resolve_profile_name().await?; tracing::debug!(profile = %profile, "loaded profile"); - let credentials = self + + let credentials = match self .client - .get(format!( - "/latest/meta-data/iam/security-credentials/{}", - profile - )) + .get(format!("{uri_base}{profile}", uri_base = self.uri_base())) .await - .map_err(CredentialsError::provider_error)?; + { + Ok(credentials) => { + let state = &mut self.provider_state.write().expect("write critical section does not cause panic"); + if state.api_version == ApiVersion::Unknown { + state.api_version = ApiVersion::Extended; + } + Ok(credentials) + } + Err(ImdsError::ErrorResponse(raw)) if raw.response().status().as_u16() == 404 => { + { + let state = &mut self.provider_state.write().expect("write critical section does not cause panic"); + if state.api_version == ApiVersion::Unknown { + tracing::debug!("retrieving credentials failed using the extended API, switching to the legacy API and trying again"); + state.api_version = ApiVersion::Legacy; + } else if self.profile.is_none() { + tracing::debug!("retrieving credentials failed using {:?}, clearing cached profile and trying again", state.api_version); + state.resolved_profile = None; + } else { + return Err(CredentialsError::provider_error(ImdsError::ErrorResponse( + raw, + ))); + } + } + return Box::pin(self.retrieve_credentials()).await; + } + otherwise => otherwise, + } + .map_err(CredentialsError::provider_error)?; + match parse_json_credentials(credentials.as_ref()) { Ok(JsonCredentials::RefreshableCredentials(RefreshableCredentials { access_key_id, @@ -238,16 +380,15 @@ impl ImdsCredentialsProvider { expiration, .. })) => { - // TODO(IMDSv2.X): Use `account_id` once the design is finalized - let _ = account_id; let expiration = self.maybe_extend_expiration(expiration); - let creds = Credentials::new( - access_key_id, - secret_access_key, - Some(session_token.to_string()), - expiration.into(), - "IMDSv2", - ); + let mut builder = Credentials::builder() + .access_key_id(access_key_id) + .secret_access_key(secret_access_key) + .session_token(session_token) + .expiry(expiration) + .provider_name("IMDSv2"); + builder.set_account_id(account_id.map(AccountId::from)); + let creds = builder.build(); *self.last_retrieved_credentials.write().unwrap() = Some(creds.clone()); Ok(creds) } @@ -287,51 +428,246 @@ impl ImdsCredentialsProvider { mod test { use super::*; use crate::imds::client::test::{ - imds_request, imds_response, make_imds_client, token_request, token_response, + imds_request, imds_response, imds_response_404, make_imds_client, token_request, + token_response, }; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::ProvideCredentials; use aws_smithy_async::test_util::instant_time_and_sleep; use aws_smithy_http_client::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_types::body::SdkBody; + use std::convert::identity as IdentityFn; + use std::future::Future; + use std::pin::Pin; use std::time::{Duration, UNIX_EPOCH}; use tracing_test::traced_test; const TOKEN_A: &str = "token_a"; #[tokio::test] - async fn profile_is_not_cached() { - let http_client = StaticReplayClient::new(vec![ - ReplayEvent::new( - token_request("http://169.254.169.254", 21600), - token_response(21600, TOKEN_A), - ), - ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - imds_response(r#"profile-name"#), - ), - ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), - imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), - ), - ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - imds_response(r#"different-profile"#), - ), - ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/different-profile", TOKEN_A), - imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST2\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), - ), - ]); - let client = ImdsCredentialsProvider::builder() - .imds_client(make_imds_client(&http_client)) - .configure(&ProviderConfig::no_configuration()) + #[traced_test] + #[cfg(feature = "default-https-client")] + async fn warn_on_invalid_value_for_disable_ec2_metadata() { + let provider_config = + ProviderConfig::empty().with_env(aws_types::os_shim_internal::Env::from_slice(&[( + imds::env::EC2_METADATA_DISABLED, + "not-a-boolean", + )])); + let client = crate::imds::Client::builder() + .configure(&provider_config) .build(); - let creds1 = client.provide_credentials().await.expect("valid creds"); - let creds2 = client.provide_credentials().await.expect("valid creds"); - assert_eq!(creds1.access_key_id(), "ASIARTEST"); - assert_eq!(creds2.access_key_id(), "ASIARTEST2"); - http_client.assert_requests_match(&[]); + let provider = ImdsCredentialsProvider::builder() + .configure(&provider_config) + .imds_client(client) + .build(); + assert!(!provider.imds_disabled().await.0); + assert!(logs_contain( + "invalid value for `disable ec2 metadata` setting" + )); + assert!(logs_contain(imds::env::EC2_METADATA_DISABLED)); + } + + #[tokio::test] + #[traced_test] + #[cfg(feature = "default-https-client")] + async fn environment_priority_on_disable_ec2_metadata() { + let provider_config = ProviderConfig::empty() + .with_env(aws_types::os_shim_internal::Env::from_slice(&[( + imds::env::EC2_METADATA_DISABLED, + "TRUE", + )])) + .with_profile_config( + Some( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFiles::builder() + .with_file( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFileKind::Config, + "conf", + ) + .build(), + ), + None, + ) + .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[( + "conf", + "[default]\ndisable_ec2_metadata = false", + )])); + let client = crate::imds::Client::builder() + .configure(&provider_config) + .build(); + let provider = ImdsCredentialsProvider::builder() + .configure(&provider_config) + .imds_client(client) + .build(); + assert_eq!( + (true, Origin::shared_environment_variable()), + provider.imds_disabled().await + ); + } + + #[tokio::test] + #[traced_test] + #[cfg(feature = "default-https-client")] + async fn disable_ec2_metadata_via_profile_file() { + let provider_config = ProviderConfig::empty() + .with_profile_config( + Some( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFiles::builder() + .with_file( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFileKind::Config, + "conf", + ) + .build(), + ), + None, + ) + .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[( + "conf", + "[default]\ndisable_ec2_metadata = true", + )])); + let client = crate::imds::Client::builder() + .configure(&provider_config) + .build(); + let provider = ImdsCredentialsProvider::builder() + .configure(&provider_config) + .imds_client(client) + .build(); + assert_eq!( + (true, Origin::shared_profile_file()), + provider.imds_disabled().await + ); + } + + #[tokio::test] + #[traced_test] + #[cfg(feature = "default-https-client")] + async fn creds_provider_configuration_priority_on_ec2_instance_profile_name() { + let provider_config = ProviderConfig::empty() + .with_env(aws_types::os_shim_internal::Env::from_slice(&[( + imds::env::EC2_INSTANCE_PROFILE_NAME, + "profile-via-env", + )])) + .with_profile_config( + Some( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFiles::builder() + .with_file( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFileKind::Config, + "conf", + ) + .build(), + ), + None, + ) + .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[( + "conf", + "[default]\nec2_instance_profile_name = profile-via-profile-file", + )])); + + let client = crate::imds::Client::builder() + .configure(&provider_config) + .build(); + let provider = ImdsCredentialsProvider::builder() + .profile("profile-via-creds-provider") + .configure(&provider_config) + .imds_client(client.clone()) + .build(); + assert_eq!( + Some(Cow::Borrowed("profile-via-creds-provider")), + provider.configured_instance_profile_name().await.unwrap() + ); + + // negative test with a blank profile name + let provider = ImdsCredentialsProvider::builder() + .profile("") + .configure(&provider_config) + .imds_client(client) + .build(); + let err = provider + .configured_instance_profile_name() + .await + .err() + .unwrap(); + assert!(format!("{}", DisplayErrorContext(&err)) + .contains("blank profile name is not supported")); + } + + #[tokio::test] + #[traced_test] + #[cfg(feature = "default-https-client")] + async fn environment_priority_on_ec2_instance_profile_name() { + let provider_config = ProviderConfig::empty() + .with_env(aws_types::os_shim_internal::Env::from_slice(&[( + imds::env::EC2_INSTANCE_PROFILE_NAME, + "profile-via-env", + )])) + .with_profile_config( + Some( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFiles::builder() + .with_file( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFileKind::Config, + "conf", + ) + .build(), + ), + None, + ) + .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[( + "conf", + "[default]\nec2_instance_profile_name = profile-via-profile-file", + )])); + let client = crate::imds::Client::builder() + .configure(&provider_config) + .build(); + let provider = ImdsCredentialsProvider::builder() + .configure(&provider_config) + .imds_client(client) + .build(); + assert_eq!( + Some(Cow::Borrowed("profile-via-env")), + provider.configured_instance_profile_name().await.unwrap() + ); + } + + #[tokio::test] + #[traced_test] + #[cfg(feature = "default-https-client")] + async fn ec2_instance_profile_name_via_profile_file() { + let provider_config = ProviderConfig::empty() + .with_profile_config( + Some( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFiles::builder() + .with_file( + #[allow(deprecated)] + crate::profile::profile_file::ProfileFileKind::Config, + "conf", + ) + .build(), + ), + None, + ) + .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[( + "conf", + "[default]\nec2_instance_profile_name = profile-via-profile-file", + )])); + let client = crate::imds::Client::builder() + .configure(&provider_config) + .build(); + let provider = ImdsCredentialsProvider::builder() + .configure(&provider_config) + .imds_client(client) + .build(); + assert_eq!( + Some(Cow::Borrowed("profile-via-profile-file")), + provider.configured_instance_profile_name().await.unwrap() + ); } #[tokio::test] @@ -343,11 +679,11 @@ mod test { token_response(21600, TOKEN_A), ), ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), imds_response(r#"profile-name"#), ), ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), ]); @@ -374,11 +710,13 @@ mod test { creds.expiry(), UNIX_EPOCH.checked_add(Duration::from_secs(1632197813)) ); + assert!(creds.account_id().is_none()); http_client.assert_requests_match(&[]); // There should not be logs indicating credentials are extended for stability. assert!(!logs_contain(WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY)); } + #[tokio::test] #[traced_test] async fn expired_credentials_should_be_extended() { @@ -388,11 +726,11 @@ mod test { token_response(21600, TOKEN_A), ), ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), imds_response(r#"profile-name"#), ), ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), ]); @@ -434,8 +772,8 @@ mod test { // seed fallback credentials for testing .last_retrieved_credentials(expected.clone()) .build(); - let actual = provider.provide_credentials().await; - assert_eq!(actual.unwrap(), expected); + let actual = provider.provide_credentials().await.unwrap(); + assert_eq!(expected, actual); } #[tokio::test] @@ -451,15 +789,13 @@ mod test { .imds_client(client) // no fallback credentials provided .build(); - let actual = provider.provide_credentials().await; + let actual = provider.provide_credentials().await.err().unwrap(); assert!( - matches!(actual, Err(CredentialsError::CredentialsNotLoaded(_))), + matches!(actual, CredentialsError::CredentialsNotLoaded(_)), "\nexpected: Err(CredentialsError::CredentialsNotLoaded(_))\nactual: {actual:?}" ); } - // TODO(https://github.com/awslabs/aws-sdk-rust/issues/1117) This test is ignored on Windows because it uses Unix-style paths - #[cfg_attr(windows, ignore)] #[tokio::test] #[cfg(feature = "default-https-client")] async fn external_timeout_during_credentials_refresh_should_yield_last_retrieved_credentials() { @@ -485,7 +821,7 @@ mod test { match timeout.await { Ok(_) => panic!("provide_credentials completed before timeout future"), Err(_err) => match provider.fallback_on_interrupt() { - Some(actual) => assert_eq!(actual, expected), + Some(actual) => assert_eq!(expected, actual), None => panic!( "provide_credentials timed out and no credentials returned from fallback_on_interrupt" ), @@ -504,17 +840,17 @@ mod test { token_response(21600, TOKEN_A), ), ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), imds_response(r#"profile-name"#), ), ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), // The following request/response pair corresponds to the second call to `provide_credentials`. // During the call, IMDS returns response code 500. ReplayEvent::new( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A), http::Response::builder().status(500).body(SdkBody::empty()).unwrap(), ), ]); @@ -523,10 +859,314 @@ mod test { .configure(&ProviderConfig::no_configuration()) .build(); let creds1 = provider.provide_credentials().await.expect("valid creds"); - assert_eq!(creds1.access_key_id(), "ASIARTEST"); - // `creds1` should be returned as fallback credentials and assigned to `creds2` - let creds2 = provider.provide_credentials().await.expect("valid creds"); - assert_eq!(creds1, creds2); + assert_eq!("ASIARTEST", creds1.access_key_id()); + // `creds1` should be returned as fallback credentials + assert_eq!( + creds1, + provider.provide_credentials().await.expect("valid creds") + ); + http_client.assert_requests_match(&[]); + } + + async fn run_test( + events: Vec, + update_builder: fn(Builder) -> Builder, + runner: F, + ) where + F: Fn(ImdsCredentialsProvider) -> Pin + Send + 'static>>, + { + let http_client = StaticReplayClient::new(events); + let builder = ImdsCredentialsProvider::builder() + .imds_client(make_imds_client(&http_client)) + .configure(&ProviderConfig::no_configuration()); + let provider = update_builder(builder).build(); + runner(provider).await; http_client.assert_requests_match(&[]); } + + async fn assert(provider: ImdsCredentialsProvider, expected: &[(Option<&str>, Option<&str>)]) { + for (expected_access_key_id, expected_account_id) in expected { + let creds = provider.provide_credentials().await.expect("valid creds"); + assert_eq!(expected_access_key_id, &Some(creds.access_key_id()),); + assert_eq!( + expected_account_id, + &creds.account_id().map(|id| id.as_str()) + ); + } + } + + #[tokio::test] + async fn returns_valid_credentials_with_account_id() { + let extended_api_events = vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + // A profile is not cached, so we should expect a network call to obtain one. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), + imds_response(r#"my-profile-0001"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0001", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"AccountId\" : \"123456789101\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + // For the second call to `provide_credentials`, we shouldn't expect a network call to obtain a profile since it's been resolved and cached. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0001", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"AccountId\" : \"123456789101\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]; + run_test(extended_api_events, IdentityFn, |provider| { + Box::pin(assert( + provider, + &[ + (Some("ASIARTEST"), Some("123456789101")), + (Some("ASIARTEST"), Some("123456789101")), + ], + )) + }) + .await; + + let legacy_api_events = vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + // Obtaining a profile from IMDS using the extended API results in 404. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), + imds_response_404(), + ), + // Should be retried using the legacy API. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_response(r#"my-profile-0009"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0009", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0009", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]; + run_test(legacy_api_events, IdentityFn, |provider| { + Box::pin(assert( + provider, + &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)], + )) + }) + .await; + } + + #[tokio::test] + async fn should_return_credentials_when_profile_is_configured_by_user() { + let extended_api_events = vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0002", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"AccountId\" : \"234567891011\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0002", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"AccountId\" : \"234567891011\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]; + run_test( + extended_api_events, + |b| b.profile("my-profile-0002"), + |provider| { + Box::pin(assert( + provider, + &[ + (Some("ASIARTEST"), Some("234567891011")), + (Some("ASIARTEST"), Some("234567891011")), + ], + )) + }, + ) + .await; + + let legacy_api_events = vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + // Obtaining a credentials using the extended API results in 404. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0010", TOKEN_A), + imds_response_404(), + ), + // Obtain credentials using the legacy API with the configured profile. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0010", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0010", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]; + run_test( + legacy_api_events, + |b| b.profile("my-profile-0010"), + |provider| { + Box::pin(assert( + provider, + &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)], + )) + }, + ) + .await; + } + + #[tokio::test] + async fn should_return_valid_credentials_when_profile_is_unstable() { + let extended_api_events = vec![ + // First call to `provide_credentials` succeeds with the extended API. + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), + imds_response(r#"my-profile-0003"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0003", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"AccountId\" : \"345678910112\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + + // Credentials retrieval failed due to unstable profile. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0003", TOKEN_A), + imds_response_404(), + ), + // Start over and retrieve a new profile with the extended API. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), + imds_response(r#"my-profile-0003-b"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0003-b", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"AccountId\" : \"314253647589\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]; + run_test(extended_api_events, IdentityFn, |provider| { + Box::pin(assert( + provider, + &[ + (Some("ASIARTEST"), Some("345678910112")), + (Some("ASIARTEST"), Some("314253647589")), + ], + )) + }) + .await; + + let legacy_api_events = vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), + imds_response_404() + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_response(r#"my-profile-0011"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0011", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + // Credentials retrieval failed due to unstable profile. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0011", TOKEN_A), + imds_response_404() + ), + // Start over and retrieve a new profile with the legacy API. + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_response(r#"my-profile-0011-b"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0011-b", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]; + run_test(legacy_api_events, IdentityFn, |provider| { + Box::pin(assert( + provider, + &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)], + )) + }) + .await; + } + + #[tokio::test] + async fn should_error_when_imds_remains_unstable_with_profile_configured_by_user() { + // This negative test exercises the same code path for both the extended and legacy APIs. + // A single set of events is sufficient for testing both. + let events = vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0004", TOKEN_A), + imds_response_404(), + ), + // Try obtaining credentials again with the legacy API + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0004", TOKEN_A), + imds_response_404(), + ), + ]; + run_test( + events, + |b| b.profile("my-profile-0004"), + |provider| { + Box::pin(async move { + let err = provider.provide_credentials().await.err().unwrap(); + matches!(err, CredentialsError::CredentialsNotLoaded(_)); + }) + }, + ) + .await; + } + + #[tokio::test] + async fn returns_valid_credentials_without_account_id_using_extended_api() { + let extended_api_events = vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A), + imds_response(r#"my-profile-0005"#), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0005", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0005", TOKEN_A), + imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), + ), + ]; + run_test(extended_api_events, IdentityFn, |provider| { + Box::pin(assert( + provider, + &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)], + )) + }) + .await; + } } diff --git a/aws/rust-runtime/aws-config/src/imds/mod.rs b/aws/rust-runtime/aws-config/src/imds/mod.rs index a85e3536447..6a22d9114a1 100644 --- a/aws/rust-runtime/aws-config/src/imds/mod.rs +++ b/aws/rust-runtime/aws-config/src/imds/mod.rs @@ -13,6 +13,12 @@ pub mod region; mod env { pub(crate) const EC2_METADATA_DISABLED: &str = "AWS_EC2_METADATA_DISABLED"; + pub(crate) const EC2_INSTANCE_PROFILE_NAME: &str = "AWS_EC2_INSTANCE_PROFILE_NAME"; +} + +mod profile_key { + pub(crate) const EC2_METADATA_DISABLED: &str = "disable_ec2_metadata"; + pub(crate) const EC2_INSTANCE_PROFILE_NAME: &str = "ec2_instance_profile_name"; } #[doc(inline)] diff --git a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_assume_role/http-traffic.json b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_assume_role/http-traffic.json index 285f10b37cb..c646235e902 100644 --- a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_assume_role/http-traffic.json +++ b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_assume_role/http-traffic.json @@ -89,7 +89,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", "headers": { "x-aws-ec2-metadata-token": [ "imdssesiontoken==" @@ -179,7 +179,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test", "headers": { "user-agent": [ "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1" diff --git a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_config_with_no_creds/http-traffic.json b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_config_with_no_creds/http-traffic.json index 9033ccb10ce..722faa688c5 100644 --- a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_config_with_no_creds/http-traffic.json +++ b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_config_with_no_creds/http-traffic.json @@ -89,7 +89,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", "headers": { "x-amz-user-agent": [ "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1" @@ -179,7 +179,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test", "headers": { "x-aws-ec2-metadata-token": [ "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw==" diff --git a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_error/http-traffic.json b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_error/http-traffic.json index 91db215749e..415ecafcbdb 100644 --- a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_error/http-traffic.json +++ b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_error/http-traffic.json @@ -89,7 +89,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", "headers": { "user-agent": [ "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1" @@ -167,6 +167,90 @@ "direction": "Response" } } + }, + { + "connection_id": 2, + "action": { + "Request": { + "request": { + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "headers": { + "user-agent": [ + "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1" + ], + "x-amz-user-agent": [ + "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1" + ], + "x-aws-ec2-metadata-token": [ + "faketoken" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 2, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 2, + "action": { + "Response": { + "response": { + "Ok": { + "status": 404, + "version": "HTTP/1.1", + "headers": { + "content-length": [ + "339" + ], + "date": [ + "Mon, 20 Sep 2021 20:51:52 GMT" + ], + "content-type": [ + "text/html" + ], + "x-aws-ec2-metadata-token-ttl-seconds": [ + "21600" + ], + "connection": [ + "close" + ], + "server": [ + "EC2ws" + ] + } + } + } + } + } + }, + { + "connection_id": 2, + "action": { + "Data": { + "data": { + "Utf8": "\n\n\n \n 404 - Not Found\n \n \n

404 - Not Found

\n \n\n" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 2, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } } ], "docs": "live IMDS token retrieval", diff --git a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_retries/http-traffic.json b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_retries/http-traffic.json index 1a639488c71..72c4d97cb5a 100644 --- a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_retries/http-traffic.json +++ b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_retries/http-traffic.json @@ -143,7 +143,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", "headers": { "x-amz-user-agent": [ "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1" @@ -197,7 +197,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", "headers": { "x-aws-ec2-metadata-token": [ "imdstoken" @@ -287,7 +287,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test", "headers": { "x-amz-user-agent": [ "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1" @@ -341,7 +341,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test", "headers": { "user-agent": [ "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1" diff --git a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_success/http-traffic.json b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_success/http-traffic.json index a1f57fc6ef7..5b14ae0ae4e 100644 --- a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_success/http-traffic.json +++ b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_default_chain_success/http-traffic.json @@ -89,7 +89,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", "headers": { "x-amz-user-agent": [ "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1" @@ -179,7 +179,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test", "headers": { "x-aws-ec2-metadata-token": [ "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw==" diff --git a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_no_iam_role/http-traffic.json b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_no_iam_role/http-traffic.json index 91db215749e..415ecafcbdb 100644 --- a/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_no_iam_role/http-traffic.json +++ b/aws/rust-runtime/aws-config/test-data/default-credential-provider-chain/imds_no_iam_role/http-traffic.json @@ -89,7 +89,7 @@ "action": { "Request": { "request": { - "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", "headers": { "user-agent": [ "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1" @@ -167,6 +167,90 @@ "direction": "Response" } } + }, + { + "connection_id": 2, + "action": { + "Request": { + "request": { + "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "headers": { + "user-agent": [ + "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1" + ], + "x-amz-user-agent": [ + "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1" + ], + "x-aws-ec2-metadata-token": [ + "faketoken" + ] + }, + "method": "GET" + } + } + } + }, + { + "connection_id": 2, + "action": { + "Eof": { + "ok": true, + "direction": "Request" + } + } + }, + { + "connection_id": 2, + "action": { + "Response": { + "response": { + "Ok": { + "status": 404, + "version": "HTTP/1.1", + "headers": { + "content-length": [ + "339" + ], + "date": [ + "Mon, 20 Sep 2021 20:51:52 GMT" + ], + "content-type": [ + "text/html" + ], + "x-aws-ec2-metadata-token-ttl-seconds": [ + "21600" + ], + "connection": [ + "close" + ], + "server": [ + "EC2ws" + ] + } + } + } + } + } + }, + { + "connection_id": 2, + "action": { + "Data": { + "data": { + "Utf8": "\n\n\n \n 404 - Not Found\n \n \n

404 - Not Found

\n \n\n" + }, + "direction": "Response" + } + } + }, + { + "connection_id": 2, + "action": { + "Eof": { + "ok": true, + "direction": "Response" + } + } } ], "docs": "live IMDS token retrieval",