aws_config/imds/
region.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! IMDS Region Provider
7//!
8//! Load region from IMDS from `/latest/meta-data/placement/region`
9//! This provider has a 5 second timeout.
10
11use crate::imds::{self, Client};
12use crate::meta::region::{future, ProvideRegion};
13use crate::provider_config::ProviderConfig;
14use aws_smithy_types::error::display::DisplayErrorContext;
15use aws_types::os_shim_internal::Env;
16use aws_types::region::Region;
17use std::fmt::Debug;
18use tracing::Instrument;
19
20/// IMDSv2 Region Provider
21///
22/// This provider is included in the default region chain, so it does not need to be used manually.
23pub struct ImdsRegionProvider {
24    client: Client,
25    env: Env,
26}
27
28impl Debug for ImdsRegionProvider {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("ImdsRegionProvider")
31            .field("client", &"IMDS client truncated for readability")
32            .field("env", &self.env)
33            .finish()
34    }
35}
36
37const REGION_PATH: &str = "/latest/meta-data/placement/region";
38
39impl ImdsRegionProvider {
40    /// Builder for [`ImdsRegionProvider`]
41    pub fn builder() -> Builder {
42        Builder::default()
43    }
44
45    fn imds_disabled(&self) -> bool {
46        match self.env.get(super::env::EC2_METADATA_DISABLED) {
47            Ok(value) => value.eq_ignore_ascii_case("true"),
48            _ => false,
49        }
50    }
51
52    /// Load a region from IMDS
53    ///
54    /// This provider uses the API `/latest/meta-data/placement/region`
55    pub async fn region(&self) -> Option<Region> {
56        if self.imds_disabled() {
57            tracing::debug!("not using IMDS to load region, IMDS is disabled");
58            return None;
59        }
60        match self.client.get(REGION_PATH).await {
61            Ok(region) => {
62                tracing::debug!(region = %region.as_ref(), "loaded region from IMDS");
63                Some(Region::new(String::from(region)))
64            }
65            Err(err) => {
66                tracing::warn!(err = %DisplayErrorContext(&err), "failed to load region from IMDS");
67                None
68            }
69        }
70    }
71}
72
73impl ProvideRegion for ImdsRegionProvider {
74    fn region(&self) -> future::ProvideRegion<'_> {
75        future::ProvideRegion::new(
76            self.region()
77                .instrument(tracing::debug_span!("imds_load_region")),
78        )
79    }
80}
81
82/// Builder for [`ImdsRegionProvider`]
83#[derive(Debug, Default)]
84pub struct Builder {
85    provider_config: Option<ProviderConfig>,
86    imds_client_override: Option<imds::Client>,
87}
88
89impl Builder {
90    /// Set configuration options of the [`Builder`]
91    pub fn configure(self, provider_config: &ProviderConfig) -> Self {
92        Self {
93            provider_config: Some(provider_config.clone()),
94            ..self
95        }
96    }
97
98    /// Override the IMDS client used to load the region
99    pub fn imds_client(mut self, imds_client: imds::Client) -> Self {
100        self.imds_client_override = Some(imds_client);
101        self
102    }
103
104    /// Create an [`ImdsRegionProvider`] from this builder
105    pub fn build(self) -> ImdsRegionProvider {
106        let provider_config = self.provider_config.unwrap_or_default();
107        let client = self
108            .imds_client_override
109            .unwrap_or_else(|| imds::Client::builder().configure(&provider_config).build());
110        ImdsRegionProvider {
111            client,
112            env: provider_config.env(),
113        }
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use crate::imds::client::test::{imds_request, imds_response, token_request, token_response};
120    use crate::imds::region::ImdsRegionProvider;
121    use crate::provider_config::ProviderConfig;
122    use aws_smithy_async::rt::sleep::TokioSleep;
123    use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient};
124    use aws_smithy_types::body::SdkBody;
125    use aws_types::region::Region;
126    use tracing_test::traced_test;
127
128    #[tokio::test]
129    async fn load_region() {
130        let http_client = StaticReplayClient::new(vec![
131            ReplayEvent::new(
132                token_request("http://169.254.169.254", 21600),
133                token_response(21600, "token"),
134            ),
135            ReplayEvent::new(
136                imds_request(
137                    "http://169.254.169.254/latest/meta-data/placement/region",
138                    "token",
139                ),
140                imds_response("eu-west-1"),
141            ),
142        ]);
143        let provider = ImdsRegionProvider::builder()
144            .configure(
145                &ProviderConfig::no_configuration()
146                    .with_http_client(http_client)
147                    .with_sleep_impl(TokioSleep::new()),
148            )
149            .build();
150        assert_eq!(
151            provider.region().await.expect("returns region"),
152            Region::new("eu-west-1")
153        );
154    }
155
156    #[traced_test]
157    #[tokio::test]
158    async fn no_region_imds_disabled() {
159        let http_client = StaticReplayClient::new(vec![ReplayEvent::new(
160            token_request("http://169.254.169.254", 21600),
161            http::Response::builder()
162                .status(403)
163                .body(SdkBody::empty())
164                .unwrap(),
165        )]);
166        let provider = ImdsRegionProvider::builder()
167            .configure(
168                &ProviderConfig::no_configuration()
169                    .with_http_client(http_client)
170                    .with_sleep_impl(TokioSleep::new()),
171            )
172            .build();
173        assert_eq!(provider.region().await, None);
174        assert!(logs_contain("failed to load region from IMDS"));
175        assert!(logs_contain("IMDS is disabled"));
176    }
177}