Skip to content

Commit cc7f1c6

Browse files
Add azure arc managed identity (#730)
* Add azure arc managed identity * Removed lenient from mock and merge conflicts * Clear cache in unit tests * Fix after manual testing * Update the log message * Service Fabric MSI (#729) * Support for service fabric, most tests working * TODOs and sonarlint recommendations * Address PR comments --------- Co-authored-by: Avery-Dunn <[email protected]> Co-authored-by: Avery-Dunn <[email protected]>
1 parent 1f7fa9d commit cc7f1c6

13 files changed

+473
-45
lines changed

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractManagedIdentitySource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ abstract class AbstractManagedIdentitySource {
2121

2222
protected final ManagedIdentityRequest managedIdentityRequest;
2323
protected final ServiceBundle serviceBundle;
24-
private ManagedIdentitySourceType managedIdentitySourceType;
24+
ManagedIdentitySourceType managedIdentitySourceType;
2525

2626
@Getter
2727
@Setter

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByManagedIdentitySupplier.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ AuthenticationResult execute() throws Exception {
4343
scopes.add(this.managedIdentityParameters.resource);
4444
SilentParameters parameters = SilentParameters
4545
.builder(scopes)
46+
.tenant(managedIdentityParameters.tenant())
4647
.build();
4748

4849
RequestContext context = new RequestContext(

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AppServiceManagedIdentitySource.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ class AppServiceManagedIdentitySource extends AbstractManagedIdentitySource{
1919
private static final String APP_SERVICE_MSI_API_VERSION = "2019-08-01";
2020
private static final String SECRET_HEADER_NAME = "X-IDENTITY-HEADER";
2121

22-
private final URI MSI_ENDPOINT;
23-
private final String SECRET;
22+
private final URI msiEndpoint;
23+
private final String identityHeader;
2424

2525
@Override
2626
public void createManagedIdentityRequest(String resource) {
27-
managedIdentityRequest.baseEndpoint = MSI_ENDPOINT;
27+
managedIdentityRequest.baseEndpoint = msiEndpoint;
2828
managedIdentityRequest.method = HttpMethod.GET;
2929

3030
managedIdentityRequest.headers = new HashMap<>();
31-
managedIdentityRequest.headers.put(SECRET_HEADER_NAME, SECRET);
31+
managedIdentityRequest.headers.put(SECRET_HEADER_NAME, identityHeader);
3232

3333
managedIdentityRequest.queryParameters = new HashMap<>();
3434
managedIdentityRequest.queryParameters.put("api-version", Collections.singletonList(APP_SERVICE_MSI_API_VERSION));
@@ -50,8 +50,8 @@ public void createManagedIdentityRequest(String resource) {
5050
private AppServiceManagedIdentitySource(MsalRequest msalRequest, ServiceBundle serviceBundle, URI msiEndpoint, String secret)
5151
{
5252
super(msalRequest, serviceBundle, ManagedIdentitySourceType.APP_SERVICE);
53-
this.MSI_ENDPOINT = msiEndpoint;
54-
this.SECRET = secret;
53+
this.msiEndpoint = msiEndpoint;
54+
this.identityHeader = secret;
5555
}
5656

5757
static AbstractManagedIdentitySource create(MsalRequest msalRequest, ServiceBundle serviceBundle) {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.aad.msal4j;
5+
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
import java.io.FileReader;
10+
import java.io.IOException;
11+
import java.net.HttpURLConnection;
12+
import java.net.URI;
13+
import java.net.URISyntaxException;
14+
import java.nio.charset.StandardCharsets;
15+
import java.nio.file.Files;
16+
import java.nio.file.Path;
17+
import java.nio.file.Paths;
18+
import java.util.Collections;
19+
import java.util.HashMap;
20+
21+
class AzureArcManagedIdentitySource extends AbstractManagedIdentitySource{
22+
23+
private final static Logger LOG = LoggerFactory.getLogger(AzureArcManagedIdentitySource.class);
24+
private static final String ARC_API_VERSION = "2019-11-01";
25+
private static final String AZURE_ARC = "Azure Arc";
26+
27+
private final URI MSI_ENDPOINT;
28+
29+
static AbstractManagedIdentitySource create(MsalRequest msalRequest, ServiceBundle serviceBundle)
30+
{
31+
IEnvironmentVariables environmentVariables = getEnvironmentVariables((ManagedIdentityParameters) msalRequest.requestContext().apiParameters());
32+
String identityEndpoint = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_ENDPOINT);
33+
String imdsEndpoint = environmentVariables.getEnvironmentVariable(Constants.IMDS_ENDPOINT);
34+
35+
URI validatedUri = validateAndGetUri(identityEndpoint, imdsEndpoint);
36+
return validatedUri == null ? null : new AzureArcManagedIdentitySource(validatedUri, msalRequest, serviceBundle );
37+
}
38+
39+
private static URI validateAndGetUri(String identityEndpoint, String imdsEndpoint) {
40+
41+
// if BOTH the env vars IDENTITY_ENDPOINT and IMDS_ENDPOINT are set the MsiType is Azure Arc
42+
if (StringHelper.isNullOrBlank(identityEndpoint) || StringHelper.isNullOrBlank(imdsEndpoint))
43+
{
44+
LOG.info("[Managed Identity] Azure Arc managed identity is unavailable.");
45+
return null;
46+
}
47+
48+
URI endpointUri;
49+
try {
50+
endpointUri = new URI(identityEndpoint);
51+
} catch (URISyntaxException e) {
52+
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT, String.format(
53+
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR, "IDENTITY_ENDPOINT", identityEndpoint, AZURE_ARC),
54+
ManagedIdentitySourceType.AZURE_ARC);
55+
}
56+
57+
LOG.info("[Managed Identity] Creating Azure Arc managed identity. Endpoint URI: " + endpointUri);
58+
return endpointUri;
59+
}
60+
61+
private AzureArcManagedIdentitySource(URI endpoint, MsalRequest msalRequest, ServiceBundle serviceBundle){
62+
super(msalRequest, serviceBundle, ManagedIdentitySourceType.AZURE_ARC);
63+
this.MSI_ENDPOINT = endpoint;
64+
65+
ManagedIdentityIdType idType =
66+
((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getIdType();
67+
if (idType != ManagedIdentityIdType.SYSTEM_ASSIGNED) {
68+
throw new MsalManagedIdentityException(MsalError.USER_ASSIGNED_MANAGED_IDENTITY_NOT_SUPPORTED,
69+
String.format(MsalErrorMessage.MANAGED_IDENTITY_USER_ASSIGNED_NOT_SUPPORTED, AZURE_ARC),
70+
ManagedIdentitySourceType.AZURE_ARC);
71+
}
72+
}
73+
74+
@Override
75+
public void createManagedIdentityRequest(String resource)
76+
{
77+
managedIdentityRequest.baseEndpoint = MSI_ENDPOINT;
78+
managedIdentityRequest.method = HttpMethod.GET;
79+
80+
managedIdentityRequest.headers = new HashMap<>();
81+
managedIdentityRequest.headers.put("Metadata", "true");
82+
83+
managedIdentityRequest.queryParameters = new HashMap<>();
84+
managedIdentityRequest.queryParameters.put("api-version", Collections.singletonList(ARC_API_VERSION));
85+
managedIdentityRequest.queryParameters.put("resource", Collections.singletonList(resource));
86+
}
87+
88+
@Override
89+
public ManagedIdentityResponse handleResponse(
90+
ManagedIdentityParameters parameters,
91+
IHttpResponse response) {
92+
93+
LOG.info("[Managed Identity] Response received. Status code: {response.StatusCode}");
94+
95+
if (response.statusCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
96+
if(!response.headers().containsKey("Www-Authenticate")) {
97+
LOG.error("[Managed Identity] WWW-Authenticate header is expected but not found.");
98+
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_REQUEST_FAILED,
99+
MsalErrorMessage.MANAGED_IDENTITY_NO_CHALLENGE_ERROR,
100+
ManagedIdentitySourceType.AZURE_ARC);
101+
}
102+
103+
String challenge = response.headers().get("Www-Authenticate").get(0);
104+
String[] splitChallenge = challenge.split("=");
105+
106+
if (splitChallenge.length != 2) {
107+
LOG.error("[Managed Identity] The WWW-Authenticate header for Azure arc managed identity is not an expected format.");
108+
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_REQUEST_FAILED,
109+
MsalErrorMessage.MANAGED_IDENTITY_INVALID_CHALLENGE,
110+
ManagedIdentitySourceType.AZURE_ARC);
111+
}
112+
113+
Path path = Paths.get(splitChallenge[1]);
114+
115+
String authHeaderValue = null;
116+
try {
117+
authHeaderValue = "Basic " + new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
118+
} catch (IOException e) {
119+
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_FILE_READ_ERROR, e.getMessage(), ManagedIdentitySourceType.AZURE_ARC);
120+
}
121+
122+
createManagedIdentityRequest(parameters.resource);
123+
124+
LOG.info("[Managed Identity] Adding authorization header to the request.");
125+
126+
managedIdentityRequest.headers.put("Authorization", authHeaderValue);
127+
128+
try {
129+
response = HttpHelper.executeHttpRequest(
130+
new HttpRequest(HttpMethod.GET, managedIdentityRequest.computeURI().toString(),
131+
managedIdentityRequest.headers),
132+
managedIdentityRequest.requestContext(),
133+
serviceBundle);
134+
} catch (URISyntaxException e) {
135+
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT,
136+
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR,
137+
managedIdentitySourceType);
138+
}
139+
140+
return super.handleResponse(parameters, response);
141+
}
142+
143+
return super.handleResponse(parameters, response);
144+
}
145+
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/CloudShellManagedIdentitySource.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ class CloudShellManagedIdentitySource extends AbstractManagedIdentitySource{
1515

1616
private static final Logger LOG = LoggerFactory.getLogger(CloudShellManagedIdentitySource.class);
1717

18-
private final URI MSI_ENDPOINT;
18+
private final URI msiEndpoint;
1919

2020
@Override
2121
public void createManagedIdentityRequest(String resource) {
22-
managedIdentityRequest.baseEndpoint = MSI_ENDPOINT;
22+
managedIdentityRequest.baseEndpoint = msiEndpoint;
2323
managedIdentityRequest.method = HttpMethod.POST;
2424

2525
managedIdentityRequest.headers = new HashMap<>();
@@ -33,7 +33,7 @@ public void createManagedIdentityRequest(String resource) {
3333
private CloudShellManagedIdentitySource(MsalRequest msalRequest, ServiceBundle serviceBundle, URI msiEndpoint)
3434
{
3535
super(msalRequest, serviceBundle, ManagedIdentitySourceType.CLOUD_SHELL);
36-
this.MSI_ENDPOINT = msiEndpoint;
36+
this.msiEndpoint = msiEndpoint;
3737

3838
ManagedIdentityIdType idType =
3939
((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getIdType();
@@ -57,28 +57,23 @@ static AbstractManagedIdentitySource create(MsalRequest msalRequest, ServiceBund
5757
return null;
5858
}
5959

60-
URI validatedUri = validateAndGetUri(msiEndpoint);
61-
return validatedUri == null ? null
62-
: new CloudShellManagedIdentitySource(msalRequest, serviceBundle, validatedUri);
60+
return new CloudShellManagedIdentitySource(msalRequest, serviceBundle, validateAndGetUri(msiEndpoint));
6361
}
6462

6563
private static URI validateAndGetUri(String msiEndpoint)
6664
{
67-
URI endpointUri = null;
68-
6965
try
7066
{
71-
endpointUri = new URI(msiEndpoint);
67+
URI endpointUri = new URI(msiEndpoint);
68+
LOG.info("[Managed Identity] Environment variables validation passed for cloud shell managed identity. Endpoint URI: " + endpointUri + ". Creating cloud shell managed identity.");
69+
return endpointUri;
7270
}
7371
catch (URISyntaxException ex)
7472
{
7573
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT, String.format(
7674
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR, "MSI_ENDPOINT", msiEndpoint, "Cloud Shell"),
7775
ManagedIdentitySourceType.CLOUD_SHELL);
7876
}
79-
80-
LOG.info("[Managed Identity] Environment variables validation passed for cloud shell managed identity. Endpoint URI: " + endpointUri + ". Creating cloud shell managed identity.");
81-
return endpointUri;
8277
}
8378

8479
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IMDSManagedIdentitySource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public ManagedIdentityResponse handleResponse(
120120

121121
message = message + " " + errorContentMessage;
122122

123-
LOG.error(String.format("Error message: %s Http status code: %s"), message, response.statusCode());
123+
LOG.error(String.format("Error message: %s Http status code: %s", message, response.statusCode()));
124124
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_REQUEST_FAILED, message,
125125
ManagedIdentitySourceType.IMDS);
126126
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ManagedIdentityClient.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ ManagedIdentityResponse getManagedIdentityResponse(ManagedIdentityParameters par
3434
private static AbstractManagedIdentitySource createManagedIdentitySource(MsalRequest msalRequest,
3535
ServiceBundle serviceBundle) {
3636
AbstractManagedIdentitySource managedIdentitySource;
37-
if ((managedIdentitySource = AppServiceManagedIdentitySource.create(msalRequest, serviceBundle)) != null) {
37+
if ((managedIdentitySource = ServiceFabricManagedIdentitySource.create(msalRequest, serviceBundle)) != null) {
38+
return managedIdentitySource;
39+
} else if ((managedIdentitySource = AppServiceManagedIdentitySource.create(msalRequest, serviceBundle)) != null) {
3840
return managedIdentitySource;
3941
} else if ((managedIdentitySource = CloudShellManagedIdentitySource.create(msalRequest, serviceBundle)) != null) {
4042
return managedIdentitySource;
43+
} else if ((managedIdentitySource = AzureArcManagedIdentitySource.create(msalRequest, serviceBundle)) != null) {
44+
return managedIdentitySource;
4145
} else {
4246
return new IMDSManagedIdentitySource(msalRequest, serviceBundle);
4347
}

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ManagedIdentityParameters.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
@AllArgsConstructor(access = AccessLevel.PRIVATE)
2323
public class ManagedIdentityParameters implements IAcquireTokenParameters {
2424

25+
@Getter
2526
String resource;
2627

2728
boolean forceRefresh;

msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/MsalError.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ public class MsalError {
3232
* Managed Identity endpoint is not reachable.
3333
*/
3434
public static final String MANAGED_IDENTITY_UNREACHABLE_NETWORK = "managed_identity_unreachable_network";
35+
36+
public static final String MANAGED_IDENTITY_FILE_READ_ERROR = "managed_identity_file_read_error";
3537
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.aad.msal4j;
5+
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
import java.net.URI;
10+
import java.net.URISyntaxException;
11+
import java.util.Collections;
12+
import java.util.HashMap;
13+
14+
class ServiceFabricManagedIdentitySource extends AbstractManagedIdentitySource {
15+
16+
private static final Logger LOG = LoggerFactory.getLogger(ServiceFabricManagedIdentitySource.class);
17+
18+
private static final String SERVICE_FABRIC_MSI_API_VERSION = "2019-07-01-preview";
19+
20+
private final URI msiEndpoint;
21+
private final String identityHeader;
22+
private final ManagedIdentityIdType idType;
23+
private final String userAssignedId;
24+
25+
@Override
26+
public void createManagedIdentityRequest(String resource) {
27+
managedIdentityRequest.baseEndpoint = msiEndpoint;
28+
managedIdentityRequest.method = HttpMethod.GET;
29+
30+
managedIdentityRequest.headers = new HashMap<>();
31+
managedIdentityRequest.headers.put("secret", identityHeader);
32+
33+
managedIdentityRequest.queryParameters = new HashMap<>();
34+
managedIdentityRequest.queryParameters.put("resource", Collections.singletonList(resource));
35+
managedIdentityRequest.queryParameters.put("api-version", Collections.singletonList(SERVICE_FABRIC_MSI_API_VERSION));
36+
37+
if (idType == ManagedIdentityIdType.CLIENT_ID) {
38+
LOG.info("[Managed Identity] Adding user assigned client id to the request for Service Fabric Managed Identity.");
39+
managedIdentityRequest.queryParameters.put(Constants.MANAGED_IDENTITY_CLIENT_ID, Collections.singletonList(userAssignedId));
40+
} else if (idType == ManagedIdentityIdType.RESOURCE_ID) {
41+
LOG.info("[Managed Identity] Adding user assigned resource id to the request for Service Fabric Managed Identity.");
42+
managedIdentityRequest.queryParameters.put(Constants.MANAGED_IDENTITY_RESOURCE_ID, Collections.singletonList(userAssignedId));
43+
}
44+
}
45+
46+
private ServiceFabricManagedIdentitySource(MsalRequest msalRequest, ServiceBundle serviceBundle, URI msiEndpoint, String identityHeader)
47+
{
48+
super(msalRequest, serviceBundle, ManagedIdentitySourceType.SERVICE_FABRIC);
49+
this.msiEndpoint = msiEndpoint;
50+
this.identityHeader = identityHeader;
51+
52+
this.idType = ((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getIdType();
53+
this.userAssignedId = ((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getUserAssignedId();
54+
}
55+
56+
static AbstractManagedIdentitySource create(MsalRequest msalRequest, ServiceBundle serviceBundle) {
57+
58+
IEnvironmentVariables environmentVariables = getEnvironmentVariables((ManagedIdentityParameters) msalRequest.requestContext().apiParameters());
59+
String msiEndpoint = environmentVariables.getEnvironmentVariable(Constants.MSI_ENDPOINT);
60+
String identityHeader = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_ENDPOINT);
61+
String identityServerThumbprint = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_SERVER_THUMBPRINT);
62+
63+
64+
if (StringHelper.isNullOrBlank(msiEndpoint) || StringHelper.isNullOrBlank(identityHeader) || StringHelper.isNullOrBlank(identityServerThumbprint))
65+
{
66+
LOG.info("[Managed Identity] Service fabric managed identity is unavailable.");
67+
return null;
68+
}
69+
70+
return new ServiceFabricManagedIdentitySource(msalRequest, serviceBundle, validateAndGetUri(msiEndpoint), identityHeader);
71+
}
72+
73+
private static URI validateAndGetUri(String msiEndpoint)
74+
{
75+
try
76+
{
77+
URI endpointUri = new URI(msiEndpoint);
78+
LOG.info("[Managed Identity] Environment variables validation passed for Service Fabric Managed Identity. Endpoint URI: " + endpointUri);
79+
return endpointUri;
80+
}
81+
catch (URISyntaxException ex)
82+
{
83+
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT, String.format(
84+
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR, "MSI_ENDPOINT", msiEndpoint, "Service Fabric"),
85+
ManagedIdentitySourceType.SERVICE_FABRIC);
86+
}
87+
}
88+
89+
}

0 commit comments

Comments
 (0)