Skip to content

Commit eab1581

Browse files
committed
Throw CredentialUnavailableException for cases where IMDS returns invalid JSON.
1 parent 1e76928 commit eab1581

File tree

2 files changed

+70
-27
lines changed

2 files changed

+70
-27
lines changed

sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import com.azure.core.credential.AccessToken;
77
import com.azure.core.credential.TokenRequestContext;
88
import com.azure.core.exception.ClientAuthenticationException;
9+
import com.azure.core.http.HttpMethod;
910
import com.azure.core.http.HttpPipeline;
11+
import com.azure.core.http.HttpRequest;
1012
import com.azure.core.http.ProxyOptions;
1113
import com.azure.core.util.CoreUtils;
1214
import com.azure.core.util.SharedExecutorService;
@@ -33,6 +35,7 @@
3335
import com.microsoft.aad.msal4j.ManagedIdentityApplication;
3436
import com.microsoft.aad.msal4j.ManagedIdentitySourceType;
3537
import com.microsoft.aad.msal4j.MsalInteractionRequiredException;
38+
import com.microsoft.aad.msal4j.MsalJsonParsingException;
3639
import com.microsoft.aad.msal4j.PublicClientApplication;
3740
import com.microsoft.aad.msal4j.RefreshTokenParameters;
3841
import com.microsoft.aad.msal4j.SilentParameters;
@@ -43,7 +46,6 @@
4346
import reactor.core.publisher.Mono;
4447

4548
import java.io.IOException;
46-
import java.net.HttpURLConnection;
4749
import java.net.MalformedURLException;
4850
import java.net.Proxy;
4951
import java.net.Proxy.Type;
@@ -553,7 +555,12 @@ private Mono<AccessToken> getTokenFromMsalMIClient(String resource) {
553555
throw new RuntimeException(e);
554556
}
555557
}))
556-
.onErrorMap(t -> new CredentialUnavailableException("Managed Identity authentication is not available.", t))
558+
.onErrorMap(t -> {
559+
if (options.isChained() && t instanceof MsalJsonParsingException) {
560+
return new CredentialUnavailableException("Managed Identity authentication is not available.", t);
561+
}
562+
return new ClientAuthenticationException("Managed Identity authentication is not available.", null, t);
563+
})
557564
.map(MsalToken::new);
558565
}
559566

@@ -913,27 +920,18 @@ int getRetryTimeoutInMs(int retry) {
913920
}
914921

915922
private Mono<Boolean> checkIMDSAvailable(String endpoint) {
916-
return Mono.fromCallable(() -> {
917-
HttpURLConnection connection = null;
918-
URL url = getUrl(endpoint + "?api-version=2018-02-01");
919-
920-
try {
921-
connection = (HttpURLConnection) url.openConnection();
922-
connection.setRequestMethod("GET");
923-
connection.setConnectTimeout(1000);
924-
connection.connect();
925-
} catch (Exception e) {
926-
throw LoggingUtil.logCredentialUnavailableException(LOGGER, options,
927-
new CredentialUnavailableException("ManagedIdentityCredential authentication unavailable. "
928-
+ "Connection to IMDS endpoint cannot be established, " + e.getMessage() + ".", e));
929-
} finally {
930-
if (connection != null) {
931-
connection.disconnect();
932-
}
933-
}
923+
URL url = null;
924+
try {
925+
url = getUrl(endpoint + "?api-version=2018-02-01");
926+
} catch (MalformedURLException e) {
927+
return Mono.error(e);
928+
}
929+
HttpRequest request = new HttpRequest(HttpMethod.GET, url);
934930

935-
return true;
936-
});
931+
return getPipeline().send(request)
932+
.onErrorMap(t -> new CredentialUnavailableException("ManagedIdentityCredential authentication unavailable. "
933+
+ "Connection to IMDS endpoint cannot be established, " + t.getMessage() + ".", t))
934+
.flatMap(response -> Mono.just(true));
937935
}
938936

