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 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}