-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <avdunn@microsoft.com> Co-authored-by: Avery-Dunn <62066438+Avery-Dunn@users.noreply.github.com>
- Loading branch information
1 parent
1f7fa9d
commit cc7f1c6
Showing
13 changed files
with
473 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AzureArcManagedIdentitySource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package com.microsoft.aad.msal4j; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.FileReader; | ||
import java.io.IOException; | ||
import java.net.HttpURLConnection; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
|
||
class AzureArcManagedIdentitySource extends AbstractManagedIdentitySource{ | ||
|
||
private final static Logger LOG = LoggerFactory.getLogger(AzureArcManagedIdentitySource.class); | ||
private static final String ARC_API_VERSION = "2019-11-01"; | ||
private static final String AZURE_ARC = "Azure Arc"; | ||
|
||
private final URI MSI_ENDPOINT; | ||
|
||
static AbstractManagedIdentitySource create(MsalRequest msalRequest, ServiceBundle serviceBundle) | ||
{ | ||
IEnvironmentVariables environmentVariables = getEnvironmentVariables((ManagedIdentityParameters) msalRequest.requestContext().apiParameters()); | ||
String identityEndpoint = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_ENDPOINT); | ||
String imdsEndpoint = environmentVariables.getEnvironmentVariable(Constants.IMDS_ENDPOINT); | ||
|
||
URI validatedUri = validateAndGetUri(identityEndpoint, imdsEndpoint); | ||
return validatedUri == null ? null : new AzureArcManagedIdentitySource(validatedUri, msalRequest, serviceBundle ); | ||
} | ||
|
||
private static URI validateAndGetUri(String identityEndpoint, String imdsEndpoint) { | ||
|
||
// if BOTH the env vars IDENTITY_ENDPOINT and IMDS_ENDPOINT are set the MsiType is Azure Arc | ||
if (StringHelper.isNullOrBlank(identityEndpoint) || StringHelper.isNullOrBlank(imdsEndpoint)) | ||
{ | ||
LOG.info("[Managed Identity] Azure Arc managed identity is unavailable."); | ||
return null; | ||
} | ||
|
||
URI endpointUri; | ||
try { | ||
endpointUri = new URI(identityEndpoint); | ||
} catch (URISyntaxException e) { | ||
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT, String.format( | ||
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR, "IDENTITY_ENDPOINT", identityEndpoint, AZURE_ARC), | ||
ManagedIdentitySourceType.AZURE_ARC); | ||
} | ||
|
||
LOG.info("[Managed Identity] Creating Azure Arc managed identity. Endpoint URI: " + endpointUri); | ||
return endpointUri; | ||
} | ||
|
||
private AzureArcManagedIdentitySource(URI endpoint, MsalRequest msalRequest, ServiceBundle serviceBundle){ | ||
super(msalRequest, serviceBundle, ManagedIdentitySourceType.AZURE_ARC); | ||
this.MSI_ENDPOINT = endpoint; | ||
|
||
ManagedIdentityIdType idType = | ||
((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getIdType(); | ||
if (idType != ManagedIdentityIdType.SYSTEM_ASSIGNED) { | ||
throw new MsalManagedIdentityException(MsalError.USER_ASSIGNED_MANAGED_IDENTITY_NOT_SUPPORTED, | ||
String.format(MsalErrorMessage.MANAGED_IDENTITY_USER_ASSIGNED_NOT_SUPPORTED, AZURE_ARC), | ||
ManagedIdentitySourceType.AZURE_ARC); | ||
} | ||
} | ||
|
||
@Override | ||
public void createManagedIdentityRequest(String resource) | ||
{ | ||
managedIdentityRequest.baseEndpoint = MSI_ENDPOINT; | ||
managedIdentityRequest.method = HttpMethod.GET; | ||
|
||
managedIdentityRequest.headers = new HashMap<>(); | ||
managedIdentityRequest.headers.put("Metadata", "true"); | ||
|
||
managedIdentityRequest.queryParameters = new HashMap<>(); | ||
managedIdentityRequest.queryParameters.put("api-version", Collections.singletonList(ARC_API_VERSION)); | ||
managedIdentityRequest.queryParameters.put("resource", Collections.singletonList(resource)); | ||
} | ||
|
||
@Override | ||
public ManagedIdentityResponse handleResponse( | ||
ManagedIdentityParameters parameters, | ||
IHttpResponse response) { | ||
|
||
LOG.info("[Managed Identity] Response received. Status code: {response.StatusCode}"); | ||
|
||
if (response.statusCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { | ||
if(!response.headers().containsKey("Www-Authenticate")) { | ||
LOG.error("[Managed Identity] WWW-Authenticate header is expected but not found."); | ||
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_REQUEST_FAILED, | ||
MsalErrorMessage.MANAGED_IDENTITY_NO_CHALLENGE_ERROR, | ||
ManagedIdentitySourceType.AZURE_ARC); | ||
} | ||
|
||
String challenge = response.headers().get("Www-Authenticate").get(0); | ||
String[] splitChallenge = challenge.split("="); | ||
|
||
if (splitChallenge.length != 2) { | ||
LOG.error("[Managed Identity] The WWW-Authenticate header for Azure arc managed identity is not an expected format."); | ||
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_REQUEST_FAILED, | ||
MsalErrorMessage.MANAGED_IDENTITY_INVALID_CHALLENGE, | ||
ManagedIdentitySourceType.AZURE_ARC); | ||
} | ||
|
||
Path path = Paths.get(splitChallenge[1]); | ||
|
||
String authHeaderValue = null; | ||
try { | ||
authHeaderValue = "Basic " + new String(Files.readAllBytes(path), StandardCharsets.UTF_8); | ||
} catch (IOException e) { | ||
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_FILE_READ_ERROR, e.getMessage(), ManagedIdentitySourceType.AZURE_ARC); | ||
} | ||
|
||
createManagedIdentityRequest(parameters.resource); | ||
|
||
LOG.info("[Managed Identity] Adding authorization header to the request."); | ||
|
||
managedIdentityRequest.headers.put("Authorization", authHeaderValue); | ||
|
||
try { | ||
response = HttpHelper.executeHttpRequest( | ||
new HttpRequest(HttpMethod.GET, managedIdentityRequest.computeURI().toString(), | ||
managedIdentityRequest.headers), | ||
managedIdentityRequest.requestContext(), | ||
serviceBundle); | ||
} catch (URISyntaxException e) { | ||
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT, | ||
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR, | ||
managedIdentitySourceType); | ||
} | ||
|
||
return super.handleResponse(parameters, response); | ||
} | ||
|
||
return super.handleResponse(parameters, response); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ServiceFabricManagedIdentitySource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package com.microsoft.aad.msal4j; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
|
||
class ServiceFabricManagedIdentitySource extends AbstractManagedIdentitySource { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(ServiceFabricManagedIdentitySource.class); | ||
|
||
private static final String SERVICE_FABRIC_MSI_API_VERSION = "2019-07-01-preview"; | ||
|
||
private final URI msiEndpoint; | ||
private final String identityHeader; | ||
private final ManagedIdentityIdType idType; | ||
private final String userAssignedId; | ||
|
||
@Override | ||
public void createManagedIdentityRequest(String resource) { | ||
managedIdentityRequest.baseEndpoint = msiEndpoint; | ||
managedIdentityRequest.method = HttpMethod.GET; | ||
|
||
managedIdentityRequest.headers = new HashMap<>(); | ||
managedIdentityRequest.headers.put("secret", identityHeader); | ||
|
||
managedIdentityRequest.queryParameters = new HashMap<>(); | ||
managedIdentityRequest.queryParameters.put("resource", Collections.singletonList(resource)); | ||
managedIdentityRequest.queryParameters.put("api-version", Collections.singletonList(SERVICE_FABRIC_MSI_API_VERSION)); | ||
|
||
if (idType == ManagedIdentityIdType.CLIENT_ID) { | ||
LOG.info("[Managed Identity] Adding user assigned client id to the request for Service Fabric Managed Identity."); | ||
managedIdentityRequest.queryParameters.put(Constants.MANAGED_IDENTITY_CLIENT_ID, Collections.singletonList(userAssignedId)); | ||
} else if (idType == ManagedIdentityIdType.RESOURCE_ID) { | ||
LOG.info("[Managed Identity] Adding user assigned resource id to the request for Service Fabric Managed Identity."); | ||
managedIdentityRequest.queryParameters.put(Constants.MANAGED_IDENTITY_RESOURCE_ID, Collections.singletonList(userAssignedId)); | ||
} | ||
} | ||
|
||
private ServiceFabricManagedIdentitySource(MsalRequest msalRequest, ServiceBundle serviceBundle, URI msiEndpoint, String identityHeader) | ||
{ | ||
super(msalRequest, serviceBundle, ManagedIdentitySourceType.SERVICE_FABRIC); | ||
this.msiEndpoint = msiEndpoint; | ||
this.identityHeader = identityHeader; | ||
|
||
this.idType = ((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getIdType(); | ||
this.userAssignedId = ((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getUserAssignedId(); | ||
} | ||
|
||
static AbstractManagedIdentitySource create(MsalRequest msalRequest, ServiceBundle serviceBundle) { | ||
|
||
IEnvironmentVariables environmentVariables = getEnvironmentVariables((ManagedIdentityParameters) msalRequest.requestContext().apiParameters()); | ||
String msiEndpoint = environmentVariables.getEnvironmentVariable(Constants.MSI_ENDPOINT); | ||
String identityHeader = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_ENDPOINT); | ||
String identityServerThumbprint = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_SERVER_THUMBPRINT); | ||
|
||
|
||
if (StringHelper.isNullOrBlank(msiEndpoint) || StringHelper.isNullOrBlank(identityHeader) || StringHelper.isNullOrBlank(identityServerThumbprint)) | ||
{ | ||
LOG.info("[Managed Identity] Service fabric managed identity is unavailable."); | ||
return null; | ||
} | ||
|
||
return new ServiceFabricManagedIdentitySource(msalRequest, serviceBundle, validateAndGetUri(msiEndpoint), identityHeader); | ||
} | ||
|
||
private static URI validateAndGetUri(String msiEndpoint) | ||
{ | ||
try | ||
{ | ||
URI endpointUri = new URI(msiEndpoint); | ||
LOG.info("[Managed Identity] Environment variables validation passed for Service Fabric Managed Identity. Endpoint URI: " + endpointUri); | ||
return endpointUri; | ||
} | ||
catch (URISyntaxException ex) | ||
{ | ||
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT, String.format( | ||
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR, "MSI_ENDPOINT", msiEndpoint, "Service Fabric"), | ||
ManagedIdentitySourceType.SERVICE_FABRIC); | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.