Skip to main content

secrets_core/
probe.rs

1#![allow(dead_code)]
2
3#[cfg(feature = "k8s")]
4const SERVICE_ACCOUNT_DIR: &str = "/var/run/secrets/kubernetes.io/serviceaccount";
5
6#[cfg(feature = "k8s")]
7fn service_account_exists() -> bool {
8    #[cfg(test)]
9    {
10        if let Some(override_path) = SERVICE_ACCOUNT_OVERRIDE
11            .lock()
12            .expect("service account override mutex poisoned")
13            .clone()
14        {
15            return override_path.exists();
16        }
17    }
18
19    std::path::Path::new(SERVICE_ACCOUNT_DIR).exists()
20}
21
22#[cfg(feature = "k8s")]
23#[cfg(test)]
24static SERVICE_ACCOUNT_OVERRIDE: once_cell::sync::Lazy<
25    std::sync::Mutex<Option<std::path::PathBuf>>,
26> = once_cell::sync::Lazy::new(|| std::sync::Mutex::new(None));
27
28#[cfg(feature = "k8s")]
29pub async fn is_kubernetes() -> bool {
30    if std::env::var_os("KUBERNETES_SERVICE_HOST").is_some() {
31        return service_account_exists();
32    }
33    false
34}
35
36#[cfg(not(feature = "k8s"))]
37pub async fn is_kubernetes() -> bool {
38    false
39}
40
41#[cfg(feature = "aws")]
42pub async fn is_aws() -> bool {
43    super::imds::head(
44        "http://169.254.169.254/latest/meta-data/instance-id",
45        &[],
46        probe_timeout(),
47    )
48    .await
49}
50
51#[cfg(not(feature = "aws"))]
52pub async fn is_aws() -> bool {
53    false
54}
55
56#[cfg(feature = "gcp")]
57pub async fn is_gcp() -> bool {
58    super::imds::head(
59        "http://169.254.169.254",
60        &[("Metadata-Flavor", "Google")],
61        probe_timeout(),
62    )
63    .await
64}
65
66#[cfg(not(feature = "gcp"))]
67pub async fn is_gcp() -> bool {
68    false
69}
70
71#[cfg(feature = "azure")]
72pub async fn is_azure() -> bool {
73    super::imds::head(
74        "http://169.254.169.254/metadata/instance",
75        &[("Metadata", "true")],
76        probe_timeout(),
77    )
78    .await
79}
80
81#[cfg(not(feature = "azure"))]
82pub async fn is_azure() -> bool {
83    false
84}
85
86#[cfg(all(test, feature = "k8s"))]
87mod tests {
88    use super::*;
89    use std::path::PathBuf;
90    use tempfile::tempdir;
91
92    fn set_service_account_override(path: Option<PathBuf>) {
93        let mut guard = SERVICE_ACCOUNT_OVERRIDE
94            .lock()
95            .expect("service account override mutex poisoned");
96        *guard = path;
97    }
98
99    #[tokio::test]
100    async fn detects_kubernetes_when_env_and_service_account_present() {
101        let tmp = tempdir().expect("temp dir");
102        let sa_dir = tmp.path().join("serviceaccount");
103        std::fs::create_dir_all(&sa_dir).expect("create service account dir");
104
105        // SAFETY: test overrides the probe environment and restores it afterwards.
106        unsafe {
107            std::env::set_var("KUBERNETES_SERVICE_HOST", "10.0.0.1");
108        }
109        set_service_account_override(Some(sa_dir));
110        assert!(is_kubernetes().await);
111
112        set_service_account_override(None);
113        unsafe {
114            std::env::remove_var("KUBERNETES_SERVICE_HOST");
115        }
116    }
117}
118
119#[cfg(all(test, feature = "aws"))]
120mod aws_tests {
121    use super::*;
122
123    #[tokio::test]
124    async fn aws_probe_fails_closed_without_metadata() {
125        let detected = is_aws().await;
126        assert!(
127            !detected,
128            "aws probe should fail closed when metadata unavailable"
129        );
130    }
131}
132
133#[cfg(all(test, feature = "gcp"))]
134mod gcp_tests {
135    use super::*;
136
137    #[tokio::test]
138    async fn gcp_probe_fails_closed_without_metadata() {
139        assert!(!is_gcp().await);
140    }
141}
142
143#[cfg(all(test, feature = "azure"))]
144mod azure_tests {
145    use super::*;
146
147    #[tokio::test]
148    async fn azure_probe_fails_closed_without_metadata() {
149        assert!(!is_azure().await);
150    }
151}
152
153fn probe_timeout() -> std::time::Duration {
154    let ms = std::env::var("GREENTIC_SECRETS_PROBE_TIMEOUT_MS")
155        .ok()
156        .and_then(|value| value.parse::<u64>().ok())
157        .unwrap_or(50);
158    std::time::Duration::from_millis(ms)
159}