AWS SDK

AWS SDK

rev. bff4f26934f9f187b452fcbd0e37fb97fc81397a (ignoring whitespace)

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

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   
}