Skip to content

Commit 2a84232

Browse files
bmc-msftdemoray
andauthored
add timeout to IMDS when used in DefaultAzureCredential (#1016)
* make checking IMDS for tokens happen last in DefaultAzureCredential The DefaultAzureCredential iterates through multiple methods for obtaining a credential until one succeeds (or all fail). Currently, the default is to check the Environment, IMDS, then the CLI. In many non-Azure workloads (such as MSFT corp computers), the IMDS token request takes an exceedingly long time to fail. This makes using DefaultAzureCredential without disabling IMDS on corp machines painfully slow. This PR changes the order to Environment, CLI, then IMDS, which is beneficial for developers using the examples. * address feedback * use azure-core and a minimal future rather than futures-time Rather than expanding our dependencies, this uses azure-core's sleep to provide the future used for timing out and lifts the future from futures-time. Co-authored-by: Brian Caswell <[email protected]>
1 parent 7afd134 commit 2a84232

File tree

4 files changed

+95
-16
lines changed

4 files changed

+95
-16
lines changed

sdk/identity/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ base64 = "0.13.0"
2828
uuid = { version = "1.0", features = ["v4"] }
2929
# work around https://github.com/rust-lang/rust/issues/63033
3030
fix-hidden-lifetime-bug = "0.2"
31+
pin-project = "1.0"
3132

3233
[dev-dependencies]
3334
reqwest = { version = "0.11", features = ["json"], default-features = false }

sdk/identity/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub mod development;
4949
pub mod device_code_flow;
5050
mod oauth2_http_client;
5151
pub mod refresh_token;
52+
mod timeout;
5253
mod token_credentials;
5354

5455
pub use crate::token_credentials::*;

sdk/identity/src/timeout.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) 2020 Yoshua Wuyts
2+
//
3+
// based on https://crates.io/crates/futures-time
4+
// Licensed under either of Apache License, Version 2.0 or MIT license at your option.
5+
6+
use azure_core::sleep::{sleep, Sleep};
7+
use futures::Future;
8+
use std::time::Duration;
9+
use std::{
10+
pin::Pin,
11+
task::{Context, Poll},
12+
};
13+
14+
#[pin_project::pin_project]
15+
#[derive(Debug)]
16+
pub(crate) struct Timeout<F, D> {
17+
#[pin]
18+
future: F,
19+
#[pin]
20+
deadline: D,
21+
completed: bool,
22+
}
23+
24+
impl<F, D> Timeout<F, D> {
25+
pub(crate) fn new(future: F, deadline: D) -> Self {
26+
Self {
27+
future,
28+
deadline,
29+
completed: false,
30+
}
31+
}
32+
}
33+
34+
impl<F: Future, D: Future> Future for Timeout<F, D> {
35+
type Output = azure_core::Result<F::Output>;
36+
37+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
38+
let this = self.project();
39+
40+
assert!(!*this.completed, "future polled after completing");
41+
42+
match this.future.poll(cx) {
43+
Poll::Ready(v) => {
44+
*this.completed = true;
45+
Poll::Ready(Ok(v))
46+
}
47+
Poll::Pending => match this.deadline.poll(cx) {
48+
Poll::Ready(_) => {
49+
*this.completed = true;
50+
Poll::Ready(Err(azure_core::error::Error::with_message(
51+
azure_core::error::ErrorKind::Other,
52+
|| String::from("operation timed out"),
53+
)))
54+
}
55+
Poll::Pending => Poll::Pending,
56+
},
57+
}
58+
}
59+
}
60+
61+
pub(crate) trait TimeoutExt: Future {
62+
fn timeout(self, duration: Duration) -> Timeout<Self, Sleep>
63+
where
64+
Self: Sized,
65+
{
66+
Timeout::new(self, sleep(duration))
67+
}
68+
}
69+
70+
impl<T> TimeoutExt for T where T: Future {}

sdk/identity/src/token_credentials/default_credentials.rs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
use super::{AzureCliCredential, ImdsManagedIdentityCredential};
2-
use azure_core::auth::{TokenCredential, TokenResponse};
3-
use azure_core::error::{Error, ErrorKind, ResultExt};
1+
use crate::{
2+
timeout::TimeoutExt,
3+
{AzureCliCredential, ImdsManagedIdentityCredential},
4+
};
5+
use azure_core::{
6+
auth::{TokenCredential, TokenResponse},
7+
error::{Error, ErrorKind, ResultExt},
8+
};
9+
use std::time::Duration;
410

511
#[derive(Debug)]
612
/// Provides a mechanism of selectively disabling credentials used for a `DefaultAzureCredential` instance
@@ -91,10 +97,19 @@ impl TokenCredential for DefaultAzureCredentialEnum {
9197
)
9298
}
9399
DefaultAzureCredentialEnum::ManagedIdentity(credential) => {
94-
credential.get_token(resource).await.context(
95-
ErrorKind::Credential,
96-
"error getting managed identity credential",
97-
)
100+
// IMSD timeout is only limited to 1 second when used in DefaultAzureCredential
101+
credential
102+
.get_token(resource)
103+
.timeout(Duration::from_secs(1))
104+
.await
105+
.context(
106+
ErrorKind::Credential,
107+
"getting managed identity credential timed out",
108+
)?
109+
.context(
110+
ErrorKind::Credential,
111+
"error getting managed identity credential",
112+
)
98113
}
99114
DefaultAzureCredentialEnum::AzureCli(credential) => {
100115
credential.get_token(resource).await.context(
@@ -128,15 +143,7 @@ impl DefaultAzureCredential {
128143

129144
impl Default for DefaultAzureCredential {
130145
fn default() -> Self {
131-
DefaultAzureCredential {
132-
sources: vec![
133-
DefaultAzureCredentialEnum::Environment(super::EnvironmentCredential::default()),
134-
DefaultAzureCredentialEnum::ManagedIdentity(
135-
ImdsManagedIdentityCredential::default(),
136-
),
137-
DefaultAzureCredentialEnum::AzureCli(AzureCliCredential::new()),
138-
],
139-
}
146+
DefaultAzureCredentialBuilder::new().build()
140147
}
141148
}
142149

0 commit comments

Comments
 (0)