Skip to content

Commit 894008b

Browse files
authored
Identity mi dac fixes (Azure#45101)
* code cleanup: remove some old MI tests which no longer are necessary and a couple constants. * helper for getting an HttpClient that will return a sequence of responses. * Throw CredentialUnavailableException for cases where IMDS returns invalid JSON. * spotless * fix test to expect new exception * use a logger instead of println * fix ordering * wip * wip * Remove an opportunistic change I was trying to make to move away from HttpURLConnection. There's too many details of how precisely to do this to work out for the moment. * fix error message * remove currently unused test helper method. * spotless
1 parent 6d48d49 commit 894008b

File tree

5 files changed

+88
-85
lines changed

5 files changed

+88
-85
lines changed

sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,9 @@ public final class ManagedIdentityCredential implements TokenCredential {
8787
final ManagedIdentityServiceCredential managedIdentityServiceCredential;
8888
private final IdentityClientOptions identityClientOptions;
8989
private final String managedIdentityId;
90-
static final String PROPERTY_IMDS_ENDPOINT = "IMDS_ENDPOINT";
9190
static final String PROPERTY_IDENTITY_SERVER_THUMBPRINT = "IDENTITY_SERVER_THUMBPRINT";
9291
static final String AZURE_FEDERATED_TOKEN_FILE = "AZURE_FEDERATED_TOKEN_FILE";
9392

94-
static final String USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI = "USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI";
95-
9693
/**
9794
* Creates an instance of the ManagedIdentityCredential with the client ID of a
9895
* user-assigned identity, or app registration (when working with AKS pod-identity).

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.microsoft.aad.msal4j.ManagedIdentityApplication;
3434
import com.microsoft.aad.msal4j.ManagedIdentitySourceType;
3535
import com.microsoft.aad.msal4j.MsalInteractionRequiredException;
36+
import com.microsoft.aad.msal4j.MsalJsonParsingException;
3637
import com.microsoft.aad.msal4j.PublicClientApplication;
3738
import com.microsoft.aad.msal4j.RefreshTokenParameters;
3839
import com.microsoft.aad.msal4j.SilentParameters;
@@ -553,7 +554,13 @@ private Mono<AccessToken> getTokenFromMsalMIClient(String resource) {
553554
throw new RuntimeException(e);
554555
}
555556
}))
556-
.onErrorMap(t -> new CredentialUnavailableException("Managed Identity authentication is not available.", t))
557+
.onErrorMap(t -> {
558+
if (options.isChained() && t instanceof MsalJsonParsingException) {
559+
return new CredentialUnavailableException("Managed Identity authentication is not available.", t);
560+
}
561+
return new ClientAuthenticationException(
562+
"Managed Identity authentication failed, see inner exception" + " for more information.", null, t);
563+
})
557564
.map(MsalToken::new);
558565
}
559566

sdk/identity/azure-identity/src/test/java/LiveManagedIdentityTests.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static org.junit.jupiter.api.Assertions.fail;
3030

3131
public class LiveManagedIdentityTests {
32+
private static final ClientLogger LOGGER = new ClientLogger(LiveManagedIdentityTests.class);
3233

3334
@Test
3435
@EnabledIfEnvironmentVariable(named = "AZURE_TEST_MODE", matches = "LIVE")
@@ -67,9 +68,9 @@ public void testManagedIdentityWebAppDeployment() {
6768
@Timeout(value = 15, unit = TimeUnit.MINUTES)
6869
@Retry(maxRetries = 3)
6970
public void testManagedIdentityAksDeployment() {
70-
System.out.println("Environment: " + System.getenv("IDENTITY_ENVIRONMENT"));
71+
LOGGER.log(LogLevel.INFORMATIONAL, () -> "Environment: " + System.getenv("IDENTITY_ENVIRONMENT"));
7172
String os = System.getProperty("os.name");
72-
System.out.println("OS: " + os);
73+
LOGGER.log(LogLevel.INFORMATIONAL, () -> "OS: " + os);
7374
//Setup Env
7475
Configuration configuration = Configuration.getGlobalConfiguration().clone();
7576

@@ -82,6 +83,7 @@ public void testManagedIdentityAksDeployment() {
8283
assertTrue(podOutput.contains(podName), "Pod name not found in the output");
8384

8485
String output = runCommand(kubectlPath, "exec", "-it", podName, "--", "java", "-jar", "/identity-test.jar");
86+
8587
assertTrue(output.contains("Successfully retrieved managed identity tokens"),
8688
"Failed to get response from AKS");
8789
}
@@ -93,9 +95,9 @@ public void testManagedIdentityAksDeployment() {
9395
@Timeout(value = 15, unit = TimeUnit.MINUTES)
9496
@Retry(maxRetries = 3)
9597
public void testManagedIdentityVmDeployment() {
96-
System.out.println("Environment: " + System.getenv("IDENTITY_ENVIRONMENT"));
98+
LOGGER.log(LogLevel.INFORMATIONAL, () -> "Environment: " + System.getenv("IDENTITY_ENVIRONMENT"));
9799
String os = System.getProperty("os.name");
98-
System.out.println("OS: " + os);
100+
LOGGER.log(LogLevel.INFORMATIONAL, () -> "OS: " + os);
99101
//Setup Env
100102
Configuration configuration = Configuration.getGlobalConfiguration().clone();
101103

@@ -128,7 +130,7 @@ public void testManagedIdentityVmDeployment() {
128130
storageAcccountName, sasToken);
129131
String script = String.format("curl \'%s\' -o ./testfile.jar && java -jar ./testfile.jar", vmBlob);
130132

131-
System.out.println("Script: " + script);
133+
LOGGER.log(LogLevel.INFORMATIONAL, () -> "Script: " + script);
132134

133135
String output = runCommand(azPath, "vm", "run-command", "invoke", "-n", vmName, "-g", resourceGroup,
134136
"--command-id", "RunShellScript", "--scripts", script);
@@ -163,7 +165,7 @@ private String runCommand(String... args) {
163165
for (String arg : args) {
164166
command.append(arg).append(" ");
165167
}
166-
System.out.println("Running command: " + command);
168+
LOGGER.log(LogLevel.INFORMATIONAL, () -> "Running command: " + command);
167169
ProcessBuilder processBuilder = new ProcessBuilder(args);
168170
processBuilder.redirectErrorStream(true);
169171
Process process = processBuilder.start();
@@ -176,7 +178,7 @@ private String runCommand(String... args) {
176178
output.append(line).append(System.lineSeparator());
177179
}
178180
}
179-
System.out.println("Output:" + System.lineSeparator() + output);
181+
LOGGER.log(LogLevel.INFORMATIONAL, () -> "Output:" + System.lineSeparator() + output);
180182
return output.toString();
181183
} catch (IOException | InterruptedException e) {
182184
e.printStackTrace();

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

Lines changed: 48 additions & 74 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,39 @@ 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,
50+
"{\"error\":\"invalid_request\",\"error_description\":\"Required metadata header not specified\"}"),
51+
getMockResponse(200, "invalid json"));
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
63+
= new IdentityClientOptions().setChained(isChained).setHttpClient(client).setConfiguration(configuration);
64+
ManagedIdentityCredential cred = new ManagedIdentityCredential("clientId", null, null, options);
65+
StepVerifier.create(cred.getToken(new TokenRequestContext().addScopes("https://management.azure.com")))
66+
.expectErrorMatches(t -> {
67+
if (isChained) {
68+
return t instanceof CredentialUnavailableException;
69+
} else {
70+
return t instanceof ClientAuthenticationException;
71+
}
72+
})
73+
.verify();
74+
3575
}
3676

3777
@Test
@@ -117,7 +157,7 @@ public void testCloudshellUserAssigned() {
117157
ManagedIdentityCredential credential
118158
= new ManagedIdentityCredentialBuilder().configuration(configuration).objectId(OBJECT_ID).build();
119159
StepVerifier.create(credential.getToken(request))
120-
.expectErrorMatches(t -> t instanceof CredentialUnavailableException)
160+
.expectErrorMatches(t -> t instanceof ClientAuthenticationException)
121161
.verify();
122162
}
123163

@@ -129,82 +169,22 @@ public void testInvalidIdCombination() {
129169
String objectId = "2323-sd2323s-32323-32334-34343";
130170

131171
// test
132-
Assertions.assertThrows(IllegalStateException.class,
172+
assertThrows(IllegalStateException.class,
133173
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).resourceId(resourceId).build());
134174

135-
Assertions.assertThrows(IllegalStateException.class,
175+
assertThrows(IllegalStateException.class,
136176
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID)
137177
.resourceId(resourceId)
138178
.objectId(objectId)
139179
.build());
140180

141-
Assertions.assertThrows(IllegalStateException.class,
181+
assertThrows(IllegalStateException.class,
142182
() -> new ManagedIdentityCredentialBuilder().clientId(CLIENT_ID).objectId(objectId).build());
143183

144-
Assertions.assertThrows(IllegalStateException.class,
184+
assertThrows(IllegalStateException.class,
145185
() -> new ManagedIdentityCredentialBuilder().resourceId(resourceId).objectId(objectId).build());
146186
}
147187

148-
@Test
149-
public void testArcIdentityCredentialCreated() {
150-
Configuration configuration
151-
= TestUtils
152-
.createTestConfiguration(new TestConfigurationSource().put("IDENTITY_ENDPOINT", "http://localhost")
153-
.put("IMDS_ENDPOINT", "http://localhost"))
154-
.put("USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI", "true");
155-
156-
ManagedIdentityCredential cred = new ManagedIdentityCredentialBuilder().configuration(configuration).build();
157-
assertThat("Received class " + cred.managedIdentityServiceCredential.getClass().toString(),
158-
cred.managedIdentityServiceCredential, instanceOf(ManagedIdentityMsalCredential.class));
159-
}
160-
161-
@Test
162-
public void testServiceFabricMsiCredentialCreated() {
163-
Configuration configuration = TestUtils
164-
.createTestConfiguration(new TestConfigurationSource().put("IDENTITY_ENDPOINT", "http://localhost")
165-
.put("IDENTITY_SERVER_THUMBPRINT", "thumbprint")
166-
.put("IDENTITY_HEADER", "header"))
167-
.put("USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI", "true");
168-
169-
ManagedIdentityCredential cred = new ManagedIdentityCredentialBuilder().configuration(configuration).build();
170-
assertThat("Received class " + cred.managedIdentityServiceCredential.getClass().toString(),
171-
cred.managedIdentityServiceCredential, instanceOf(ManagedIdentityMsalCredential.class));
172-
}
173-
174-
@Test
175-
public void testAppServiceMsi2019CredentialCreated() {
176-
Configuration configuration = TestUtils.createTestConfiguration(
177-
new TestConfigurationSource().put("IDENTITY_ENDPOINT", "http://localhost").put("IDENTITY_HEADER", "header"))
178-
.put("USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI", "true");
179-
180-
ManagedIdentityCredential cred = new ManagedIdentityCredentialBuilder().configuration(configuration).build();
181-
assertThat("Received class " + cred.managedIdentityServiceCredential.getClass().toString(),
182-
cred.managedIdentityServiceCredential, instanceOf(ManagedIdentityMsalCredential.class));
183-
}
184-
185-
@Test
186-
public void testAppServiceMsi2017CredentialCreated() {
187-
Configuration configuration = TestUtils
188-
.createTestConfiguration(
189-
new TestConfigurationSource().put("MSI_ENDPOINT", "http://localhost").put("MSI_SECRET", "secret"))
190-
.put("USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI", "true");
191-
192-
ManagedIdentityCredential cred = new ManagedIdentityCredentialBuilder().configuration(configuration).build();
193-
assertThat("Received class " + cred.managedIdentityServiceCredential.getClass().toString(),
194-
cred.managedIdentityServiceCredential, instanceOf(ManagedIdentityMsalCredential.class));
195-
}
196-
197-
@Test
198-
public void testCloudShellCredentialCreated() {
199-
Configuration configuration
200-
= TestUtils.createTestConfiguration(new TestConfigurationSource().put("MSI_ENDPOINT", "http://localhost"))
201-
.put("USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI", "true");
202-
203-
ManagedIdentityCredential cred = new ManagedIdentityCredentialBuilder().configuration(configuration).build();
204-
assertThat("Received class " + cred.managedIdentityServiceCredential.getClass().toString(),
205-
cred.managedIdentityServiceCredential, instanceOf(ManagedIdentityMsalCredential.class));
206-
}
207-
208188
@Test
209189
public void testAksExchangeTokenCredentialCreated() {
210190
Configuration configuration
@@ -217,13 +197,7 @@ public void testAksExchangeTokenCredentialCreated() {
217197
cred.managedIdentityServiceCredential, instanceOf(AksExchangeTokenCredential.class));
218198
}
219199

220-
@Test
221-
public void testIMDSPodIdentityV1Credential() {
222-
Configuration configuration = TestUtils.createTestConfiguration(new TestConfigurationSource());
223-
configuration.put("USE_AZURE_IDENTITY_CLIENT_LIBRARY_LEGACY_MI", "true");
224-
225-
ManagedIdentityCredential cred = new ManagedIdentityCredentialBuilder().configuration(configuration).build();
226-
assertThat("Received class " + cred.managedIdentityServiceCredential.getClass().toString(),
227-
cred.managedIdentityServiceCredential, instanceOf(ManagedIdentityMsalCredential.class));
200+
private MockHttpResponse getMockResponse(int code, String body) {
201+
return new MockHttpResponse(null, code, body.getBytes(StandardCharsets.UTF_8));
228202
}
229203
}

sdk/identity/azure-identity/src/test/java/com/azure/identity/util/TestUtils.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
package com.azure.identity.util;
55

66
import com.azure.core.credential.AccessToken;
7+
import com.azure.core.http.HttpClient;
8+
import com.azure.core.http.HttpRequest;
9+
import com.azure.core.http.HttpResponse;
710
import com.azure.core.util.Configuration;
811
import com.azure.core.util.ConfigurationBuilder;
912
import com.azure.core.util.ConfigurationSource;
@@ -147,6 +150,26 @@ public static Mono<AccessToken> getMockAccessToken(String accessToken, OffsetDat
147150
return Mono.just(new AccessToken(accessToken, expiresOn.plusMinutes(2).minus(tokenRefreshOffset)));
148151
}
149152

153+
/**
154+
* Creates a mock {@link HttpClient} which simply returns the provided responses in order.
155+
* @param responses The responses to return.
156+
* @param delay If specified, throw a {@code RuntimeException} after the specified delay.
157+
* @return A mock HttpClient that returns the provided responses.
158+
*/
159+
public static HttpClient getMockHttpClient(HttpResponse... responses) {
160+
return new HttpClient() {
161+
int index = 0;
162+
163+
@Override
164+
public Mono<HttpResponse> send(HttpRequest request) {
165+
if (index >= responses.length) {
166+
throw new IllegalStateException("No more responses available");
167+
}
168+
return Mono.just(responses[index++]);
169+
}
170+
};
171+
}
172+
150173
/**
151174
* Creates a {@link Configuration} with the specified {@link ConfigurationSource} as the only source of
152175
* configurations.

0 commit comments

Comments
 (0)