939937
private static Proxy proxyOptionsToJavaNetProxy(ProxyOptions options) {

sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,29 @@
55

66
import com.azure.core.credential.TokenRequestContext;
77
import com.azure.core.exception.ClientAuthenticationException;
8+
import com.azure.core.http.HttpClient;
9+
import com.azure.core.test.http.MockHttpResponse;
810
import com.azure.core.test.utils.TestConfigurationSource;
911
import com.azure.core.util.Configuration;
1012
import com.azure.identity.implementation.IdentityClient;
13+
import com.azure.identity.implementation.IdentityClientOptions;
1114
import com.azure.identity.util.TestUtils;
1215
import org.junit.jupiter.api.Assertions;
1316
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.params.ParameterizedTest;
18+
import org.junit.jupiter.params.provider.ValueSource;
1419
import org.mockito.MockedConstruction;
1520
import reactor.test.StepVerifier;
1621

22+
import java.nio.charset.StandardCharsets;
1723
import java.time.OffsetDateTime;
1824
import java.time.ZoneOffset;
1925
import java.util.UUID;
2026

2127
import static org.hamcrest.MatcherAssert.assertThat;
2228
import static org.hamcrest.core.IsInstanceOf.instanceOf;
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertThrows;
2331
import static org.mockito.Mockito.mockConstruction;
2432
import static org.mockito.Mockito.when;
2533

@@ -31,7 +39,40 @@ public class ManagedIdentityCredentialTest {
3139
@Test
3240
public void testVirtualMachineMSICredentialConfigurations() {
3341
ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder().clientId("foo").build();
34-
Assertions.assertEquals("foo", credential.getClientId());
42+
assertEquals("foo", credential.getClientId());
43+
}
44+
45+
@ParameterizedTest
46+
@ValueSource(booleans = {true, false})
47+
public void testInvalidJsonResponse(boolean isChained) {
48+
HttpClient client = TestUtils.getMockHttpClient(
49+
getMockResponse(400, "{\"error\":\"invalid_request\",\"error_description\":\"Required metadata header not specified\"}"),
50+
getMockResponse( 200, "invalid json")
51+
);
52+
53+
String endpoint = "http://localhost";
54+
String secret = "secret";
55+
56+
Configuration configuration
57+
= TestUtils.createTestConfiguration(new TestConfigurationSource().put("MSI_ENDPOINT", endpoint) // This must stay to signal we are in an app service context
58+
.put("MSI_SECRET", secret)
59+
.put("IDENTITY_ENDPOINT", endpoint)
60+
.put("IDENTITY_HEADER", secret));
61+
62+
IdentityClientOptions options = new IdentityClientOptions()
63+
.setChained(isChained)
64+
.setHttpClient(client)
65+
.setConfiguration(configuration);
66+
ManagedIdentityCredential cred = new ManagedIdentityCredential("clientId", null, null, options);
67+
StepVerifier.create(cred.getToken(new TokenRequestContext().addScopes("https://management.azure.com")))
68+
.expectErrorMatches(t -> {
69+
if (isChained) {
70+
return t instanceof CredentialUnavailableException;
71+
} else {
72+
return t instanceof ClientAuthenticationException;
73+
}
74+
}).verify();
75+
3576
}
3677

3778
@Test
@@ -129,19 +170,19 @@ public void testInvalidIdCombination() {
129170
String objectId = "2323-sd2323s-32323-32334-34343";
130171

131172
// test
132-
Assertions.assertThrows(IllegalStateException.class,
173+
assertThrows(IllegalStateException.class,
133174
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).resourceId(resourceId).build());
134175

135-
Assertions.assertThrows(IllegalStateException.class,
176+
assertThrows(IllegalStateException.class,
136177
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID)
137178
.resourceId(resourceId)
138179
.objectId(objectId)
139180
.build());
140181

141-
Assertions.assertThrows(IllegalStateException.class,
182+
assertThrows(IllegalStateException.class,
142183
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).objectId(objectId).build());
143184

144-
Assertions.assertThrows(IllegalStateException.class,
185+
assertThrows(IllegalStateException.class,
145186
() -> new ManagedIdentityCredentialBuilder().resourceId(resourceId).objectId(objectId).build());
146187
}
147188

@@ -156,4 +197,8 @@ public void testAksExchangeTokenCredentialCreated() {
156197
assertThat("Received class " + cred.managedIdentityServiceCredential.getClass().toString(),
157198
cred.managedIdentityServiceCredential, instanceOf(AksExchangeTokenCredential.class));
158199
}
200+
201+
private MockHttpResponse getMockResponse(int code, String body) {
202+
return new MockHttpResponse(null, code, body.getBytes(StandardCharsets.UTF_8));
203+
}
159204
}

0 commit comments

Comments
 (0)