oci_rust_sdk/auth/
instance_principals.rs1use crate::auth::federation::{generate_session_keypair, request_security_token};
2use crate::auth::imds::ImdsClient;
3use crate::auth::provider::AuthProvider;
4use crate::auth::x509_utils::extract_tenant_id;
5use crate::core::region::Region;
6use chrono::{DateTime, Duration, Utc};
7use std::str::FromStr;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11struct SessionState {
13 security_token: String,
14 session_private_key_pem: String,
15 expires_at: DateTime<Utc>,
16}
17
18pub struct InstancePrincipalsAuthProvider {
40 runtime_handle: tokio::runtime::Handle,
41 region: Region,
42 tenancy_id: String,
43 leaf_certificate: String,
44 leaf_private_key_pem: String,
45 intermediate_certificates: Vec<String>,
46 session_state: Arc<RwLock<SessionState>>,
47}
48
49impl InstancePrincipalsAuthProvider {
50 pub async fn new() -> crate::core::Result<Self> {
66 let runtime_handle = tokio::runtime::Handle::current();
67
68 let imds = ImdsClient::new()?;
70
71 let region_str = imds.get_region().await?;
73 let region = Region::from_str(®ion_str).map_err(|e| {
74 crate::core::OciError::ConfigError(format!("Invalid region from IMDS: {}", e))
75 })?;
76
77 let leaf_cert = imds.get_leaf_certificate().await?;
78 let leaf_key = imds.get_leaf_private_key().await?;
79 let intermediate_certs = imds.get_intermediate_certificates().await?;
80
81 let tenancy_id = extract_tenant_id(&leaf_cert)?;
83
84 let session_keypair = generate_session_keypair()?;
86
87 let security_token = request_security_token(
89 ®ion,
90 &tenancy_id,
91 &leaf_cert,
92 &leaf_key,
93 &intermediate_certs,
94 &session_keypair.public_key_pem,
95 )
96 .await?;
97
98 let session_state = Arc::new(RwLock::new(SessionState {
100 security_token: security_token.token,
101 session_private_key_pem: session_keypair.private_key_pem,
102 expires_at: security_token.expires_at,
103 }));
104
105 Ok(Self {
106 runtime_handle,
107 region,
108 tenancy_id,
109 leaf_certificate: leaf_cert,
110 leaf_private_key_pem: leaf_key,
111 intermediate_certificates: intermediate_certs,
112 session_state,
113 })
114 }
115
116 pub fn region(&self) -> Region {
118 self.region
119 }
120
121 async fn ensure_token_valid(&self) -> crate::core::Result<()> {
123 {
125 let state = self.session_state.read().await;
126 if !Self::is_expired(&state.expires_at) {
127 return Ok(());
128 }
129 }
130
131 {
133 let mut state = self.session_state.write().await;
134
135 if !Self::is_expired(&state.expires_at) {
137 return Ok(());
138 }
139
140 let session_keypair = generate_session_keypair()?;
142
143 let security_token = request_security_token(
145 &self.region,
146 &self.tenancy_id,
147 &self.leaf_certificate,
148 &self.leaf_private_key_pem,
149 &self.intermediate_certificates,
150 &session_keypair.public_key_pem,
151 )
152 .await?;
153
154 state.security_token = security_token.token;
156 state.session_private_key_pem = session_keypair.private_key_pem;
157 state.expires_at = security_token.expires_at;
158 }
159
160 Ok(())
161 }
162
163 fn is_expired(expires_at: &DateTime<Utc>) -> bool {
165 let now = Utc::now();
166 let buffer = Duration::minutes(5);
167 now + buffer >= *expires_at
168 }
169}
170
171impl AuthProvider for InstancePrincipalsAuthProvider {
172 fn get_key_id(&self) -> String {
173 self.runtime_handle
175 .block_on(self.ensure_token_valid())
176 .unwrap_or_else(|e| {
177 eprintln!("Warning: Failed to refresh token: {}", e);
178 });
179
180 let state = self.session_state.blocking_read();
182 format!("ST${}", state.security_token)
183 }
184
185 fn get_private_key(&self) -> &str {
186 self.runtime_handle
188 .block_on(self.ensure_token_valid())
189 .unwrap_or_else(|e| {
190 eprintln!("Warning: Failed to refresh token: {}", e);
191 });
192
193 let pem = self
198 .session_state
199 .blocking_read()
200 .session_private_key_pem
201 .clone();
202 Box::leak(pem.into_boxed_str())
203 }
204
205 fn get_passphrase(&self) -> Option<&str> {
206 None
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use chrono::Utc;
215
216 #[test]
217 fn test_is_expired() {
218 let expires_at = Utc::now() + Duration::minutes(10);
220 assert!(!InstancePrincipalsAuthProvider::is_expired(&expires_at));
221
222 let expires_at = Utc::now() + Duration::minutes(4);
224 assert!(InstancePrincipalsAuthProvider::is_expired(&expires_at));
225
226 let expires_at = Utc::now() - Duration::minutes(10);
228 assert!(InstancePrincipalsAuthProvider::is_expired(&expires_at));
229 }
230
231 #[test]
232 fn test_key_id_format() {
233 let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.test.sig";
235 let key_id = format!("ST${}", token);
236 assert!(key_id.starts_with("ST$"));
237 assert!(key_id.contains("eyJ"));
238 }
239}