AWS SDK

AWS SDK

rev. fd513891e2666336a3bce8b7369fa5a24f989512

Files changed:

tmp-codegen-diff/aws-sdk/README.md

@@ -1,1 +57,57 @@
   17     17   
   18     18   
> For a step-by-step guide including several advanced use cases, check out the [Developer Guide](https://docs.aws.amazon.com/sdk-for-rust/latest/dg/welcome.html).
   19     19   
   20     20   
The SDK provides one crate per AWS service. You must add [Tokio](https://crates.io/crates/tokio) as a dependency within your Rust project to execute asynchronous code.
   21     21   
   22     22   
1. Create a new Rust project: `cargo new sdk-example`
   23     23   
2. Add dependencies to DynamoDB and Tokio to your **Cargo.toml** file:
   24     24   
   25     25   
    ```toml
   26     26   
    [dependencies]
   27         -
    aws-config = { version= "1.6.2", features = ["behavior-version-latest"] }
          27  +
    aws-config = { version= "1.7.0", features = ["behavior-version-latest"] }
   28     28   
    aws-sdk-dynamodb = "0.0.0-local"
   29     29   
    tokio = { version = "1", features = ["full"] }
   30     30   
    ```
   31     31   
   32     32   
3. Provide your AWS credentials with the default credential provider chain, which currently looks in:
   33     33   
   - Environment variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_REGION`
   34     34   
   - The default credentials files located in `~/.aws/config` and `~/.aws/credentials` (location can vary per platform)
   35     35   
   - Web Identity Token credentials from the environment or container (including EKS)
   36     36   
   - ECS Container Credentials (IAM roles for tasks)
   37     37   
   - EC2 Instance Metadata Service (IAM Roles attached to instance)

tmp-codegen-diff/aws-sdk/sdk/aws-config/Cargo.toml

@@ -1,1 +34,34 @@
    1      1   
# Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
    2      2   
[package]
    3      3   
name = "aws-config"
    4         -
version = "1.6.2"
           4  +
version = "1.7.0"
    5      5   
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "Russell Cohen <rcoh@amazon.com>"]
    6      6   
description = "AWS SDK config and credential provider implementations."
    7      7   
edition = "2021"
    8      8   
exclude = ["test-data/*", "integration-tests/*"]
    9      9   
license = "Apache-2.0"
   10     10   
repository = "https://github.com/smithy-lang/smithy-rs"
   11     11   
[package.metadata.docs.rs]
   12     12   
all-features = true
   13     13   
targets = ["x86_64-unknown-linux-gnu"]
   14     14   
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

tmp-codegen-diff/aws-sdk/sdk/aws-config/fuzz/Cargo.toml

@@ -1,1 +30,30 @@
   13     13   
edition = "2021"
   14     14   
   15     15   
[package.metadata]
   16     16   
cargo-fuzz = true
   17     17   
   18     18   
[dependencies]
   19     19   
libfuzzer-sys = "=0.4.7"
   20     20   
   21     21   
[dependencies.aws-config]
   22     22   
path = ".."
   23         -
version = "1.6.2"
          23  +
version = "1.7.0"
   24     24   
   25     25   
[dependencies.aws-types]
   26     26   
path = "../../../sdk/build/aws-sdk/sdk/aws-types"
   27     27   
version = "1.3.7"
   28     28   
   29     29   
[workspace]
   30     30   
members = ["."]

tmp-codegen-diff/aws-sdk/sdk/aws-config/src/imds/client.rs

@@ -706,706 +767,776 @@
  726    726   
    pub(crate) fn imds_response(body: &'static str) -> HttpResponse {
  727    727   
        HttpResponse::try_from(
  728    728   
            http::Response::builder()
  729    729   
                .status(200)
  730    730   
                .body(SdkBody::from(body))
  731    731   
                .unwrap(),
  732    732   
        )
  733    733   
        .unwrap()
  734    734   
    }
  735    735   
         736  +
    pub(crate) fn imds_response_404() -> HttpResponse {
         737  +
        HttpResponse::try_from(
         738  +
            http::Response::builder()
         739  +
                .status(404)
         740  +
                .body(SdkBody::empty())
         741  +
                .unwrap(),
         742  +
        )
         743  +
        .unwrap()
         744  +
    }
         745  +
  736    746   
    pub(crate) fn make_imds_client(http_client: &StaticReplayClient) -> super::Client {
  737         -
        tokio::time::pause();
  738    747   
        super::Client::builder()
  739    748   
            .configure(
  740    749   
                &ProviderConfig::no_configuration()
  741    750   
                    .with_sleep_impl(InstantSleep::unlogged())
  742    751   
                    .with_http_client(http_client.clone()),
  743    752   
            )
  744    753   
            .build()
  745    754   
    }
  746    755   
  747    756   
    fn mock_imds_client(events: Vec<ReplayEvent>) -> (Client, StaticReplayClient) {

tmp-codegen-diff/aws-sdk/sdk/aws-config/src/imds/credentials.rs

@@ -1,1 +532,1172 @@
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
    6      6   
//! IMDSv2 Credentials Provider
    7      7   
//!
    8      8   
//! # Important
    9      9   
//! This credential provider will NOT fallback to IMDSv1. Ensure that IMDSv2 is enabled on your instances.
   10     10   
   11     11   
use super::client::error::ImdsError;
          12  +
use crate::environment::parse_bool;
   12     13   
use crate::imds::{self, Client};
   13     14   
use crate::json_credentials::{parse_json_credentials, JsonCredentials, RefreshableCredentials};
   14     15   
use crate::provider_config::ProviderConfig;
          16  +
use aws_credential_types::attributes::AccountId;
   15     17   
use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials};
   16     18   
use aws_credential_types::Credentials;
          19  +
use aws_runtime::env_config::EnvConfigValue;
   17     20   
use aws_smithy_async::time::SharedTimeSource;
   18         -
use aws_types::os_shim_internal::Env;
          21  +
use aws_smithy_types::error::display::DisplayErrorContext;
          22  +
use aws_types::origin::Origin;
   19     23   
use std::borrow::Cow;
   20     24   
use std::error::Error as StdError;
   21     25   
use std::fmt;
   22     26   
use std::sync::{Arc, RwLock};
   23     27   
use std::time::{Duration, SystemTime};
   24     28   
   25     29   
const CREDENTIAL_EXPIRATION_INTERVAL: Duration = Duration::from_secs(10 * 60);
   26     30   
const WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY: &str =
   27     31   
    "Attempting credential expiration extension due to a credential service availability issue. \
   28     32   
    A refresh of these credentials will be attempted again within the next";
   29     33   
   30     34   
#[derive(Debug)]
   31     35   
struct ImdsCommunicationError {
   32     36   
    source: Box<dyn StdError + Send + Sync + 'static>,
   33     37   
}
   34     38   
   35     39   
impl fmt::Display for ImdsCommunicationError {
   36     40   
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
   37     41   
        write!(f, "could not communicate with IMDS")
   38     42   
    }
   39     43   
}
   40     44   
   41     45   
impl StdError for ImdsCommunicationError {
   42     46   
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
   43     47   
        Some(self.source.as_ref())
   44     48   
    }
   45     49   
}
   46     50   
          51  +
// Enum representing the type of IMDS endpoint that the credentials provider should access
          52  +
// when retrieving the IMDS profile name or credentials.
          53  +
#[derive(Clone, Debug, Default, Eq, PartialEq)]
          54  +
enum ApiVersion {
          55  +
    #[default]
          56  +
    Unknown,
          57  +
    Extended,
          58  +
    Legacy,
          59  +
}
          60  +
          61  +
// A state maintained by the IMDS credentials provider to manage the retrieval of the IMDS profile name or credentials.
          62  +
#[derive(Clone, Debug, Default)]
          63  +
struct ProviderState {
          64  +
    api_version: ApiVersion,
          65  +
    resolved_profile: Option<String>,
          66  +
}
          67  +
   47     68   
/// IMDSv2 Credentials Provider
   48     69   
///
   49     70   
/// _Note: This credentials provider will NOT fallback to the IMDSv1 flow._
   50     71   
#[derive(Debug)]
   51     72   
pub struct ImdsCredentialsProvider {
   52     73   
    client: Client,
   53         -
    env: Env,
          74  +
    provider_config: ProviderConfig,
   54     75   
    profile: Option<String>,
   55     76   
    time_source: SharedTimeSource,
   56     77   
    last_retrieved_credentials: Arc<RwLock<Option<Credentials>>>,
          78  +
    provider_state: Arc<RwLock<ProviderState>>,
   57     79   
}
   58     80   
   59     81   
/// Builder for [`ImdsCredentialsProvider`]
   60     82   
#[derive(Default, Debug)]
   61     83   
pub struct Builder {
   62     84   
    provider_config: Option<ProviderConfig>,
   63     85   
    profile_override: Option<String>,
   64     86   
    imds_override: Option<imds::Client>,
   65     87   
    last_retrieved_credentials: Option<Credentials>,
   66     88   
}
   67     89   
   68     90   
impl Builder {
   69     91   
    /// Override the configuration used for this provider
   70     92   
    pub fn configure(mut self, provider_config: &ProviderConfig) -> Self {
   71     93   
        self.provider_config = Some(provider_config.clone());
   72     94   
        self
   73     95   
    }
   74     96   
   75     97   
    /// Override the [instance profile](instance-profile) used for this provider.
   76     98   
    ///
   77     99   
    /// When retrieving IMDS credentials, a call must first be made to
   78    100   
    /// `<IMDS_BASE_URL>/latest/meta-data/iam/security-credentials/`. This returns the instance
   79    101   
    /// profile used. By setting this parameter, retrieving the profile is skipped
   80    102   
    /// and the provided value is used instead.
   81    103   
    ///
   82    104   
    /// [instance-profile]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#ec2-instance-profile
   83    105   
    pub fn profile(mut self, profile: impl Into<String>) -> Self {
   84    106   
        self.profile_override = Some(profile.into());
   85    107   
        self
   86    108   
    }
   87    109   
   88    110   
    /// Override the IMDS client used for this provider
   89    111   
    ///
   90    112   
    /// The IMDS client will be loaded and configured via `~/.aws/config` and environment variables,
   91    113   
    /// however, if necessary the entire client may be provided directly.
   92    114   
    ///
   93    115   
    /// For more information about IMDS client configuration loading see [`imds::Client`]
   94    116   
    pub fn imds_client(mut self, client: imds::Client) -> Self {
   95    117   
        self.imds_override = Some(client);
   96    118   
        self
   97    119   
    }
   98    120   
   99    121   
    #[allow(dead_code)]
  100    122   
    #[cfg(test)]
  101    123   
    fn last_retrieved_credentials(mut self, credentials: Credentials) -> Self {
  102    124   
        self.last_retrieved_credentials = Some(credentials);
  103    125   
        self
  104    126   
    }
  105    127   
  106    128   
    /// Create an [`ImdsCredentialsProvider`] from this builder.
  107    129   
    pub fn build(self) -> ImdsCredentialsProvider {
  108    130   
        let provider_config = self.provider_config.unwrap_or_default();
  109         -
        let env = provider_config.env();
  110    131   
        let client = self
  111    132   
            .imds_override
  112    133   
            .unwrap_or_else(|| imds::Client::builder().configure(&provider_config).build());
  113    134   
        ImdsCredentialsProvider {
  114    135   
            client,
  115         -
            env,
  116    136   
            profile: self.profile_override,
  117    137   
            time_source: provider_config.time_source(),
         138  +
            provider_config,
  118    139   
            last_retrieved_credentials: Arc::new(RwLock::new(self.last_retrieved_credentials)),
         140  +
            provider_state: Arc::new(RwLock::new(ProviderState::default())),
  119    141   
        }
  120    142   
    }
  121    143   
}
  122    144   
  123    145   
mod codes {
  124    146   
    pub(super) const ASSUME_ROLE_UNAUTHORIZED_ACCESS: &str = "AssumeRoleUnauthorizedAccess";
  125    147   
}
  126    148   
  127    149   
impl ProvideCredentials for ImdsCredentialsProvider {
  128    150   
    fn provide_credentials<'a>(&'a self) -> future::ProvideCredentials<'a>
  129    151   
    where
  130    152   
        Self: 'a,
  131    153   
    {
  132    154   
        future::ProvideCredentials::new(self.credentials())
  133    155   
    }
  134    156   
  135    157   
    fn fallback_on_interrupt(&self) -> Option<Credentials> {
  136    158   
        self.last_retrieved_credentials.read().unwrap().clone()
  137    159   
    }
  138    160   
}
  139    161   
  140    162   
impl ImdsCredentialsProvider {
  141    163   
    /// Builder for [`ImdsCredentialsProvider`]
  142    164   
    pub fn builder() -> Builder {
  143    165   
        Builder::default()
  144    166   
    }
  145    167   
  146         -
    fn imds_disabled(&self) -> bool {
  147         -
        match self.env.get(super::env::EC2_METADATA_DISABLED) {
  148         -
            Ok(value) => value.eq_ignore_ascii_case("true"),
  149         -
            _ => false,
         168  +
    // Retrieve the value for "disable ec2 metadata". If the value is `true`, the method also returns
         169  +
    // the source that set it to `true`.
         170  +
    //
         171  +
    // This checks the following sources:
         172  +
    // 1. The environment variable `AWS_EC2_METADATA_DISABLED=true/false`
         173  +
    // 2. The profile key `disable_ec2_metadata=true/false`
         174  +
    async fn imds_disabled(&self) -> (bool, Origin) {
         175  +
        EnvConfigValue::new()
         176  +
            .env(super::env::EC2_METADATA_DISABLED)
         177  +
            .profile(super::profile_key::EC2_METADATA_DISABLED)
         178  +
            .validate_and_return_origin(
         179  +
                &self.provider_config.env(),
         180  +
                self.provider_config.profile().await,
         181  +
                parse_bool,
         182  +
            )
         183  +
            .map_err(
         184  +
                |err| tracing::warn!(err = %DisplayErrorContext(&err), "invalid value for `disable ec2 metadata` setting"),
         185  +
            )
         186  +
            .map(|(disabled, origin)| (disabled.unwrap_or_default(), origin))
         187  +
            .unwrap_or_default()
         188  +
    }
         189  +
         190  +
    // Return a configured instance profile name. If the profile name is blank, the method returns
         191  +
    // a `CredentialsError`.
         192  +
    //
         193  +
    // This checks the following sources:
         194  +
    // 1. The profile name configured via [`Builder::profile`]
         195  +
    // 2. The environment variable `AWS_EC2_INSTANCE_PROFILE_NAME`
         196  +
    // 3. The profile key `ec2_instance_profile_name`
         197  +
    async fn configured_instance_profile_name(
         198  +
        &self,
         199  +
    ) -> Result<Option<Cow<'_, str>>, CredentialsError> {
         200  +
        let configured = match &self.profile {
         201  +
            Some(profile) => Some(profile.into()),
         202  +
            None => EnvConfigValue::new()
         203  +
                .env(super::env::EC2_INSTANCE_PROFILE_NAME)
         204  +
                .profile(super::profile_key::EC2_INSTANCE_PROFILE_NAME)
         205  +
                .validate(
         206  +
                    &self.provider_config.env(),
         207  +
                    self.provider_config.profile().await,
         208  +
                    |s| Ok::<String, std::convert::Infallible>(s.to_owned()),
         209  +
                )
         210  +
                .expect("validator is infallible")
         211  +
                .map(Cow::Owned),
         212  +
        };
         213  +
         214  +
        match configured {
         215  +
            Some(configured) if configured.trim().is_empty() => Err(CredentialsError::not_loaded(
         216  +
                "blank profile name is not supported",
         217  +
            )),
         218  +
            otherwise => Ok(otherwise),
         219  +
        }
         220  +
    }
         221  +
         222  +
    fn uri_base(&self) -> &str {
         223  +
        let api_version = &self.provider_state.read().unwrap().api_version;
         224  +
        use ApiVersion::*;
         225  +
        match api_version {
         226  +
            Legacy => "/latest/meta-data/iam/security-credentials/",
         227  +
            _ => "/latest/meta-data/iam/security-credentials-extended/",
  150    228   
        }
  151    229   
    }
  152    230   
  153         -
    /// Retrieve the instance profile from IMDS
  154         -
    async fn get_profile_uncached(&self) -> Result<String, CredentialsError> {
  155         -
        match self
  156         -
            .client
  157         -
            .get("/latest/meta-data/iam/security-credentials/")
  158         -
            .await
  159         -
        {
  160         -
            Ok(profile) => Ok(profile.as_ref().into()),
         231  +
    // Retrieve the instance profile from IMDS
         232  +
    //
         233  +
    // Starting with `ApiVersion::Unknown`, the method first attempts to retrive it using the extended API.
         234  +
    // If the call is successful, it updates `ProviderState` to remember that the extended API is functional and moves on.
         235  +
    // Otherwise, it updates `ProviderState` to the legacy mode and tries again.
         236  +
    // In the end, if the legacy API does not work either, the method gives up and returns a `CredentialsError`.
         237  +
    async fn resolve_profile_name(&self) -> Result<Cow<'_, str>, CredentialsError> {
         238  +
        if let Some(profile) = self.configured_instance_profile_name().await? {
         239  +
            return Ok(profile);
         240  +
        }
         241  +
         242  +
        if let Some(profile) = &self.provider_state.read().unwrap().resolved_profile {
         243  +
            return Ok(profile.clone().into());
         244  +
        }
         245  +
         246  +
        match self.client.get(self.uri_base()).await {
         247  +
            Ok(profile) => {
         248  +
                let state = &mut self.provider_state.write().unwrap();
         249  +
                state.resolved_profile = Some(profile.clone().into());
         250  +
                if state.api_version == ApiVersion::Unknown {
         251  +
                    state.api_version = ApiVersion::Extended;
         252  +
                }
         253  +
                Ok(Cow::Owned(profile.into()))
         254  +
            }
  161    255   
            Err(ImdsError::ErrorResponse(context))
  162    256   
                if context.response().status().as_u16() == 404 =>
  163    257   
            {
  164    258   
                tracing::warn!(
  165    259   
                    "received 404 from IMDS when loading profile information. \
  166    260   
                    Hint: This instance may not have an IAM role associated."
  167    261   
                );
  168         -
                Err(CredentialsError::not_loaded("received 404 from IMDS"))
         262  +
         263  +
                // A block is required to drop the `RwLockWriteGuard`,
         264  +
                // as calling `.drop` on the guard didn't satisfy the compiler as shown below:
         265  +
                //
         266  +
                //  note: future is not `Send` as this value is used across an await
         267  +
                //
         268  +
                //  return Box::pin(self.resolve_profile_name()).await;
         269  +
                //                                               ^^^^^ await occurs here, with `self.provider_state.write().unwrap()` maybe used later
         270  +
                {
         271  +
                    let state = &mut self.provider_state.write().unwrap();
         272  +
                    if state.api_version == ApiVersion::Unknown {
         273  +
                        tracing::debug!("retrieving an IMDS profile name failed using the extended API, switching to the legacy API and trying again");
         274  +
                        state.api_version = ApiVersion::Legacy;
         275  +
                    } else {
         276  +
                        return Err(CredentialsError::not_loaded("received 404 from IMDS"));
         277  +
                    }
         278  +
                }
         279  +
         280  +
                Box::pin(self.resolve_profile_name()).await
  169    281   
            }
  170    282   
            Err(ImdsError::FailedToLoadToken(context)) if context.is_dispatch_failure() => {
  171    283   
                Err(CredentialsError::not_loaded(ImdsCommunicationError {
  172    284   
                    source: context.into_source().into(),
  173    285   
                }))
  174    286   
            }
  175    287   
            Err(other) => Err(CredentialsError::provider_error(other)),
  176    288   
        }
  177    289   
    }
  178    290   
  179    291   
    // Extend the cached expiration time if necessary
  180    292   
    //
  181    293   
    // This allows continued use of the credentials even when IMDS returns expired ones.
  182    294   
    fn maybe_extend_expiration(&self, expiration: SystemTime) -> SystemTime {
  183    295   
        let now = self.time_source.now();
  184    296   
        // If credentials from IMDS are not stale, use them as they are.
  185    297   
        if now < expiration {
  186    298   
            return expiration;
  187    299   
        }
  188    300   
  189    301   
        let mut rng = fastrand::Rng::with_seed(
  190    302   
            now.duration_since(SystemTime::UNIX_EPOCH)
  191    303   
                .expect("now should be after UNIX EPOCH")
  192    304   
                .as_secs(),
  193    305   
        );
  194    306   
        // Calculate credentials' refresh offset with jitter, which should be less than 15 minutes
  195    307   
        // the smallest amount of time credentials are valid for.
  196    308   
        // Setting it to something longer than that may have the risk of the credentials expiring
  197    309   
        // before the next refresh.
  198    310   
        let refresh_offset = CREDENTIAL_EXPIRATION_INTERVAL + Duration::from_secs(rng.u64(0..=300));
  199    311   
        let new_expiry = now + refresh_offset;
  200    312   
  201    313   
        tracing::warn!(
  202    314   
            "{WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY} {:.2} minutes.",
  203    315   
            refresh_offset.as_secs_f64() / 60.0,
  204    316   
        );
  205    317   
  206    318   
        new_expiry
  207    319   
    }
  208    320   
  209    321   
    async fn retrieve_credentials(&self) -> provider::Result {
  210         -
        if self.imds_disabled() {
  211         -
            let err = format!(
  212         -
                "IMDS disabled by {} env var set to `true`",
  213         -
                super::env::EC2_METADATA_DISABLED
  214         -
            );
         322  +
        if let (true, origin) = self.imds_disabled().await {
         323  +
            let err = format!("IMDS disabled by {origin} set to `true`",);
  215    324   
            tracing::debug!(err);
  216    325   
            return Err(CredentialsError::not_loaded(err));
  217    326   
        }
         327  +
  218    328   
        tracing::debug!("loading credentials from IMDS");
  219         -
        let profile: Cow<'_, str> = match &self.profile {
  220         -
            Some(profile) => profile.into(),
  221         -
            None => self.get_profile_uncached().await?.into(),
  222         -
        };
         329  +
         330  +
        let profile = self.resolve_profile_name().await?;
  223    331   
        tracing::debug!(profile = %profile, "loaded profile");
  224         -
        let credentials = self
         332  +
         333  +
        let credentials = match self
  225    334   
            .client
  226         -
            .get(format!(
  227         -
                "/latest/meta-data/iam/security-credentials/{}",
  228         -
                profile
  229         -
            ))
         335  +
            .get(format!("{uri_base}{profile}", uri_base = self.uri_base()))
  230    336   
            .await
  231         -
            .map_err(CredentialsError::provider_error)?;
         337  +
        {
         338  +
            Ok(credentials) => {
         339  +
                let state = &mut self.provider_state.write().unwrap();
         340  +
                state.resolved_profile = Some(profile.to_string());
         341  +
                if state.api_version == ApiVersion::Unknown {
         342  +
                    state.api_version = ApiVersion::Extended;
         343  +
                }
         344  +
                Ok(credentials)
         345  +
            }
         346  +
            Err(ImdsError::ErrorResponse(raw)) if raw.response().status().as_u16() == 404 => {
         347  +
                // A block is required to drop the `RwLockWriteGuard`,
         348  +
                // as calling `.drop` on the guard didn't satisfy the compiler as shown below:
         349  +
                //
         350  +
                //  note: future is not `Send` as this value is used across an await
         351  +
                //
         352  +
                //  return Box::pin(self.retrieve_credentials()).await;
         353  +
                //                                               ^^^^^ await occurs here, with `self.provider_state.write().unwrap()` maybe used later
         354  +
                {
         355  +
                    let state = &mut self.provider_state.write().unwrap();
         356  +
                    if state.api_version == ApiVersion::Unknown {
         357  +
                        tracing::debug!("retrieving credentials failed using the extended API, switching to the legacy API and trying again");
         358  +
                        state.api_version = ApiVersion::Legacy;
         359  +
                    } else if self.profile.is_none() {
         360  +
                        tracing::debug!("retrieving credentials failed using {:?}, clearing cached profile and trying again", state.api_version);
         361  +
                        state.resolved_profile = None;
         362  +
                    } else {
         363  +
                        return Err(CredentialsError::provider_error(ImdsError::ErrorResponse(
         364  +
                            raw,
         365  +
                        )));
         366  +
                    }
         367  +
                }
         368  +
                return Box::pin(self.retrieve_credentials()).await;
         369  +
            }
         370  +
            otherwise => otherwise,
         371  +
        }
         372  +
        .map_err(CredentialsError::provider_error)?;
         373  +
  232    374   
        match parse_json_credentials(credentials.as_ref()) {
  233    375   
            Ok(JsonCredentials::RefreshableCredentials(RefreshableCredentials {
  234    376   
                access_key_id,
  235    377   
                secret_access_key,
  236    378   
                session_token,
  237    379   
                account_id,
  238    380   
                expiration,
  239    381   
                ..
  240    382   
            })) => {
  241         -
                // TODO(IMDSv2.X): Use `account_id` once the design is finalized
  242         -
                let _ = account_id;
  243    383   
                let expiration = self.maybe_extend_expiration(expiration);
  244         -
                let creds = Credentials::new(
  245         -
                    access_key_id,
  246         -
                    secret_access_key,
  247         -
                    Some(session_token.to_string()),
  248         -
                    expiration.into(),
  249         -
                    "IMDSv2",
  250         -
                );
         384  +
                let mut builder = Credentials::builder()
         385  +
                    .access_key_id(access_key_id)
         386  +
                    .secret_access_key(secret_access_key)
         387  +
                    .session_token(session_token)
         388  +
                    .expiry(expiration)
         389  +
                    .provider_name("IMDSv2");
         390  +
                builder.set_account_id(account_id.map(AccountId::from));
         391  +
                let creds = builder.build();
  251    392   
                *self.last_retrieved_credentials.write().unwrap() = Some(creds.clone());
  252    393   
                Ok(creds)
  253    394   
            }
  254    395   
            Ok(JsonCredentials::Error { code, message })
  255    396   
                if code == codes::ASSUME_ROLE_UNAUTHORIZED_ACCESS =>
  256    397   
            {
  257    398   
                Err(CredentialsError::invalid_configuration(format!(
  258    399   
                    "Incorrect IMDS/IAM configuration: [{}] {}. \
  259    400   
                        Hint: Does this role have a trust relationship with EC2?",
  260    401   
                    code, message
  261    402   
                )))
  262    403   
            }
  263    404   
            Ok(JsonCredentials::Error { code, message }) => {
  264    405   
                Err(CredentialsError::provider_error(format!(
  265    406   
                    "Error retrieving credentials from IMDS: {} {}",
  266    407   
                    code, message
  267    408   
                )))
  268    409   
            }
  269    410   
            // got bad data from IMDS, should not occur during normal operation:
  270    411   
            Err(invalid) => Err(CredentialsError::unhandled(invalid)),
  271    412   
        }
  272    413   
    }
  273    414   
  274    415   
    async fn credentials(&self) -> provider::Result {
  275    416   
        match self.retrieve_credentials().await {
  276    417   
            creds @ Ok(_) => creds,
  277    418   
            // Any failure while retrieving credentials MUST NOT impede use of existing credentials.
  278    419   
            err => match &*self.last_retrieved_credentials.read().unwrap() {
  279    420   
                Some(creds) => Ok(creds.clone()),
  280    421   
                _ => err,
  281    422   
            },
  282    423   
        }
  283    424   
    }
  284    425   
}
  285    426   
  286    427   
#[cfg(test)]
  287    428   
mod test {
  288    429   
    use super::*;
  289    430   
    use crate::imds::client::test::{
  290         -
        imds_request, imds_response, make_imds_client, token_request, token_response,
         431  +
        imds_request, imds_response, imds_response_404, make_imds_client, token_request,
         432  +
        token_response,
  291    433   
    };
  292    434   
    use crate::provider_config::ProviderConfig;
  293    435   
    use aws_credential_types::provider::ProvideCredentials;
  294    436   
    use aws_smithy_async::test_util::instant_time_and_sleep;
  295    437   
    use aws_smithy_http_client::test_util::{ReplayEvent, StaticReplayClient};
  296    438   
    use aws_smithy_types::body::SdkBody;
         439  +
    use std::convert::identity as IdentityFn;
         440  +
    use std::future::Future;
         441  +
    use std::pin::Pin;
  297    442   
    use std::time::{Duration, UNIX_EPOCH};
  298    443   
    use tracing_test::traced_test;
  299    444   
  300    445   
    const TOKEN_A: &str = "token_a";
  301    446   
  302    447   
    #[tokio::test]
  303         -
    async fn profile_is_not_cached() {
  304         -
        let http_client = StaticReplayClient::new(vec![
  305         -
            ReplayEvent::new(
  306         -
                token_request("http://169.254.169.254", 21600),
  307         -
                token_response(21600, TOKEN_A),
  308         -
            ),
  309         -
            ReplayEvent::new(
  310         -
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
  311         -
                imds_response(r#"profile-name"#),
  312         -
            ),
  313         -
            ReplayEvent::new(
  314         -
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A),
  315         -
                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}"),
  316         -
            ),
  317         -
            ReplayEvent::new(
  318         -
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
  319         -
                imds_response(r#"different-profile"#),
  320         -
            ),
  321         -
            ReplayEvent::new(
  322         -
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/different-profile", TOKEN_A),
  323         -
                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}"),
  324         -
            ),
  325         -
        ]);
  326         -
        let client = ImdsCredentialsProvider::builder()
  327         -
            .imds_client(make_imds_client(&http_client))
  328         -
            .configure(&ProviderConfig::no_configuration())
         448  +
    #[traced_test]
         449  +
    #[cfg(feature = "default-https-client")]
         450  +
    async fn warn_on_invalid_value_for_disable_ec2_metadata() {
         451  +
        let provider_config =
         452  +
            ProviderConfig::empty().with_env(aws_types::os_shim_internal::Env::from_slice(&[(
         453  +
                imds::env::EC2_METADATA_DISABLED,
         454  +
                "not-a-boolean",
         455  +
            )]));
         456  +
        let client = crate::imds::Client::builder()
         457  +
            .configure(&provider_config)
  329    458   
            .build();
  330         -
        let creds1 = client.provide_credentials().await.expect("valid creds");
  331         -
        let creds2 = client.provide_credentials().await.expect("valid creds");
  332         -
        assert_eq!(creds1.access_key_id(), "ASIARTEST");
  333         -
        assert_eq!(creds2.access_key_id(), "ASIARTEST2");
  334         -
        http_client.assert_requests_match(&[]);
         459  +
        let provider = ImdsCredentialsProvider::builder()
         460  +
            .configure(&provider_config)
         461  +
            .imds_client(client)
         462  +
            .build();
         463  +
        assert!(!provider.imds_disabled().await.0);
         464  +
        assert!(logs_contain(
         465  +
            "invalid value for `disable ec2 metadata` setting"
         466  +
        ));
         467  +
        assert!(logs_contain(imds::env::EC2_METADATA_DISABLED));
         468  +
    }
         469  +
         470  +
    #[tokio::test]
         471  +
    #[traced_test]
         472  +
    #[cfg(feature = "default-https-client")]
         473  +
    async fn environment_priority_on_disable_ec2_metadata() {
         474  +
        let provider_config = ProviderConfig::empty()
         475  +
            .with_env(aws_types::os_shim_internal::Env::from_slice(&[(
         476  +
                imds::env::EC2_METADATA_DISABLED,
         477  +
                "TRUE",
         478  +
            )]))
         479  +
            .with_profile_config(
         480  +
                Some(
         481  +
                    #[allow(deprecated)]
         482  +
                    crate::profile::profile_file::ProfileFiles::builder()
         483  +
                        .with_file(
         484  +
                            #[allow(deprecated)]
         485  +
                            crate::profile::profile_file::ProfileFileKind::Config,
         486  +
                            "conf",
         487  +
                        )
         488  +
                        .build(),
         489  +
                ),
         490  +
                None,
         491  +
            )
         492  +
            .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[(
         493  +
                "conf",
         494  +
                "[default]\ndisable_ec2_metadata = false",
         495  +
            )]));
         496  +
        let client = crate::imds::Client::builder()
         497  +
            .configure(&provider_config)
         498  +
            .build();
         499  +
        let provider = ImdsCredentialsProvider::builder()
         500  +
            .configure(&provider_config)
         501  +
            .imds_client(client)
         502  +
            .build();
         503  +
        assert_eq!(
         504  +
            (true, Origin::shared_environment_variable()),
         505  +
            provider.imds_disabled().await
         506  +
        );
         507  +
    }
         508  +
         509  +
    #[tokio::test]
         510  +
    #[traced_test]
         511  +
    #[cfg(feature = "default-https-client")]
         512  +
    async fn disable_ec2_metadata_via_profile_file() {
         513  +
        let provider_config = ProviderConfig::empty()
         514  +
            .with_profile_config(
         515  +
                Some(
         516  +
                    #[allow(deprecated)]
         517  +
                    crate::profile::profile_file::ProfileFiles::builder()
         518  +
                        .with_file(
         519  +
                            #[allow(deprecated)]
         520  +
                            crate::profile::profile_file::ProfileFileKind::Config,
         521  +
                            "conf",
         522  +
                        )
         523  +
                        .build(),
         524  +
                ),
         525  +
                None,
         526  +
            )
         527  +
            .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[(
         528  +
                "conf",
         529  +
                "[default]\ndisable_ec2_metadata = true",
         530  +
            )]));
         531  +
        let client = crate::imds::Client::builder()
         532  +
            .configure(&provider_config)
         533  +
            .build();
         534  +
        let provider = ImdsCredentialsProvider::builder()
         535  +
            .configure(&provider_config)
         536  +
            .imds_client(client)
         537  +
            .build();
         538  +
        assert_eq!(
         539  +
            (true, Origin::shared_profile_file()),
         540  +
            provider.imds_disabled().await
         541  +
        );
         542  +
    }
         543  +
         544  +
    #[tokio::test]
         545  +
    #[traced_test]
         546  +
    #[cfg(feature = "default-https-client")]
         547  +
    async fn creds_provider_configuration_priority_on_ec2_instance_profile_name() {
         548  +
        let provider_config = ProviderConfig::empty()
         549  +
            .with_env(aws_types::os_shim_internal::Env::from_slice(&[(
         550  +
                imds::env::EC2_INSTANCE_PROFILE_NAME,
         551  +
                "profile-via-env",
         552  +
            )]))
         553  +
            .with_profile_config(
         554  +
                Some(
         555  +
                    #[allow(deprecated)]
         556  +
                    crate::profile::profile_file::ProfileFiles::builder()
         557  +
                        .with_file(
         558  +
                            #[allow(deprecated)]
         559  +
                            crate::profile::profile_file::ProfileFileKind::Config,
         560  +
                            "conf",
         561  +
                        )
         562  +
                        .build(),
         563  +
                ),
         564  +
                None,
         565  +
            )
         566  +
            .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[(
         567  +
                "conf",
         568  +
                "[default]\nec2_instance_profile_name = profile-via-profile-file",
         569  +
            )]));
         570  +
         571  +
        let client = crate::imds::Client::builder()
         572  +
            .configure(&provider_config)
         573  +
            .build();
         574  +
        let provider = ImdsCredentialsProvider::builder()
         575  +
            .profile("profile-via-creds-provider")
         576  +
            .configure(&provider_config)
         577  +
            .imds_client(client.clone())
         578  +
            .build();
         579  +
        assert_eq!(
         580  +
            Some(Cow::Borrowed("profile-via-creds-provider")),
         581  +
            provider.configured_instance_profile_name().await.unwrap()
         582  +
        );
         583  +
         584  +
        // negative test with a blank profile name
         585  +
        let provider = ImdsCredentialsProvider::builder()
         586  +
            .profile("")
         587  +
            .configure(&provider_config)
         588  +
            .imds_client(client)
         589  +
            .build();
         590  +
        let err = provider
         591  +
            .configured_instance_profile_name()
         592  +
            .await
         593  +
            .err()
         594  +
            .unwrap();
         595  +
        assert!(format!("{}", DisplayErrorContext(&err))
         596  +
            .contains("blank profile name is not supported"));
         597  +
    }
         598  +
         599  +
    #[tokio::test]
         600  +
    #[traced_test]
         601  +
    #[cfg(feature = "default-https-client")]
         602  +
    async fn environment_priority_on_ec2_instance_profile_name() {
         603  +
        let provider_config = ProviderConfig::empty()
         604  +
            .with_env(aws_types::os_shim_internal::Env::from_slice(&[(
         605  +
                imds::env::EC2_INSTANCE_PROFILE_NAME,
         606  +
                "profile-via-env",
         607  +
            )]))
         608  +
            .with_profile_config(
         609  +
                Some(
         610  +
                    #[allow(deprecated)]
         611  +
                    crate::profile::profile_file::ProfileFiles::builder()
         612  +
                        .with_file(
         613  +
                            #[allow(deprecated)]
         614  +
                            crate::profile::profile_file::ProfileFileKind::Config,
         615  +
                            "conf",
         616  +
                        )
         617  +
                        .build(),
         618  +
                ),
         619  +
                None,
         620  +
            )
         621  +
            .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[(
         622  +
                "conf",
         623  +
                "[default]\nec2_instance_profile_name = profile-via-profile-file",
         624  +
            )]));
         625  +
        let client = crate::imds::Client::builder()
         626  +
            .configure(&provider_config)
         627  +
            .build();
         628  +
        let provider = ImdsCredentialsProvider::builder()
         629  +
            .configure(&provider_config)
         630  +
            .imds_client(client)
         631  +
            .build();
         632  +
        assert_eq!(
         633  +
            Some(Cow::Borrowed("profile-via-env")),
         634  +
            provider.configured_instance_profile_name().await.unwrap()
         635  +
        );
         636  +
    }
         637  +
         638  +
    #[tokio::test]
         639  +
    #[traced_test]
         640  +
    #[cfg(feature = "default-https-client")]
         641  +
    async fn ec2_instance_profile_name_via_profile_file() {
         642  +
        let provider_config = ProviderConfig::empty()
         643  +
            .with_profile_config(
         644  +
                Some(
         645  +
                    #[allow(deprecated)]
         646  +
                    crate::profile::profile_file::ProfileFiles::builder()
         647  +
                        .with_file(
         648  +
                            #[allow(deprecated)]
         649  +
                            crate::profile::profile_file::ProfileFileKind::Config,
         650  +
                            "conf",
         651  +
                        )
         652  +
                        .build(),
         653  +
                ),
         654  +
                None,
         655  +
            )
         656  +
            .with_fs(aws_types::os_shim_internal::Fs::from_slice(&[(
         657  +
                "conf",
         658  +
                "[default]\nec2_instance_profile_name = profile-via-profile-file",
         659  +
            )]));
         660  +
        let client = crate::imds::Client::builder()
         661  +
            .configure(&provider_config)
         662  +
            .build();
         663  +
        let provider = ImdsCredentialsProvider::builder()
         664  +
            .configure(&provider_config)
         665  +
            .imds_client(client)
         666  +
            .build();
         667  +
        assert_eq!(
         668  +
            Some(Cow::Borrowed("profile-via-profile-file")),
         669  +
            provider.configured_instance_profile_name().await.unwrap()
         670  +
        );
  335    671   
    }
  336    672   
  337    673   
    #[tokio::test]
  338    674   
    #[traced_test]
  339    675   
    async fn credentials_not_stale_should_be_used_as_they_are() {
  340    676   
        let http_client = StaticReplayClient::new(vec![
  341    677   
            ReplayEvent::new(
  342    678   
                token_request("http://169.254.169.254", 21600),
  343    679   
                token_response(21600, TOKEN_A),
  344    680   
            ),
  345    681   
            ReplayEvent::new(
  346         -
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
         682  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
  347    683   
                imds_response(r#"profile-name"#),
  348    684   
            ),
  349    685   
            ReplayEvent::new(
  350         -
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A),
         686  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A),
  351    687   
                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}"),
  352    688   
            ),
  353    689   
        ]);
  354    690   
  355    691   
        // set to 2021-09-21T04:16:50Z that makes returned credentials' expiry (2021-09-21T04:16:53Z)
  356    692   
        // not stale
  357    693   
        let time_of_request_to_fetch_credentials = UNIX_EPOCH + Duration::from_secs(1632197810);
  358    694   
        let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials);
  359    695   
  360    696   
        let provider_config = ProviderConfig::no_configuration()
  361    697   
            .with_http_client(http_client.clone())
  362    698   
            .with_sleep_impl(sleep)
  363    699   
            .with_time_source(time_source);
  364    700   
        let client = crate::imds::Client::builder()
  365    701   
            .configure(&provider_config)
  366    702   
            .build();
  367    703   
        let provider = ImdsCredentialsProvider::builder()
  368    704   
            .configure(&provider_config)
  369    705   
            .imds_client(client)
  370    706   
            .build();
  371    707   
        let creds = provider.provide_credentials().await.expect("valid creds");
  372    708   
        // The expiry should be equal to what is originally set (==2021-09-21T04:16:53Z).
  373    709   
        assert_eq!(
  374    710   
            creds.expiry(),
  375    711   
            UNIX_EPOCH.checked_add(Duration::from_secs(1632197813))
  376    712   
        );
         713  +
        assert!(creds.account_id().is_none());
  377    714   
        http_client.assert_requests_match(&[]);
  378    715   
  379    716   
        // There should not be logs indicating credentials are extended for stability.
  380    717   
        assert!(!logs_contain(WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY));
  381    718   
    }
         719  +
  382    720   
    #[tokio::test]
  383    721   
    #[traced_test]
  384    722   
    async fn expired_credentials_should_be_extended() {
  385    723   
        let http_client = StaticReplayClient::new(vec![
  386    724   
                ReplayEvent::new(
  387    725   
                    token_request("http://169.254.169.254", 21600),
  388    726   
                    token_response(21600, TOKEN_A),
  389    727   
                ),
  390    728   
                ReplayEvent::new(
  391         -
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
         729  +
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
  392    730   
                    imds_response(r#"profile-name"#),
  393    731   
                ),
  394    732   
                ReplayEvent::new(
  395         -
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A),
         733  +
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A),
  396    734   
                    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}"),
  397    735   
                ),
  398    736   
            ]);
  399    737   
  400    738   
        // set to 2021-09-21T17:41:25Z that renders fetched credentials already expired (2021-09-21T04:16:53Z)
  401    739   
        let time_of_request_to_fetch_credentials = UNIX_EPOCH + Duration::from_secs(1632246085);
  402    740   
        let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials);
  403    741   
  404    742   
        let provider_config = ProviderConfig::no_configuration()
  405    743   
            .with_http_client(http_client.clone())
  406    744   
            .with_sleep_impl(sleep)
  407    745   
            .with_time_source(time_source);
  408    746   
        let client = crate::imds::Client::builder()
  409    747   
            .configure(&provider_config)
  410    748   
            .build();
  411    749   
        let provider = ImdsCredentialsProvider::builder()
  412    750   
            .configure(&provider_config)
  413    751   
            .imds_client(client)
  414    752   
            .build();
  415    753   
        let creds = provider.provide_credentials().await.expect("valid creds");
  416    754   
        assert!(creds.expiry().unwrap() > time_of_request_to_fetch_credentials);
  417    755   
        http_client.assert_requests_match(&[]);
  418    756   
  419    757   
        // We should inform customers that expired credentials are being used for stability.
  420    758   
        assert!(logs_contain(WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY));
  421    759   
    }
  422    760   
  423    761   
    #[tokio::test]
  424    762   
    #[cfg(feature = "default-https-client")]
  425    763   
    async fn read_timeout_during_credentials_refresh_should_yield_last_retrieved_credentials() {
  426    764   
        let client = crate::imds::Client::builder()
  427    765   
            // 240.* can never be resolved
  428    766   
            .endpoint("http://240.0.0.0")
  429    767   
            .unwrap()
  430    768   
            .build();
  431    769   
        let expected = aws_credential_types::Credentials::for_tests();
  432    770   
        let provider = ImdsCredentialsProvider::builder()
  433    771   
            .imds_client(client)
  434    772   
            // seed fallback credentials for testing
  435    773   
            .last_retrieved_credentials(expected.clone())
  436    774   
            .build();
  437         -
        let actual = provider.provide_credentials().await;
  438         -
        assert_eq!(actual.unwrap(), expected);
         775  +
        let actual = provider.provide_credentials().await.unwrap();
         776  +
        assert_eq!(expected, actual);
  439    777   
    }
  440    778   
  441    779   
    #[tokio::test]
  442    780   
    #[cfg(feature = "default-https-client")]
  443    781   
    async fn read_timeout_during_credentials_refresh_should_error_without_last_retrieved_credentials(
  444    782   
    ) {
  445    783   
        let client = crate::imds::Client::builder()
  446    784   
            // 240.* can never be resolved
  447    785   
            .endpoint("http://240.0.0.0")
  448    786   
            .unwrap()
  449    787   
            .build();
  450    788   
        let provider = ImdsCredentialsProvider::builder()
  451    789   
            .imds_client(client)
  452    790   
            // no fallback credentials provided
  453    791   
            .build();
  454         -
        let actual = provider.provide_credentials().await;
         792  +
        let actual = provider.provide_credentials().await.err().unwrap();
  455    793   
        assert!(
  456         -
            matches!(actual, Err(CredentialsError::CredentialsNotLoaded(_))),
         794  +
            matches!(actual, CredentialsError::CredentialsNotLoaded(_)),
  457    795   
            "\nexpected: Err(CredentialsError::CredentialsNotLoaded(_))\nactual: {actual:?}"
  458    796   
        );
  459    797   
    }
  460    798   
  461         -
    // TODO(https://github.com/awslabs/aws-sdk-rust/issues/1117) This test is ignored on Windows because it uses Unix-style paths
  462         -
    #[cfg_attr(windows, ignore)]
  463    799   
    #[tokio::test]
  464    800   
    #[cfg(feature = "default-https-client")]
  465    801   
    async fn external_timeout_during_credentials_refresh_should_yield_last_retrieved_credentials() {
  466    802   
        use aws_smithy_async::rt::sleep::AsyncSleep;
  467    803   
        let client = crate::imds::Client::builder()
  468    804   
            // 240.* can never be resolved
  469    805   
            .endpoint("http://240.0.0.0")
  470    806   
            .unwrap()
  471    807   
            .build();
  472    808   
        let expected = aws_credential_types::Credentials::for_tests();
  473    809   
        let provider = ImdsCredentialsProvider::builder()
  474    810   
            .imds_client(client)
  475    811   
            .configure(&ProviderConfig::no_configuration())
  476    812   
            // seed fallback credentials for testing
  477    813   
            .last_retrieved_credentials(expected.clone())
  478    814   
            .build();
  479    815   
        let sleeper = aws_smithy_async::rt::sleep::TokioSleep::new();
  480    816   
        let timeout = aws_smithy_async::future::timeout::Timeout::new(
  481    817   
            provider.provide_credentials(),
  482    818   
            // make sure `sleeper.sleep` will be timed out first by setting a shorter duration than connect timeout
  483    819   
            sleeper.sleep(std::time::Duration::from_millis(100)),
  484    820   
        );
  485    821   
        match timeout.await {
  486    822   
            Ok(_) => panic!("provide_credentials completed before timeout future"),
  487    823   
            Err(_err) => match provider.fallback_on_interrupt() {
  488         -
                Some(actual) => assert_eq!(actual, expected),
         824  +
                Some(actual) => assert_eq!(expected, actual),
  489    825   
                None => panic!(
  490    826   
                    "provide_credentials timed out and no credentials returned from fallback_on_interrupt"
  491    827   
                ),
  492    828   
            },
  493    829   
        };
  494    830   
    }
  495    831   
  496    832   
    #[tokio::test]
  497    833   
    async fn fallback_credentials_should_be_used_when_imds_returns_500_during_credentials_refresh()
  498    834   
    {
  499    835   
        let http_client = StaticReplayClient::new(vec![
  500    836   
                // The next three request/response pairs will correspond to the first call to `provide_credentials`.
  501    837   
                // During the call, it populates last_retrieved_credentials.
  502    838   
                ReplayEvent::new(
  503    839   
                    token_request("http://169.254.169.254", 21600),
  504    840   
                    token_response(21600, TOKEN_A),
  505    841   
                ),
  506    842   
                ReplayEvent::new(
  507         -
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
         843  +
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
  508    844   
                    imds_response(r#"profile-name"#),
  509    845   
                ),
  510    846   
                ReplayEvent::new(
  511         -
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A),
         847  +
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A),
  512    848   
                    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}"),
  513    849   
                ),
  514    850   
                // The following request/response pair corresponds to the second call to `provide_credentials`.
  515    851   
                // During the call, IMDS returns response code 500.
  516    852   
                ReplayEvent::new(
  517         -
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
         853  +
                    imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/profile-name", TOKEN_A),
  518    854   
                    http::Response::builder().status(500).body(SdkBody::empty()).unwrap(),
  519    855   
                ),
  520    856   
            ]);
  521    857   
        let provider = ImdsCredentialsProvider::builder()
  522    858   
            .imds_client(make_imds_client(&http_client))
  523    859   
            .configure(&ProviderConfig::no_configuration())
  524    860   
            .build();
  525    861   
        let creds1 = provider.provide_credentials().await.expect("valid creds");
  526         -
        assert_eq!(creds1.access_key_id(), "ASIARTEST");
  527         -
        // `creds1` should be returned as fallback credentials and assigned to `creds2`
  528         -
        let creds2 = provider.provide_credentials().await.expect("valid creds");
  529         -
        assert_eq!(creds1, creds2);
         862  +
        assert_eq!("ASIARTEST", creds1.access_key_id());
         863  +
        // `creds1` should be returned as fallback credentials
         864  +
        assert_eq!(
         865  +
            creds1,
         866  +
            provider.provide_credentials().await.expect("valid creds")
         867  +
        );
         868  +
        http_client.assert_requests_match(&[]);
         869  +
    }
         870  +
         871  +
    async fn run_test<F>(
         872  +
        events: Vec<ReplayEvent>,
         873  +
        update_builder: fn(Builder) -> Builder,
         874  +
        runner: F,
         875  +
    ) where
         876  +
        F: Fn(ImdsCredentialsProvider) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
         877  +
    {
         878  +
        let http_client = StaticReplayClient::new(events);
         879  +
        let builder = ImdsCredentialsProvider::builder()
         880  +
            .imds_client(make_imds_client(&http_client))
         881  +
            .configure(&ProviderConfig::no_configuration());
         882  +
        let provider = update_builder(builder).build();
         883  +
        runner(provider).await;
  530    884   
        http_client.assert_requests_match(&[]);
  531    885   
    }
         886  +
         887  +
    async fn assert(provider: ImdsCredentialsProvider, expected: &[(Option<&str>, Option<&str>)]) {
         888  +
        for (expected_access_key_id, expected_account_id) in expected {
         889  +
            let creds = provider.provide_credentials().await.expect("valid creds");
         890  +
            assert_eq!(expected_access_key_id, &Some(creds.access_key_id()),);
         891  +
            assert_eq!(
         892  +
                expected_account_id,
         893  +
                &creds.account_id().map(|id| id.as_str())
         894  +
            );
         895  +
        }
         896  +
    }
         897  +
         898  +
    #[tokio::test]
         899  +
    async fn returns_valid_credentials_with_account_id() {
         900  +
        let extended_api_events = vec![
         901  +
            ReplayEvent::new(
         902  +
                token_request("http://169.254.169.254", 21600),
         903  +
                token_response(21600, TOKEN_A),
         904  +
            ),
         905  +
            // A profile is not cached, so we should expect a network call to obtain one.
         906  +
            ReplayEvent::new(
         907  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
         908  +
                imds_response(r#"my-profile-0001"#),
         909  +
            ),
         910  +
            ReplayEvent::new(
         911  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0001", TOKEN_A),
         912  +
                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}"),
         913  +
            ),
         914  +
            // 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.
         915  +
            ReplayEvent::new(
         916  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0001", TOKEN_A),
         917  +
                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}"),
         918  +
            ),
         919  +
        ];
         920  +
        run_test(extended_api_events, IdentityFn, |provider| {
         921  +
            Box::pin(assert(
         922  +
                provider,
         923  +
                &[
         924  +
                    (Some("ASIARTEST"), Some("123456789101")),
         925  +
                    (Some("ASIARTEST"), Some("123456789101")),
         926  +
                ],
         927  +
            ))
         928  +
        })
         929  +
        .await;
         930  +
         931  +
        let legacy_api_events = vec![
         932  +
            ReplayEvent::new(
         933  +
                token_request("http://169.254.169.254", 21600),
         934  +
                token_response(21600, TOKEN_A),
         935  +
            ),
         936  +
            // Obtaining a profile from IMDS using the extended API results in 404.
         937  +
            ReplayEvent::new(
         938  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
         939  +
                imds_response_404(),
         940  +
            ),
         941  +
            // Should be retried using the legacy API.
         942  +
            ReplayEvent::new(
         943  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
         944  +
                imds_response(r#"my-profile-0009"#),
         945  +
            ),
         946  +
            ReplayEvent::new(
         947  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0009", TOKEN_A),
         948  +
                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}"),
         949  +
            ),
         950  +
            ReplayEvent::new(
         951  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0009", TOKEN_A),
         952  +
                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}"),
         953  +
            ),
         954  +
        ];
         955  +
        run_test(legacy_api_events, IdentityFn, |provider| {
         956  +
            Box::pin(assert(
         957  +
                provider,
         958  +
                &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)],
         959  +
            ))
         960  +
        })
         961  +
        .await;
         962  +
    }
         963  +
         964  +
    #[tokio::test]
         965  +
    async fn should_return_credentials_when_profile_is_configured_by_user() {
         966  +
        let extended_api_events = vec![
         967  +
            ReplayEvent::new(
         968  +
                token_request("http://169.254.169.254", 21600),
         969  +
                token_response(21600, TOKEN_A),
         970  +
            ),
         971  +
            ReplayEvent::new(
         972  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0002", TOKEN_A),
         973  +
                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}"),
         974  +
            ),
         975  +
            ReplayEvent::new(
         976  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0002", TOKEN_A),
         977  +
                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}"),
         978  +
            ),
         979  +
        ];
         980  +
        run_test(
         981  +
            extended_api_events,
         982  +
            |b| b.profile("my-profile-0002"),
         983  +
            |provider| {
         984  +
                Box::pin(assert(
         985  +
                    provider,
         986  +
                    &[
         987  +
                        (Some("ASIARTEST"), Some("234567891011")),
         988  +
                        (Some("ASIARTEST"), Some("234567891011")),
         989  +
                    ],
         990  +
                ))
         991  +
            },
         992  +
        )
         993  +
        .await;
         994  +
         995  +
        let legacy_api_events = vec![
         996  +
            ReplayEvent::new(
         997  +
                token_request("http://169.254.169.254", 21600),
         998  +
                token_response(21600, TOKEN_A),
         999  +
            ),
        1000  +
            // Obtaining a credentials using the extended API results in 404.
        1001  +
            ReplayEvent::new(
        1002  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0010", TOKEN_A),
        1003  +
                imds_response_404(),
        1004  +
            ),
        1005  +
            // Obtain credentials using the legacy API with the configured profile.
        1006  +
            ReplayEvent::new(
        1007  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0010", TOKEN_A),
        1008  +
                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}"),
        1009  +
            ),
        1010  +
            ReplayEvent::new(
        1011  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0010", TOKEN_A),
        1012  +
                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}"),
        1013  +
            ),
        1014  +
        ];
        1015  +
        run_test(
        1016  +
            legacy_api_events,
        1017  +
            |b| b.profile("my-profile-0010"),
        1018  +
            |provider| {
        1019  +
                Box::pin(assert(
        1020  +
                    provider,
        1021  +
                    &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)],
        1022  +
                ))
        1023  +
            },
        1024  +
        )
        1025  +
        .await;
        1026  +
    }
        1027  +
        1028  +
    #[tokio::test]
        1029  +
    async fn should_return_valid_credentials_when_profile_is_unstable() {
        1030  +
        let extended_api_events = vec![
        1031  +
            // First call to `provide_credentials` succeeds with the extended API.
        1032  +
            ReplayEvent::new(
        1033  +
                token_request("http://169.254.169.254", 21600),
        1034  +
                token_response(21600, TOKEN_A),
        1035  +
            ),
        1036  +
            ReplayEvent::new(
        1037  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
        1038  +
                imds_response(r#"my-profile-0003"#),
        1039  +
            ),
        1040  +
            ReplayEvent::new(
        1041  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0003", TOKEN_A),
        1042  +
                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}"),
        1043  +
            ),
        1044  +
        1045  +
            // Credentials retrieval failed due to unstable profile.
        1046  +
            ReplayEvent::new(
        1047  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0003", TOKEN_A),
        1048  +
                imds_response_404(),
        1049  +
            ),
        1050  +
            // Start over and retrieve a new profile with the extended API.
        1051  +
            ReplayEvent::new(
        1052  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
        1053  +
                imds_response(r#"my-profile-0003-b"#),
        1054  +
            ),
        1055  +
            ReplayEvent::new(
        1056  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0003-b", TOKEN_A),
        1057  +
                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}"),
        1058  +
            ),
        1059  +
        ];
        1060  +
        run_test(extended_api_events, IdentityFn, |provider| {
        1061  +
            Box::pin(assert(
        1062  +
                provider,
        1063  +
                &[
        1064  +
                    (Some("ASIARTEST"), Some("345678910112")),
        1065  +
                    (Some("ASIARTEST"), Some("314253647589")),
        1066  +
                ],
        1067  +
            ))
        1068  +
        })
        1069  +
        .await;
        1070  +
        1071  +
        let legacy_api_events = vec![
        1072  +
            ReplayEvent::new(
        1073  +
                token_request("http://169.254.169.254", 21600),
        1074  +
                token_response(21600, TOKEN_A),
        1075  +
            ),
        1076  +
            ReplayEvent::new(
        1077  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
        1078  +
                imds_response_404()
        1079  +
            ),
        1080  +
            ReplayEvent::new(
        1081  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
        1082  +
                imds_response(r#"my-profile-0011"#),
        1083  +
            ),
        1084  +
            ReplayEvent::new(
        1085  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0011", TOKEN_A),
        1086  +
                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}"),
        1087  +
            ),
        1088  +
            // Credentials retrieval failed due to unstable profile.
        1089  +
            ReplayEvent::new(
        1090  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0011", TOKEN_A),
        1091  +
                imds_response_404()
        1092  +
            ),
        1093  +
            // Start over and retrieve a new profile with the legacy API.
        1094  +
            ReplayEvent::new(
        1095  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A),
        1096  +
                imds_response(r#"my-profile-0011-b"#),
        1097  +
            ),
        1098  +
            ReplayEvent::new(
        1099  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0011-b", TOKEN_A),
        1100  +
                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}"),
        1101  +
            ),
        1102  +
        ];
        1103  +
        run_test(legacy_api_events, IdentityFn, |provider| {
        1104  +
            Box::pin(assert(
        1105  +
                provider,
        1106  +
                &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)],
        1107  +
            ))
        1108  +
        })
        1109  +
        .await;
        1110  +
    }
        1111  +
        1112  +
    #[tokio::test]
        1113  +
    async fn should_error_when_imds_remains_unstable_with_profile_configured_by_user() {
        1114  +
        // This negative test exercises the same code path for both the extended and legacy APIs.
        1115  +
        // A single set of events is sufficient for testing both.
        1116  +
        let events = vec![
        1117  +
            ReplayEvent::new(
        1118  +
                token_request("http://169.254.169.254", 21600),
        1119  +
                token_response(21600, TOKEN_A),
        1120  +
            ),
        1121  +
            ReplayEvent::new(
        1122  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0004", TOKEN_A),
        1123  +
                imds_response_404(),
        1124  +
            ),
        1125  +
            // Try obtaining credentials again with the legacy API
        1126  +
            ReplayEvent::new(
        1127  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/my-profile-0004", TOKEN_A),
        1128  +
                imds_response_404(),
        1129  +
            ),
        1130  +
        ];
        1131  +
        run_test(
        1132  +
            events,
        1133  +
            |b| b.profile("my-profile-0004"),
        1134  +
            |provider| {
        1135  +
                Box::pin(async move {
        1136  +
                    let err = provider.provide_credentials().await.err().unwrap();
        1137  +
                    matches!(err, CredentialsError::CredentialsNotLoaded(_));
        1138  +
                })
        1139  +
            },
        1140  +
        )
        1141  +
        .await;
        1142  +
    }
        1143  +
        1144  +
    #[tokio::test]
        1145  +
    async fn returns_valid_credentials_without_account_id_using_extended_api() {
        1146  +
        let extended_api_events = vec![
        1147  +
            ReplayEvent::new(
        1148  +
                token_request("http://169.254.169.254", 21600),
        1149  +
                token_response(21600, TOKEN_A),
        1150  +
            ),
        1151  +
            ReplayEvent::new(
        1152  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/", TOKEN_A),
        1153  +
                imds_response(r#"my-profile-0005"#),
        1154  +
            ),
        1155  +
            ReplayEvent::new(
        1156  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0005", TOKEN_A),
        1157  +
                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}"),
        1158  +
            ),
        1159  +
            ReplayEvent::new(
        1160  +
                imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/my-profile-0005", TOKEN_A),
        1161  +
                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}"),
        1162  +
            ),
        1163  +
        ];
        1164  +
        run_test(extended_api_events, IdentityFn, |provider| {
        1165  +
            Box::pin(assert(
        1166  +
                provider,
        1167  +
                &[(Some("ASIARTEST"), None), (Some("ASIARTEST"), None)],
        1168  +
            ))
        1169  +
        })
        1170  +
        .await;
        1171  +
    }
  532   1172   
}

tmp-codegen-diff/aws-sdk/sdk/aws-config/src/imds/mod.rs

@@ -1,1 +19,25 @@
    6      6   
//! IMDSv2 Client, credential, and region provider
    7      7   
//!
    8      8   
//! See [`client`] for more information.
    9      9   
pub mod client;
   10     10   
   11     11   
pub mod credentials;
   12     12   
pub mod region;
   13     13   
   14     14   
mod env {
   15     15   
    pub(crate) const EC2_METADATA_DISABLED: &str = "AWS_EC2_METADATA_DISABLED";
          16  +
    pub(crate) const EC2_INSTANCE_PROFILE_NAME: &str = "AWS_EC2_INSTANCE_PROFILE_NAME";
          17  +
}
          18  +
          19  +
mod profile_key {
          20  +
    pub(crate) const EC2_METADATA_DISABLED: &str = "disable_ec2_metadata";
          21  +
    pub(crate) const EC2_INSTANCE_PROFILE_NAME: &str = "ec2_instance_profile_name";
   16     22   
}
   17     23   
   18     24   
#[doc(inline)]
   19     25   
pub use client::Client;

tmp-codegen-diff/aws-sdk/sdk/aws-config/test-data/default-credential-provider-chain/imds_assume_role/http-traffic.json

@@ -62,62 +122,122 @@
   82     82   
                    "ok": true,
   83     83   
                    "direction": "Response"
   84     84   
                }
   85     85   
            }
   86     86   
        },
   87     87   
        {
   88     88   
            "connection_id": 1,
   89     89   
            "action": {
   90     90   
                "Request": {
   91     91   
                    "request": {
   92         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
          92  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/",
   93     93   
                        "headers": {
   94     94   
                            "x-aws-ec2-metadata-token": [
   95     95   
                                "imdssesiontoken=="
   96     96   
                            ],
   97     97   
                            "user-agent": [
   98     98   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
   99     99   
                            ],
  100    100   
                            "x-amz-user-agent": [
  101    101   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  102    102   
                            ]
@@ -152,152 +212,212 @@
  172    172   
                    "ok": true,
  173    173   
                    "direction": "Response"
  174    174   
                }
  175    175   
            }
  176    176   
        },
  177    177   
        {
  178    178   
            "connection_id": 2,
  179    179   
            "action": {
  180    180   
                "Request": {
  181    181   
                    "request": {
  182         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test",
         182  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test",
  183    183   
                        "headers": {
  184    184   
                            "user-agent": [
  185    185   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  186    186   
                            ],
  187    187   
                            "x-aws-ec2-metadata-token": [
  188    188   
                                "imdssesiontoken=="
  189    189   
                            ],
  190    190   
                            "x-amz-user-agent": [
  191    191   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  192    192   
                            ]

tmp-codegen-diff/aws-sdk/sdk/aws-config/test-data/default-credential-provider-chain/imds_config_with_no_creds/http-traffic.json

@@ -62,62 +122,122 @@
   82     82   
                    "ok": true,
   83     83   
                    "direction": "Response"
   84     84   
                }
   85     85   
            }
   86     86   
        },
   87     87   
        {
   88     88   
            "connection_id": 1,
   89     89   
            "action": {
   90     90   
                "Request": {
   91     91   
                    "request": {
   92         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
          92  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/",
   93     93   
                        "headers": {
   94     94   
                            "x-amz-user-agent": [
   95     95   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
   96     96   
                            ],
   97     97   
                            "x-aws-ec2-metadata-token": [
   98     98   
                                "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw=="
   99     99   
                            ],
  100    100   
                            "user-agent": [
  101    101   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  102    102   
                            ]
@@ -152,152 +212,212 @@
  172    172   
                    "ok": true,
  173    173   
                    "direction": "Response"
  174    174   
                }
  175    175   
            }
  176    176   
        },
  177    177   
        {
  178    178   
            "connection_id": 2,
  179    179   
            "action": {
  180    180   
                "Request": {
  181    181   
                    "request": {
  182         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test",
         182  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test",
  183    183   
                        "headers": {
  184    184   
                            "x-aws-ec2-metadata-token": [
  185    185   
                                "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw=="
  186    186   
                            ],
  187    187   
                            "user-agent": [
  188    188   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  189    189   
                            ],
  190    190   
                            "x-amz-user-agent": [
  191    191   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  192    192   
                            ]

tmp-codegen-diff/aws-sdk/sdk/aws-config/test-data/default-credential-provider-chain/imds_default_chain_error/http-traffic.json

@@ -62,62 +122,122 @@
   82     82   
                    "ok": true,
   83     83   
                    "direction": "Response"
   84     84   
                }
   85     85   
            }
   86     86   
        },
   87     87   
        {
   88     88   
            "connection_id": 1,
   89     89   
            "action": {
   90     90   
                "Request": {
   91     91   
                    "request": {
   92         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
          92  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/",
   93     93   
                        "headers": {
   94     94   
                            "user-agent": [
   95     95   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
   96     96   
                            ],
   97     97   
                            "x-amz-user-agent": [
   98     98   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
   99     99   
                            ],
  100    100   
                            "x-aws-ec2-metadata-token": [
  101    101   
                               "faketoken"
  102    102   
                            ]
@@ -140,140 +174,258 @@
  160    160   
            }
  161    161   
        },
  162    162   
        {
  163    163   
            "connection_id": 1,
  164    164   
            "action": {
  165    165   
                "Eof": {
  166    166   
                    "ok": true,
  167    167   
                    "direction": "Response"
  168    168   
                }
  169    169   
            }
         170  +
        },
         171  +
        {
         172  +
            "connection_id": 2,
         173  +
            "action": {
         174  +
                "Request": {
         175  +
                    "request": {
         176  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
         177  +
                        "headers": {
         178  +
                            "user-agent": [
         179  +
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
         180  +
                            ],
         181  +
                            "x-amz-user-agent": [
         182  +
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
         183  +
                            ],
         184  +
                            "x-aws-ec2-metadata-token": [
         185  +
                               "faketoken"
         186  +
                            ]
         187  +
                        },
         188  +
                        "method": "GET"
         189  +
                    }
         190  +
                }
         191  +
            }
         192  +
        },
         193  +
        {
         194  +
            "connection_id": 2,
         195  +
            "action": {
         196  +
                "Eof": {
         197  +
                    "ok": true,
         198  +
                    "direction": "Request"
         199  +
                }
         200  +
            }
         201  +
        },
         202  +
        {
         203  +
            "connection_id": 2,
         204  +
            "action": {
         205  +
                "Response": {
         206  +
                    "response": {
         207  +
                        "Ok": {
         208  +
                            "status": 404,
         209  +
                            "version": "HTTP/1.1",
         210  +
                            "headers": {
         211  +
                                "content-length": [
         212  +
                                    "339"
         213  +
                                ],
         214  +
                                "date": [
         215  +
                                    "Mon, 20 Sep 2021 20:51:52 GMT"
         216  +
                                ],
         217  +
                                "content-type": [
         218  +
                                    "text/html"
         219  +
                                ],
         220  +
                                "x-aws-ec2-metadata-token-ttl-seconds": [
         221  +
                                    "21600"
         222  +
                                ],
         223  +
                                "connection": [
         224  +
                                    "close"
         225  +
                                ],
         226  +
                                "server": [
         227  +
                                    "EC2ws"
         228  +
                                ]
         229  +
                            }
         230  +
                        }
         231  +
                    }
         232  +
                }
         233  +
            }
         234  +
        },
         235  +
        {
         236  +
            "connection_id": 2,
         237  +
            "action": {
         238  +
                "Data": {
         239  +
                    "data": {
         240  +
                        "Utf8": "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n\t\t \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n <head>\n  <title>404 - Not Found</title>\n </head>\n <body>\n  <h1>404 - Not Found</h1>\n </body>\n</html>\n"
         241  +
                    },
         242  +
                    "direction": "Response"
         243  +
                }
         244  +
            }
         245  +
        },
         246  +
        {
         247  +
            "connection_id": 2,
         248  +
            "action": {
         249  +
                "Eof": {
         250  +
                    "ok": true,
         251  +
                    "direction": "Response"
         252  +
                }
         253  +
            }
  170    254   
        }
  171    255   
    ],
  172    256   
    "docs": "live IMDS token retrieval",
  173    257   
    "version": "V0"
  174    258   
}

tmp-codegen-diff/aws-sdk/sdk/aws-config/test-data/default-credential-provider-chain/imds_default_chain_retries/http-traffic.json

@@ -116,116 +230,230 @@
  136    136   
                    "ok": true,
  137    137   
                    "direction": "Response"
  138    138   
                }
  139    139   
            }
  140    140   
        },
  141    141   
        {
  142    142   
            "connection_id": 2,
  143    143   
            "action": {
  144    144   
                "Request": {
  145    145   
                    "request": {
  146         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
         146  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/",
  147    147   
                        "headers": {
  148    148   
                            "x-amz-user-agent": [
  149    149   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  150    150   
                            ],
  151    151   
                            "x-aws-ec2-metadata-token": [
  152    152   
                                "imdstoken"
  153    153   
                            ],
  154    154   
                            "user-agent": [
  155    155   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  156    156   
                            ]
  157    157   
                        },
  158    158   
                        "method": "GET"
  159    159   
                    }
  160    160   
                }
  161    161   
            }
  162    162   
        },
  163    163   
        {
  164    164   
            "connection_id": 2,
  165    165   
            "action": {
  166    166   
                "Response": {
  167    167   
                    "response": {
  168    168   
                        "Ok": {
  169    169   
                            "status": 503,
  170    170   
                            "version": "HTTP/1.1",
  171    171   
                            "headers": {}
  172    172   
                        }
  173    173   
                    }
  174    174   
                }
  175    175   
            }
  176    176   
        },
  177    177   
        {
  178    178   
            "connection_id": 2,
  179    179   
            "action": {
  180    180   
                "Eof": {
  181    181   
                    "ok": true,
  182    182   
                    "direction": "Request"
  183    183   
                }
  184    184   
            }
  185    185   
        },
  186    186   
        {
  187    187   
            "connection_id": 2,
  188    188   
            "action": {
  189    189   
                "Eof": {
  190    190   
                    "ok": true,
  191    191   
                    "direction": "Response"
  192    192   
                }
  193    193   
            }
  194    194   
        },
  195    195   
        {
  196    196   
            "connection_id": 3,
  197    197   
            "action": {
  198    198   
                "Request": {
  199    199   
                    "request": {
  200         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
         200  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/",
  201    201   
                        "headers": {
  202    202   
                            "x-aws-ec2-metadata-token": [
  203    203   
                                "imdstoken"
  204    204   
                            ],
  205    205   
                            "user-agent": [
  206    206   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  207    207   
                            ],
  208    208   
                            "x-amz-user-agent": [
  209    209   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  210    210   
                            ]
@@ -260,260 +374,374 @@
  280    280   
                    "ok": true,
  281    281   
                    "direction": "Response"
  282    282   
                }
  283    283   
            }
  284    284   
        },
  285    285   
        {
  286    286   
            "connection_id": 4,
  287    287   
            "action": {
  288    288   
                "Request": {
  289    289   
                    "request": {
  290         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test",
         290  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test",
  291    291   
                        "headers": {
  292    292   
                            "x-amz-user-agent": [
  293    293   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  294    294   
                            ],
  295    295   
                            "user-agent": [
  296    296   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  297    297   
                            ],
  298    298   
                            "x-aws-ec2-metadata-token": [
  299    299   
                                "imdstoken"
  300    300   
                            ]
  301    301   
                        },
  302    302   
                        "method": "GET"
  303    303   
                    }
  304    304   
                }
  305    305   
            }
  306    306   
        },
  307    307   
        {
  308    308   
            "connection_id": 4,
  309    309   
            "action": {
  310    310   
                "Response": {
  311    311   
                    "response": {
  312    312   
                        "Ok": {
  313    313   
                            "status": 503,
  314    314   
                            "version": "HTTP/1.1",
  315    315   
                            "headers": {}
  316    316   
                        }
  317    317   
                    }
  318    318   
                }
  319    319   
            }
  320    320   
        },
  321    321   
        {
  322    322   
            "connection_id": 4,
  323    323   
            "action": {
  324    324   
                "Eof": {
  325    325   
                    "ok": true,
  326    326   
                    "direction": "Request"
  327    327   
                }
  328    328   
            }
  329    329   
        },
  330    330   
        {
  331    331   
            "connection_id": 4,
  332    332   
            "action": {
  333    333   
                "Eof": {
  334    334   
                    "ok": true,
  335    335   
                    "direction": "Response"
  336    336   
                }
  337    337   
            }
  338    338   
        },
  339    339   
        {
  340    340   
            "connection_id": 5,
  341    341   
            "action": {
  342    342   
                "Request": {
  343    343   
                    "request": {
  344         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test",
         344  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test",
  345    345   
                        "headers": {
  346    346   
                            "user-agent": [
  347    347   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  348    348   
                            ],
  349    349   
                            "x-amz-user-agent": [
  350    350   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  351    351   
                            ],
  352    352   
                            "x-aws-ec2-metadata-token": [
  353    353   
                                "imdstoken"
  354    354   
                            ]

tmp-codegen-diff/aws-sdk/sdk/aws-config/test-data/default-credential-provider-chain/imds_default_chain_success/http-traffic.json

@@ -62,62 +122,122 @@
   82     82   
                    "ok": true,
   83     83   
                    "direction": "Response"
   84     84   
                }
   85     85   
            }
   86     86   
        },
   87     87   
        {
   88     88   
            "connection_id": 1,
   89     89   
            "action": {
   90     90   
                "Request": {
   91     91   
                    "request": {
   92         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
          92  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/",
   93     93   
                        "headers": {
   94     94   
                            "x-amz-user-agent": [
   95     95   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
   96     96   
                            ],
   97     97   
                            "x-aws-ec2-metadata-token": [
   98     98   
                                "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw=="
   99     99   
                            ],
  100    100   
                            "user-agent": [
  101    101   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  102    102   
                            ]
@@ -152,152 +212,212 @@
  172    172   
                    "ok": true,
  173    173   
                    "direction": "Response"
  174    174   
                }
  175    175   
            }
  176    176   
        },
  177    177   
        {
  178    178   
            "connection_id": 2,
  179    179   
            "action": {
  180    180   
                "Request": {
  181    181   
                    "request": {
  182         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/imds-assume-role-test",
         182  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/imds-assume-role-test",
  183    183   
                        "headers": {
  184    184   
                            "x-aws-ec2-metadata-token": [
  185    185   
                                "AQAEAKQRRHnsX8GCPgYTGMShrFJkMhru3n-8Ul5Gzvzj-bpWKYZuiw=="
  186    186   
                            ],
  187    187   
                            "user-agent": [
  188    188   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
  189    189   
                            ],
  190    190   
                            "x-amz-user-agent": [
  191    191   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
  192    192   
                            ]

tmp-codegen-diff/aws-sdk/sdk/aws-config/test-data/default-credential-provider-chain/imds_no_iam_role/http-traffic.json

@@ -62,62 +122,122 @@
   82     82   
                    "ok": true,
   83     83   
                    "direction": "Response"
   84     84   
                }
   85     85   
            }
   86     86   
        },
   87     87   
        {
   88     88   
            "connection_id": 1,
   89     89   
            "action": {
   90     90   
                "Request": {
   91     91   
                    "request": {
   92         -
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
          92  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials-extended/",
   93     93   
                        "headers": {
   94     94   
                            "user-agent": [
   95     95   
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
   96     96   
                            ],
   97     97   
                            "x-amz-user-agent": [
   98     98   
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
   99     99   
                            ],
  100    100   
                            "x-aws-ec2-metadata-token": [
  101    101   
                               "faketoken"
  102    102   
                            ]
@@ -140,140 +174,258 @@
  160    160   
            }
  161    161   
        },
  162    162   
        {
  163    163   
            "connection_id": 1,
  164    164   
            "action": {
  165    165   
                "Eof": {
  166    166   
                    "ok": true,
  167    167   
                    "direction": "Response"
  168    168   
                }
  169    169   
            }
         170  +
        },
         171  +
        {
         172  +
            "connection_id": 2,
         173  +
            "action": {
         174  +
                "Request": {
         175  +
                    "request": {
         176  +
                        "uri": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
         177  +
                        "headers": {
         178  +
                            "user-agent": [
         179  +
                                "aws-sdk-rust/0.1.0 os/linux lang/rust/1.52.1"
         180  +
                            ],
         181  +
                            "x-amz-user-agent": [
         182  +
                                "aws-sdk-rust/0.1.0 api/imds/0.1.0 os/linux lang/rust/1.52.1"
         183  +
                            ],
         184  +
                            "x-aws-ec2-metadata-token": [
         185  +
                               "faketoken"
         186  +
                            ]
         187  +
                        },
         188  +
                        "method": "GET"
         189  +
                    }
         190  +
                }
         191  +
            }
         192  +
        },
         193  +
        {
         194  +
            "connection_id": 2,
         195  +
            "action": {
         196  +
                "Eof": {
         197  +
                    "ok": true,
         198  +
                    "direction": "Request"
         199  +
                }
         200  +
            }
         201  +
        },
         202  +
        {
         203  +
            "connection_id": 2,
         204  +
            "action": {
         205  +
                "Response": {
         206  +
                    "response": {
         207  +
                        "Ok": {
         208  +
                            "status": 404,
         209  +
                            "version": "HTTP/1.1",
         210  +
                            "headers": {
         211  +
                                "content-length": [
         212  +
                                    "339"
         213  +
                                ],
         214  +
                                "date": [
         215  +
                                    "Mon, 20 Sep 2021 20:51:52 GMT"
         216  +
                                ],
         217  +
                                "content-type": [
         218  +
                                    "text/html"
         219  +
                                ],
         220  +
                                "x-aws-ec2-metadata-token-ttl-seconds": [
         221  +
                                    "21600"
         222  +
                                ],
         223  +
                                "connection": [
         224  +
                                    "close"
         225  +
                                ],
         226  +
                                "server": [
         227  +
                                    "EC2ws"
         228  +
                                ]
         229  +
                            }
         230  +
                        }
         231  +
                    }
         232  +
                }
         233  +
            }
         234  +
        },
         235  +
        {
         236  +
            "connection_id": 2,
         237  +
            "action": {
         238  +
                "Data": {
         239  +
                    "data": {
         240  +
                        "Utf8": "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n\t\t \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n <head>\n  <title>404 - Not Found</title>\n </head>\n <body>\n  <h1>404 - Not Found</h1>\n </body>\n</html>\n"
         241  +
                    },
         242  +
                    "direction": "Response"
         243  +
                }
         244  +
            }
         245  +
        },
         246  +
        {
         247  +
            "connection_id": 2,
         248  +
            "action": {
         249  +
                "Eof": {
         250  +
                    "ok": true,
         251  +
                    "direction": "Response"
         252  +
                }
         253  +
            }
  170    254   
        }
  171    255   
    ],
  172    256   
    "docs": "live IMDS token retrieval",
  173    257   
    "version": "V0"
  174    258   
}