Skip to main content

resource_tracker/collector/clouds/
aws.rs

1use crate::metrics::CloudInfo;
2
3use super::{imds_get, imds_get_headers, new_imds_agent};
4
5fn fetch_imdsv2_token(agent: &ureq::Agent) -> Option<String> {
6    agent
7        .put("http://169.254.169.254/latest/api/token")
8        .header("X-aws-ec2-metadata-token-ttl-seconds", "21600")
9        .send_empty()
10        .ok()
11        .and_then(|mut r| {
12            if !r.status().is_success() {
13                return None;
14            }
15            r.body_mut().read_to_string().ok()
16        })
17        .map(|s| s.trim().to_string())
18        .filter(|s| !s.is_empty())
19}
20
21fn imds_get_with_token(agent: &ureq::Agent, path: &str, token: Option<&str>) -> Option<String> {
22    let url = format!("http://169.254.169.254{path}");
23    match token {
24        Some(t) => imds_get_headers(agent, &url, &[("X-aws-ec2-metadata-token", t)]),
25        None => imds_get(agent, &url),
26    }
27}
28
29const META_ROOT: &str = "http://169.254.169.254/latest/meta-data/";
30
31/// AWS IMDSv2 (token + header) with IMDSv1 fallback. Returns `None` if not on EC2.
32///
33/// When the IMDSv2 token `PUT` succeeds, the token is valid -- no extra validation `GET`
34/// to `META_ROOT` (avoids an unnecessary timeout on non-AWS hosts). If the token
35/// fetch fails, fall back to unauthenticated IMDSv1 `GET` on `META_ROOT`.
36///
37/// After reachability is confirmed the probe additionally verifies
38/// `/latest/meta-data/services/domain` equals `"amazonaws.com"` so that other
39/// cloud providers exposing an EC2-compatible metadata service are not mistaken
40/// for AWS.
41pub fn probe() -> Option<CloudInfo> {
42    let agent = new_imds_agent();
43    let token = fetch_imdsv2_token(&agent);
44
45    let read_token: Option<&str> = if token.is_some() {
46        token.as_deref()
47    } else if imds_get(&agent, META_ROOT).is_some() {
48        None
49    } else {
50        return None;
51    };
52
53    // Guard against EC2-compatible metadata services on other clouds.
54    let domain = imds_get_with_token(&agent, "/latest/meta-data/services/domain", read_token);
55    if domain.as_deref() != Some("amazonaws.com") {
56        return None;
57    }
58
59    let cloud_region_id =
60        imds_get_with_token(&agent, "/latest/meta-data/placement/region", read_token);
61    let cloud_zone_id = imds_get_with_token(
62        &agent,
63        "/latest/meta-data/placement/availability-zone",
64        read_token,
65    );
66    let cloud_instance_type =
67        imds_get_with_token(&agent, "/latest/meta-data/instance-type", read_token);
68    let cloud_account_id = imds_get_with_token(
69        &agent,
70        "/latest/meta-data/identity-credentials/ec2/info",
71        read_token,
72    )
73    .and_then(|body| {
74        let marker = "\"AccountId\":\"";
75        let start = body.find(marker)? + marker.len();
76        let end = body[start..].find('"')? + start;
77        Some(body[start..end].to_string())
78    });
79
80    Some(CloudInfo {
81        cloud_vendor_id: Some("aws".to_string()),
82        cloud_account_id,
83        cloud_region_id,
84        cloud_zone_id,
85        cloud_instance_type,
86    })
87}