Skip to content

Commit 9bbb2f0

Browse files
committed
Periodically read workload identity token from file
Fixes #1739 similar to other languages by reading the file every 10 minutes pointed to by AZURE_FEDERATED_TOKEN_FILE.
1 parent a9f719d commit 9bbb2f0

File tree

1 file changed

+71
-17
lines changed

1 file changed

+71
-17
lines changed

sdk/identity/azure_identity/src/credentials/workload_identity_credentials.rs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
// Licensed under the MIT License.
33

44
use crate::{credentials::cache::TokenCache, federated_credentials_flow, TokenCredentialOptions};
5+
use async_lock::{RwLock, RwLockUpgradableReadGuard};
56
use azure_core::{
67
credentials::{AccessToken, Secret, TokenCredential},
78
error::{ErrorKind, ResultExt},
89
Error, HttpClient, Url,
910
};
10-
use std::{str, sync::Arc, time::Duration};
11+
use std::{
12+
str,
13+
sync::Arc,
14+
time::{Duration, Instant},
15+
};
1116
use time::OffsetDateTime;
1217

1318
const AZURE_TENANT_ID_ENV_KEY: &str = "AZURE_TENANT_ID";
@@ -19,14 +24,13 @@ const AZURE_FEDERATED_TOKEN: &str = "AZURE_FEDERATED_TOKEN";
1924
///
2025
/// More information on how to configure a client secret can be found here:
2126
/// <https://learn.microsoft.com/azure/active-directory/develop/quickstart-configure-app-access-web-apis#add-credentials-to-your-web-application>
22-
2327
#[derive(Debug)]
2428
pub struct WorkloadIdentityCredential {
2529
http_client: Arc<dyn HttpClient>,
2630
authority_host: Url,
2731
tenant_id: String,
2832
client_id: String,
29-
token: Secret,
33+
token: Token,
3034
cache: TokenCache,
3135
}
3236

@@ -47,7 +51,7 @@ impl WorkloadIdentityCredential {
4751
authority_host,
4852
tenant_id,
4953
client_id,
50-
token: token.into(),
54+
token: Token::Value(token.into()),
5155
cache: TokenCache::new(),
5256
}))
5357
}
@@ -100,22 +104,14 @@ impl WorkloadIdentityCredential {
100104
.var(AZURE_FEDERATED_TOKEN_FILE)
101105
.map_kind(ErrorKind::Credential)
102106
{
103-
let token = std::fs::read_to_string(token_file.clone()).with_context(
104-
ErrorKind::Credential,
105-
|| {
106-
format!(
107-
"failed to read federated token from file {}",
108-
token_file.as_str()
109-
)
110-
},
111-
)?;
112-
return WorkloadIdentityCredential::new(
107+
return Ok(Arc::new(Self {
113108
http_client,
114109
authority_host,
115110
tenant_id,
116111
client_id,
117-
token,
118-
);
112+
token: Token::with_file(token_file.as_ref())?,
113+
cache: TokenCache::new(),
114+
}));
119115
}
120116

121117
Err(Error::with_message(ErrorKind::Credential, || {
@@ -124,10 +120,11 @@ impl WorkloadIdentityCredential {
124120
}
125121

126122
async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
123+
let token = self.token.secret().await?;
127124
let res: AccessToken = federated_credentials_flow::authorize(
128125
self.http_client.clone(),
129126
&self.client_id,
130-
self.token.secret(),
127+
&token,
131128
scopes,
132129
&self.tenant_id,
133130
&self.authority_host,
@@ -155,3 +152,60 @@ impl TokenCredential for WorkloadIdentityCredential {
155152
self.cache.clear().await
156153
}
157154
}
155+
156+
#[derive(Debug)]
157+
enum Token {
158+
Value(Secret),
159+
File {
160+
path: String,
161+
cache: Arc<RwLock<FileCache>>,
162+
},
163+
}
164+
165+
#[derive(Debug)]
166+
struct FileCache {
167+
token: Secret,
168+
last_read: Instant,
169+
}
170+
171+
impl Token {
172+
fn with_file(path: &str) -> azure_core::Result<Self> {
173+
let last_read = Instant::now();
174+
let token = std::fs::read_to_string(path).with_context(ErrorKind::Credential, || {
175+
format!("failed to read federated token from file {}", path)
176+
})?;
177+
178+
Ok(Self::File {
179+
path: path.into(),
180+
cache: Arc::new(RwLock::new(FileCache {
181+
token: Secret::new(token),
182+
last_read,
183+
})),
184+
})
185+
}
186+
187+
async fn secret(&self) -> azure_core::Result<String> {
188+
match self {
189+
Self::Value(secret) => Ok(secret.secret().into()),
190+
Self::File { path, cache } => {
191+
const TIMEOUT: Duration = Duration::from_secs(600);
192+
193+
let now = Instant::now();
194+
let cache = cache.upgradable_read().await;
195+
if now - cache.last_read > TIMEOUT {
196+
let token = std::fs::read_to_string(path)
197+
.with_context(ErrorKind::Credential, || {
198+
format!("failed to read federated token from file {}", path)
199+
})?;
200+
let mut write_cache = RwLockUpgradableReadGuard::upgrade(cache).await;
201+
write_cache.token = Secret::new(token);
202+
write_cache.last_read = now;
203+
204+
return Ok(write_cache.token.secret().into());
205+
}
206+
207+
Ok(cache.token.secret().into())
208+
}
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)