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