diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt index 2ca41d0af56ed..91c8c5daf8c16 100644 --- a/eng/versioning/external_dependencies.txt +++ b/eng/versioning/external_dependencies.txt @@ -199,7 +199,7 @@ com.microsoft.azure:azure-mgmt-resources;1.3.0 com.microsoft.azure:azure-mgmt-search;1.24.1 com.microsoft.azure:azure-mgmt-storage;1.3.0 com.microsoft.azure:azure-storage;8.0.0 -com.microsoft.azure:msal4j;1.12.0 +com.microsoft.azure:msal4j;1.13.0 com.microsoft.azure:msal4j-persistence-extension;1.1.0 com.sun.activation:jakarta.activation;1.2.2 io.opentelemetry:opentelemetry-api;1.14.0 diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml b/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml index 5487d19589cbc..c13ec3f9c2d22 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml @@ -64,7 +64,7 @@ com.microsoft.azure msal4j - 1.12.0 + 1.13.0 test diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml b/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml index 81726ca0b50ae..51f36e90bfec4 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml +++ b/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml @@ -68,7 +68,7 @@ com.microsoft.azure msal4j - 1.12.0 + 1.13.0 test diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml b/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml index bb4d43b79b1e4..54b903680e3b3 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml +++ b/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml @@ -77,7 +77,7 @@ com.microsoft.azure msal4j - 1.12.0 + 1.13.0 test diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 713c96d4e8e33..7a194e8d0f88c 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -1,17 +1,18 @@ # Release History -## 1.6.0-beta.1 (Unreleased) +## 1.6.0-beta.1 (2022-08-12) ### Features Added - `EnvironmentCredential` will read the environment variable `AZURE_CLIENT_CERTIFICATE_PASSWORD` for a `pem`/`pfx` certificate specified by `AZURE_CLIENT_CERTIFICATE_PATH`. +- Added support for in-memory token caching in `ManagedIdentityCredential`. ### Breaking Changes - Removed `VisualStudioCodeCredential` from `DefaultAzureCredential` token chain. [Issue 27364](https://github.com/Azure/azure-sdk-for-java/issues/27364) tracks this. -### Bugs Fixed - ### Other Changes +#### Dependency Updates +- Upgraded `msal4j` from `1.12.0` to version `1.13.0`. ## 1.5.4 (2022-08-08) diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml index 5f37e9bc11e7a..f0d6964a31e34 100644 --- a/sdk/identity/azure-identity/pom.xml +++ b/sdk/identity/azure-identity/pom.xml @@ -43,7 +43,7 @@ com.microsoft.azure msal4j - 1.12.0 + 1.13.0 com.microsoft.azure @@ -122,7 +122,7 @@ - com.microsoft.azure:msal4j:[1.12.0] + com.microsoft.azure:msal4j:[1.13.0] com.microsoft.azure:msal4j-persistence-extension:[1.1.0] net.java.dev.jna:jna-platform:[5.6.0] org.linguafranca.pwdb:KeePassJava2:[2.1.4] diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AksExchangeTokenCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AksExchangeTokenCredential.java index c133f3be400d5..fbdcd006c9c2b 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AksExchangeTokenCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AksExchangeTokenCredential.java @@ -33,6 +33,6 @@ public Mono authenticate(TokenRequestContext request) { + " 'AZURE_CLIENT_ID' environment variable or through the credential builder." + " Please ensure client id is provided to authenticate via token exchange in AKS environment."))); } - return identityClient.authenticateWithExchangeToken(request); + return identityClient.authenticateWithManagedIdentityConfidentialClient(request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java index 187df10b2efd1..0d8f9a8e0e515 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/AppServiceMsiCredential.java @@ -51,8 +51,6 @@ class AppServiceMsiCredential extends ManagedIdentityServiceCredential { * @return A publisher that emits an {@link AccessToken}. */ public Mono authenticate(TokenRequestContext request) { - return identityClient.authenticateToManagedIdentityEndpoint(identityEndpoint, identityHeader, - msiEndpoint, msiSecret, - request); + return identityClient.authenticateWithManagedIdentityConfidentialClient(request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java index a1597be839835..b1a5ce2d5630c 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ArcIdentityCredential.java @@ -49,6 +49,6 @@ public Mono authenticate(TokenRequestContext request) { + "with the system assigned identity omit the client id when constructing the" + " ManagedIdentityCredential.", null))); } - return identityClient.authenticateToArcManagedIdentityEndpoint(identityEndpoint, request); + return identityClient.authenticateWithManagedIdentityConfidentialClient(request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java index 2b2f1da7344c2..5a9b4d1401d50 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ManagedIdentityCredential.java @@ -11,6 +11,8 @@ import com.azure.core.util.logging.ClientLogger; import com.azure.identity.implementation.IdentityClientBuilder; import com.azure.identity.implementation.IdentityClientOptions; +import com.azure.identity.implementation.ManagedIdentityParameters; +import com.azure.identity.implementation.ManagedIdentityType; import com.azure.identity.implementation.util.LoggingUtil; import reactor.core.publisher.Mono; @@ -48,7 +50,6 @@ public final class ManagedIdentityCredential implements TokenCredential { Configuration configuration = identityClientOptions.getConfiguration() == null ? Configuration.getGlobalConfiguration().clone() : identityClientOptions.getConfiguration(); - /* * Choose credential based on available environment variables in this order: * @@ -62,18 +63,33 @@ public final class ManagedIdentityCredential implements TokenCredential { */ if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT)) { - managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, clientBuilder.build()); + managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, clientBuilder + .identityClientOptions(updateIdentityClientOptions(ManagedIdentityType.APP_SERVICE, + identityClientOptions, configuration)) + .build()); } else if (configuration.contains(Configuration.PROPERTY_IDENTITY_ENDPOINT)) { if (configuration.contains(Configuration.PROPERTY_IDENTITY_HEADER)) { if (configuration.get(PROPERTY_IDENTITY_SERVER_THUMBPRINT) != null) { - managedIdentityServiceCredential = new ServiceFabricMsiCredential(clientId, clientBuilder.build()); + managedIdentityServiceCredential = new ServiceFabricMsiCredential(clientId, clientBuilder + .identityClientOptions(updateIdentityClientOptions(ManagedIdentityType.SERVICE_FABRIC, + identityClientOptions, configuration)) + .build()); } else { - managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, clientBuilder.build()); + managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, clientBuilder + .identityClientOptions(updateIdentityClientOptions(ManagedIdentityType.APP_SERVICE, + identityClientOptions, configuration)) + .build()); } } else if (configuration.get(PROPERTY_IMDS_ENDPOINT) != null) { - managedIdentityServiceCredential = new ArcIdentityCredential(clientId, clientBuilder.build()); + managedIdentityServiceCredential = new ArcIdentityCredential(clientId, clientBuilder + .identityClientOptions(updateIdentityClientOptions(ManagedIdentityType.ARC, + identityClientOptions, configuration)) + .build()); } else { - managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder.build()); + managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder + .identityClientOptions(updateIdentityClientOptions(ManagedIdentityType.VM, + identityClientOptions, configuration)) + .build()); } } else if (configuration.contains(Configuration.PROPERTY_AZURE_TENANT_ID) && configuration.get(AZURE_FEDERATED_TOKEN_FILE) != null) { @@ -83,13 +99,51 @@ public final class ManagedIdentityCredential implements TokenCredential { clientBuilder.tenantId(configuration.get(Configuration.PROPERTY_AZURE_TENANT_ID)); clientBuilder.clientAssertionPath(configuration.get(AZURE_FEDERATED_TOKEN_FILE)); clientBuilder.clientAssertionTimeout(Duration.ofMinutes(5)); - managedIdentityServiceCredential = new AksExchangeTokenCredential(clientIdentifier, clientBuilder.build()); + managedIdentityServiceCredential = new AksExchangeTokenCredential(clientIdentifier, clientBuilder + .identityClientOptions(updateIdentityClientOptions(ManagedIdentityType.AKS, + identityClientOptions, configuration)) + .build()); } else { - managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder.build()); + managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, clientBuilder + .identityClientOptions(updateIdentityClientOptions(ManagedIdentityType.VM, + identityClientOptions, configuration)) + .build()); } LoggingUtil.logAvailableEnvironmentVariables(LOGGER, configuration); } + private IdentityClientOptions updateIdentityClientOptions(ManagedIdentityType managedIdentityType, + IdentityClientOptions clientOptions, Configuration configuration) { + switch (managedIdentityType) { + case APP_SERVICE: + return clientOptions + .setManagedIdentityType(ManagedIdentityType.APP_SERVICE) + .setManagedIdentityParameters(new ManagedIdentityParameters() + .setMsiEndpoint(configuration.get(Configuration.PROPERTY_MSI_ENDPOINT)) + .setMsiSecret(configuration.get(Configuration.PROPERTY_MSI_SECRET)) + .setIdentityEndpoint(configuration.get(Configuration.PROPERTY_IDENTITY_ENDPOINT)) + .setIdentityHeader(configuration.get(Configuration.PROPERTY_IDENTITY_HEADER))); + case SERVICE_FABRIC: + return clientOptions + .setManagedIdentityType(ManagedIdentityType.SERVICE_FABRIC) + .setManagedIdentityParameters(new ManagedIdentityParameters() + .setIdentityServerThumbprint(configuration.get(PROPERTY_IDENTITY_SERVER_THUMBPRINT)) + .setIdentityEndpoint(configuration.get(Configuration.PROPERTY_IDENTITY_ENDPOINT)) + .setIdentityHeader(configuration.get(Configuration.PROPERTY_IDENTITY_HEADER))); + case ARC: + return clientOptions + .setManagedIdentityType(ManagedIdentityType.ARC) + .setManagedIdentityParameters(new ManagedIdentityParameters() + .setIdentityEndpoint(configuration.get(Configuration.PROPERTY_IDENTITY_ENDPOINT))); + case VM: + return clientOptions.setManagedIdentityType(ManagedIdentityType.VM); + case AKS: + return clientOptions.setManagedIdentityType(ManagedIdentityType.AKS); + default: + return clientOptions; + } + } + /** * Gets the client ID of user assigned or system assigned identity. * @return the client ID of user assigned or system assigned identity. diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ServiceFabricMsiCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ServiceFabricMsiCredential.java index 9b2468855e89c..a02b354fb64c3 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/ServiceFabricMsiCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/ServiceFabricMsiCredential.java @@ -49,7 +49,6 @@ class ServiceFabricMsiCredential extends ManagedIdentityServiceCredential { * @return A publisher that emits an {@link AccessToken}. */ public Mono authenticate(TokenRequestContext request) { - return identityClient.authenticateToServiceFabricManagedIdentityEndpoint(identityEndpoint, identityHeader, - identityServerThumbprint, request); + return identityClient.authenticateWithManagedIdentityConfidentialClient(request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/VirtualMachineMsiCredential.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/VirtualMachineMsiCredential.java index 30fa1e37d1604..fde04fe7b8782 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/VirtualMachineMsiCredential.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/VirtualMachineMsiCredential.java @@ -31,6 +31,6 @@ class VirtualMachineMsiCredential extends ManagedIdentityServiceCredential { * @return A publisher that emits an {@link AccessToken}. */ public Mono authenticate(TokenRequestContext request) { - return identityClient.authenticateToIMDSEndpoint(request); + return identityClient.authenticateWithManagedIdentityConfidentialClient(request); } } diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java index 7ca2288bcdb8e..08c7cd4cbd59c 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClient.java @@ -47,6 +47,7 @@ import com.microsoft.aad.msal4j.RefreshTokenParameters; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; +import com.microsoft.aad.msal4j.TokenProviderResult; import com.sun.jna.Platform; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -137,6 +138,7 @@ public class IdentityClient { private HttpPipelineAdapter httpPipelineAdapter; private final SynchronizedAccessor publicClientApplicationAccessor; private final SynchronizedAccessor confidentialClientApplicationAccessor; + private final SynchronizedAccessor managedIdentityConfidentialClientApplicationAccessor; private final SynchronizedAccessor clientAssertionAccessor; @@ -182,6 +184,9 @@ public class IdentityClient { this.confidentialClientApplicationAccessor = new SynchronizedAccessor<>(() -> getConfidentialClientApplication()); + this.managedIdentityConfidentialClientApplicationAccessor = new SynchronizedAccessor<>(() -> + getManagedIdentityConfidentialClient()); + this.clientAssertionAccessor = clientAssertionTimeout == null ? new SynchronizedAccessor<>(() -> parseClientAssertion(), Duration.ofMinutes(5)) : new SynchronizedAccessor<>(() -> parseClientAssertion(), clientAssertionTimeout); @@ -277,6 +282,84 @@ private Mono getConfidentialClientApplication() { }); } + private Mono getManagedIdentityConfidentialClient() { + return Mono.defer(() -> { + String authorityUrl = TRAILING_FORWARD_SLASHES.matcher(options.getAuthorityHost()).replaceAll("") + + "/" + tenantId; + + // Temporarily pass in Dummy Client secret and Client ID. until MSal removes its requirements. + IClientCredential credential = ClientCredentialFactory + .createFromSecret(clientSecret != null ? clientSecret : "dummy-secret"); + ConfidentialClientApplication.Builder applicationBuilder = + ConfidentialClientApplication.builder(clientId == null ? "SYSTEM-ASSIGNED-MANAGED-IDENTITY" + : clientId, credential); + try { + applicationBuilder = applicationBuilder.authority(authorityUrl); + } catch (MalformedURLException e) { + return Mono.error(LOGGER.logExceptionAsWarning(new IllegalStateException(e))); + } + + if (options.getManagedIdentityType() == null) { + return Mono.error(LOGGER.logExceptionAsError( + new CredentialUnavailableException("Managed Identity type not configured, authentication not available."))); + } + applicationBuilder.appTokenProvider(appTokenProviderParameters -> { + TokenRequestContext trc = new TokenRequestContext() + .setScopes(new ArrayList<>(appTokenProviderParameters.scopes)) + .setClaims(appTokenProviderParameters.claims) + .setTenantId(appTokenProviderParameters.tenantId); + + Mono accessTokenAsync = getTokenFromTargetManagedIdentity(trc); + + return accessTokenAsync.map(accessToken -> { + TokenProviderResult result = new TokenProviderResult(); + result.setAccessToken(accessToken.getToken()); + result.setTenantId(trc.getTenantId()); + result.setExpiresInSeconds(accessToken.getExpiresAt().toEpochSecond()); + return result; + }).toFuture(); + }); + + + initializeHttpPipelineAdapter(); + if (httpPipelineAdapter != null) { + applicationBuilder.httpClient(httpPipelineAdapter); + } else { + applicationBuilder.proxy(proxyOptionsToJavaNetProxy(options.getProxyOptions())); + } + + if (options.getExecutorService() != null) { + applicationBuilder.executorService(options.getExecutorService()); + } + + ConfidentialClientApplication confidentialClientApplication = applicationBuilder.build(); + return Mono.just(confidentialClientApplication); + }); + } + + Mono getTokenFromTargetManagedIdentity(TokenRequestContext tokenRequestContext) { + ManagedIdentityParameters parameters = options.getManagedIdentityParameters(); + ManagedIdentityType managedIdentityType = options.getManagedIdentityType(); + switch (managedIdentityType) { + case APP_SERVICE: + return authenticateToManagedIdentityEndpoint(parameters.getIdentityEndpoint(), + parameters.getIdentityHeader(), parameters.getMsiEndpoint(), parameters.getMsiSecret(), + tokenRequestContext); + case SERVICE_FABRIC: + return authenticateToServiceFabricManagedIdentityEndpoint(parameters.getIdentityEndpoint(), + parameters.getIdentityHeader(), parameters.getIdentityServerThumbprint(), tokenRequestContext); + case ARC: + return authenticateToArcManagedIdentityEndpoint(parameters.getIdentityEndpoint(), tokenRequestContext); + case AKS: + return authenticateWithExchangeToken(tokenRequestContext); + case VM: + return authenticateToIMDSEndpoint(tokenRequestContext); + default: + return Mono.error(LOGGER.logExceptionAsError( + new CredentialUnavailableException("Unknown Managed Identity type, authentication not available."))); + } + } + private Mono parseClientAssertion() { return Mono.fromCallable(() -> { if (clientAssertionFilePath != null) { @@ -288,7 +371,6 @@ private Mono parseClientAssertion() { + " It should be provided to authenticate with client assertion." )); } - }); } @@ -632,7 +714,6 @@ public Mono authenticateWithOBO(TokenRequestContext request) { .map(MsalToken::new)); } - private Mono getAccessTokenFromPowerShell(TokenRequestContext request, PowershellManager powershellManager) { return powershellManager.initSession() @@ -705,6 +786,18 @@ public Mono authenticateWithConfidentialClient(TokenRequestContext )).map(MsalToken::new); } + public Mono authenticateWithManagedIdentityConfidentialClient(TokenRequestContext request) { + return managedIdentityConfidentialClientApplicationAccessor.getValue() + .flatMap(confidentialClient -> Mono.fromFuture(() -> { + ClientCredentialParameters.ClientCredentialParametersBuilder builder = + ClientCredentialParameters.builder(new HashSet<>(request.getScopes())) + .tenant(IdentityUtil + .resolveTenantId(tenantId, request, options)); + return confidentialClient.acquireToken(builder.build()); + } + )).map(MsalToken::new); + } + private HttpPipeline setupPipeline(HttpClient httpClient) { List policies = new ArrayList<>(); HttpLogOptions httpLogOptions = new HttpLogOptions(); @@ -1047,7 +1140,7 @@ public Mono authenticateWithSharedTokenCache(TokenRequestContext requ * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateToArcManagedIdentityEndpoint(String identityEndpoint, + private Mono authenticateToArcManagedIdentityEndpoint(String identityEndpoint, TokenRequestContext request) { return Mono.fromCallable(() -> { HttpURLConnection connection = null; @@ -1142,7 +1235,7 @@ public Mono authenticateToArcManagedIdentityEndpoint(String identit * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateWithExchangeToken(TokenRequestContext request) { + private Mono authenticateWithExchangeToken(TokenRequestContext request) { return clientAssertionAccessor.getValue() .flatMap(assertionToken -> Mono.fromCallable(() -> { @@ -1201,7 +1294,7 @@ public Mono authenticateWithExchangeToken(TokenRequestContext reque * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateToServiceFabricManagedIdentityEndpoint(String identityEndpoint, + private Mono authenticateToServiceFabricManagedIdentityEndpoint(String identityEndpoint, String identityHeader, String thumbprint, TokenRequestContext request) { @@ -1271,7 +1364,7 @@ public Mono authenticateToServiceFabricManagedIdentityEndpoint(Stri * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateToManagedIdentityEndpoint(String identityEndpoint, String identityHeader, + private Mono authenticateToManagedIdentityEndpoint(String identityEndpoint, String identityHeader, String msiEndpoint, String msiSecret, TokenRequestContext request) { return Mono.fromCallable(() -> { @@ -1357,7 +1450,7 @@ static URL getUrl(String uri) throws MalformedURLException { * @param request the details of the token request * @return a Publisher that emits an AccessToken */ - public Mono authenticateToIMDSEndpoint(TokenRequestContext request) { + private Mono authenticateToIMDSEndpoint(TokenRequestContext request) { String resource = ScopeUtil.scopesToResource(request.getScopes()); StringBuilder payload = new StringBuilder(); final int imdsUpgradeTimeInMs = 70 * 1000; diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java index d347f511d12b8..a924c77ee7dd9 100644 --- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/IdentityClientOptions.java @@ -50,6 +50,8 @@ public final class IdentityClientOptions { private Configuration configuration; private IdentityLogOptionsImpl identityLogOptionsImpl; private boolean accountIdentifierLogging; + private ManagedIdentityType managedIdentityType; + private ManagedIdentityParameters managedIdentityParameters; /** * Creates an instance of IdentityClientOptions with default settings. @@ -424,6 +426,43 @@ public IdentityClientOptions setIdentityLogOptionsImpl(IdentityLogOptionsImpl id return this; } + /** + * Set the Managed Identity Type + * @param managedIdentityType the Managed Identity Type + * @return the updated identity client options + */ + public IdentityClientOptions setManagedIdentityType(ManagedIdentityType managedIdentityType) { + this.managedIdentityType = managedIdentityType; + return this; + } + + /** + * Get the Managed Identity Type + * @return the Managed Identity Type + */ + public ManagedIdentityType getManagedIdentityType() { + return managedIdentityType; + } + + /** + * Get the Managed Identity parameters + * @return the Managed Identity Parameters + */ + public ManagedIdentityParameters getManagedIdentityParameters() { + return managedIdentityParameters; + } + + /** + * Configure the managed identity parameters. + * + * @param managedIdentityParameters the managed identity parameters to use for authentication. + * @return the updated identity client options + */ + public IdentityClientOptions setManagedIdentityParameters(ManagedIdentityParameters managedIdentityParameters) { + this.managedIdentityParameters = managedIdentityParameters; + return this; + } + /** * Loads the details from the specified Configuration Store. */ diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/ManagedIdentityParameters.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/ManagedIdentityParameters.java new file mode 100644 index 0000000000000..3e2ea918ba3ea --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/ManagedIdentityParameters.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.implementation; + +/** + * Wrapper Class for Managed Identity Parameters. + */ +public class ManagedIdentityParameters { + private String identityEndpoint; + private String identityHeader; + private String msiEndpoint; + private String msiSecret; + private String identityServerThumbprint; + + /** + * Creates an Instance of ManagedIdentityParameters. + */ + public ManagedIdentityParameters() { } + + /** + * Get the Identity Endpoint. + * @return the Identity Endpoint. + */ + public String getIdentityEndpoint() { + return identityEndpoint; + } + + /** + * Set the Identity Endpoint. + * @param identityEndpoint the Identity Endpoint. + * @return the {@link ManagedIdentityParameters} + */ + public ManagedIdentityParameters setIdentityEndpoint(String identityEndpoint) { + this.identityEndpoint = identityEndpoint; + return this; + } + + /** + * Get the Identity Header. + * @return the Identity Header. + */ + public String getIdentityHeader() { + return identityHeader; + } + + /** + * Set the Identity Header. + * @param identityHeader the Identity Header. + * @return the Identity Header + */ + public ManagedIdentityParameters setIdentityHeader(String identityHeader) { + this.identityHeader = identityHeader; + return this; + } + + /** + * Get the MSI Endpoint. + * @return the MSI Endpoint. + */ + public String getMsiEndpoint() { + return msiEndpoint; + } + + /** + * Set the MSI Endpoint + * @param msiEndpoint the MSI Endpoint + * @return the MSI endpoint. + */ + public ManagedIdentityParameters setMsiEndpoint(String msiEndpoint) { + this.msiEndpoint = msiEndpoint; + return this; + } + + /** + * Get the MSI Secret. + * @return the MSI Secret. + */ + public String getMsiSecret() { + return msiSecret; + } + + /** + * Set the MSI Secret + * @param msiSecret the MSI Secret + * @return the MSI Secret + */ + public ManagedIdentityParameters setMsiSecret(String msiSecret) { + this.msiSecret = msiSecret; + return this; + } + + /** + * Get the Identity Server Thumbprint + * @return the Identity Server Thumbprint + */ + public String getIdentityServerThumbprint() { + return identityServerThumbprint; + } + + /** + * Set the Identity Server Thumbprint + * @param identityServerThumbprint the Identity Server Thumbprint + * @return the Identity Server Thumbprint + */ + public ManagedIdentityParameters setIdentityServerThumbprint(String identityServerThumbprint) { + this.identityServerThumbprint = identityServerThumbprint; + return this; + } +} diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/ManagedIdentityType.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/ManagedIdentityType.java new file mode 100644 index 0000000000000..bde15ec32f3f7 --- /dev/null +++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/ManagedIdentityType.java @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.identity.implementation; + +/** + * Enum used to represent different Managed Identity platforms. + */ +public enum ManagedIdentityType { + VM, + APP_SERVICE, + SERVICE_FABRIC, + ARC, + AKS; +} diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java index 92823bde921fd..2832ea86f7c8a 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/AzureApplicationCredentialTest.java @@ -67,7 +67,7 @@ public void testUseManagedIdentityCredential() throws Exception { // mock try (MockedConstruction identityClientMock = mockConstruction(IdentityClient.class, (identityClient, context) -> { - when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(identityClient.authenticateWithManagedIdentityConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); }); MockedConstruction intelliCredentialMock = mockConstruction(IntelliJCredential.class, (intelliJCredential, context) -> { when(intelliJCredential.getToken(request)).thenReturn(Mono.empty()); @@ -91,7 +91,7 @@ public void testNoCredentialWorks() throws Exception { Configuration configuration = new ConfigurationBuilder(source, source, source).build(); // mock try (MockedConstruction identityClientMock = mockConstruction(IdentityClient.class, (identityClient, context) -> { - when(identityClient.authenticateToIMDSEndpoint(request)) + when(identityClient.authenticateWithManagedIdentityConfidentialClient(request)) .thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from managed identity"))); })) { // test diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java index f7862d2227452..b70302fd57515 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/DefaultAzureCredentialTest.java @@ -70,7 +70,7 @@ public void testUseManagedIdentityCredential() throws Exception { // mock try (MockedConstruction mocked = mockConstruction(IdentityClient.class, (identityClient, context) -> { - when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(identityClient.authenticateWithManagedIdentityConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); }); MockedConstruction ijcredential = mockConstruction(IntelliJCredential.class, (intelliJCredential, context) -> { when(intelliJCredential.getToken(request)).thenReturn(Mono.empty()); })) { @@ -96,7 +96,7 @@ public void testUseAzureCliCredential() throws Exception { // mock try (MockedConstruction mocked = mockConstruction(IdentityClient.class, (identityClient, context) -> { when(identityClient.authenticateWithAzureCli(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); - when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(Mono.empty()); + when(identityClient.authenticateWithManagedIdentityConfidentialClient(request)).thenReturn(Mono.empty()); when(identityClient.authenticateWithSharedTokenCache(request, null)).thenReturn(Mono.empty()); when(identityClient.authenticateWithIntelliJ(request)).thenReturn(Mono.empty()); when(identityClient.authenticateWithVsCodeCredential(any(), any())).thenReturn(Mono.empty()); @@ -121,7 +121,7 @@ public void testNoCredentialWorks() throws Exception { // mock try (MockedConstruction identityClientMock = mockConstruction(IdentityClient.class, (identityClient, context) -> { - when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from managed identity"))); + when(identityClient.authenticateWithManagedIdentityConfidentialClient(request)).thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from managed identity"))); }); MockedConstruction sharedTokenCacheCredentialMock = mockConstruction(SharedTokenCacheCredential.class, (sharedTokenCacheCredential, context) -> { when(sharedTokenCacheCredential.getToken(request)).thenReturn(Mono.error(new CredentialUnavailableException("Cannot get token from shared token cache"))); }); MockedConstruction azureCliCredentialMock = mockConstruction(AzureCliCredential.class, (azureCliCredential, context) -> { diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java index 6bc79e93738b7..46ea04022e78b 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/ManagedIdentityCredentialTest.java @@ -50,7 +50,7 @@ public void testMSIEndpoint() throws Exception { // mock try (MockedConstruction identityClientMock = mockConstruction(IdentityClient.class, (identityClient, context) -> { - when(identityClient.authenticateToManagedIdentityEndpoint(endpoint, secret, endpoint, secret, request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); + when(identityClient.authenticateWithManagedIdentityConfidentialClient(request1)).thenReturn(TestUtils.getMockAccessToken(token1, expiresAt)); })) { // test ManagedIdentityCredential credential = new ManagedIdentityCredentialBuilder().configuration(configuration).clientId(CLIENT_ID).build(); @@ -78,7 +78,7 @@ public void testIMDS() throws Exception { // mock try (MockedConstruction identityClientMock = mockConstruction(IdentityClient.class, (identityClient, context) -> { - when(identityClient.authenticateToIMDSEndpoint(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); + when(identityClient.authenticateWithManagedIdentityConfidentialClient(request)).thenReturn(TestUtils.getMockAccessToken(token1, expiresOn)); })) { // test diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java index 29619d5d37b84..ac5983770b943 100644 --- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java +++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/implementation/IdentityClientTests.java @@ -189,11 +189,16 @@ public void testValidServiceFabricCodeFlow() throws Exception { String tokenJson = "{ \"access_token\" : \"token1\", \"expires_on\" : \"" + expiresOn.toEpochSecond() + "\" }"; // mock - IdentityClient client = new IdentityClientBuilder().build(); + IdentityClientOptions options = new IdentityClientOptions() + .setManagedIdentityType(ManagedIdentityType.SERVICE_FABRIC) + .setManagedIdentityParameters(new ManagedIdentityParameters() + .setIdentityEndpoint(endpoint) + .setIdentityHeader(secret) + .setIdentityServerThumbprint(thumbprint)); + IdentityClient client = new IdentityClientBuilder().identityClientOptions(options).build(); mockForServiceFabricCodeFlow(tokenJson, () -> { // test - AccessToken token = client.authenticateToServiceFabricManagedIdentityEndpoint(endpoint, secret, - thumbprint, request).block(); + AccessToken token = client.getTokenFromTargetManagedIdentity(request).block(); Assert.assertEquals("token1", token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); }); @@ -213,10 +218,15 @@ public void testValidIdentityEndpointMSICodeFlow() throws Exception { String tokenJson = "{ \"access_token\" : \"token1\", \"expires_on\" : \"" + expiresOn.format(dtf) + "\" }"; // mock - IdentityClient client = new IdentityClientBuilder().build(); + IdentityClientOptions options = new IdentityClientOptions() + .setManagedIdentityType(ManagedIdentityType.APP_SERVICE) + .setManagedIdentityParameters(new ManagedIdentityParameters() + .setIdentityEndpoint(endpoint) + .setIdentityHeader(secret)); + IdentityClient client = new IdentityClientBuilder().identityClientOptions(options).build(); mockForMSICodeFlow(tokenJson, () -> { // test - AccessToken token = client.authenticateToManagedIdentityEndpoint(endpoint, secret, null, null, request).block(); + AccessToken token = client.getTokenFromTargetManagedIdentity(request).block(); Assert.assertEquals("token1", token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); }); @@ -230,9 +240,13 @@ public void testInValidIdentityEndpointSecretArcCodeFlow() throws Exception { TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com"); configuration.put("IDENTITY_ENDPOINT", endpoint); // mock - IdentityClient client = new IdentityClientBuilder().build(); + IdentityClientOptions options = new IdentityClientOptions() + .setManagedIdentityType(ManagedIdentityType.ARC) + .setManagedIdentityParameters(new ManagedIdentityParameters() + .setIdentityEndpoint(endpoint)); + IdentityClient client = new IdentityClientBuilder().identityClientOptions(options).build(); mockForArcCodeFlow(401, () -> { - client.authenticateToArcManagedIdentityEndpoint(endpoint, request).block(); + client.getTokenFromTargetManagedIdentity(request).block(); }); } @@ -243,10 +257,14 @@ public void testInValidIdentityEndpointResponseCodeArcCodeFlow() throws Exceptio String endpoint = "http://localhost"; TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com"); configuration.put("IDENTITY_ENDPOINT", endpoint); - IdentityClient client = new IdentityClientBuilder().build(); + IdentityClientOptions options = new IdentityClientOptions() + .setManagedIdentityType(ManagedIdentityType.ARC) + .setManagedIdentityParameters(new ManagedIdentityParameters() + .setIdentityEndpoint(endpoint)); + IdentityClient client = new IdentityClientBuilder().identityClientOptions(options).build(); // mock mockForArcCodeFlow(200, () -> { - client.authenticateToArcManagedIdentityEndpoint(endpoint, request).block(); + client.getTokenFromTargetManagedIdentity(request).block(); }); } @@ -264,11 +282,13 @@ public void testValidIMDSCodeFlow() throws Exception { String tokenJson = "{ \"access_token\" : \"token1\", \"expires_on\" : \"" + expiresOn.format(dtf) + "\" }"; - IdentityClient client = new IdentityClientBuilder().build(); + IdentityClientOptions options = new IdentityClientOptions() + .setManagedIdentityType(ManagedIdentityType.VM); + IdentityClient client = new IdentityClientBuilder().identityClientOptions(options).build(); // mock mockForIMDSCodeFlow(IdentityConstants.DEFAULT_IMDS_ENDPOINT, tokenJson, () -> { // test - AccessToken token = client.authenticateToIMDSEndpoint(request).block(); + AccessToken token = client.getTokenFromTargetManagedIdentity(request).block(); Assert.assertEquals("token1", token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); }); @@ -286,11 +306,13 @@ public void testCustomIMDSCodeFlow() throws Exception { String tokenJson = "{ \"access_token\" : \"token1\", \"expires_on\" : \"" + expiresOn.format(dtf) + "\" }"; - IdentityClient client = new IdentityClientBuilder().build(); + IdentityClientOptions options = new IdentityClientOptions() + .setManagedIdentityType(ManagedIdentityType.VM); + IdentityClient client = new IdentityClientBuilder().identityClientOptions(options).build(); // mock mockForIMDSCodeFlow(endpoint, tokenJson, () -> { // test - AccessToken token = client.authenticateToIMDSEndpoint(request).block(); + AccessToken token = client.getTokenFromTargetManagedIdentity(request).block(); Assert.assertEquals("token1", token.getToken()); Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); }); @@ -393,7 +415,61 @@ public void testOpenUrl() throws Exception { } } + @Test + public void testAuthWithManagedIdentityFlow() throws Exception { + // setup + String secret = "SYSTEM-ASSIGNED-CLIENT-SECRET"; + String clientId = "SYSTEM-ASSIGNED-CLIENT-ID"; + String accessToken = "token"; + TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com"); + OffsetDateTime expiresOn = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1); + + // mock + mockForManagedIdentityFlow(secret, clientId, request, accessToken, expiresOn, () -> { + // test + IdentityClient client = new IdentityClientBuilder() + .tenantId(TENANT_ID) + .clientId(clientId) + .clientSecret(secret) + .identityClientOptions(new IdentityClientOptions() + .setManagedIdentityType(ManagedIdentityType.VM)) + .build(); + AccessToken token = client.authenticateWithManagedIdentityConfidentialClient(request).block(); + Assert.assertEquals(accessToken, token.getToken()); + Assert.assertEquals(expiresOn.getSecond(), token.getExpiresAt().getSecond()); + }); + } + /****** mocks ******/ + private void mockForManagedIdentityFlow(String secret, String clientId, TokenRequestContext request, String accessToken, OffsetDateTime expiresOn, Runnable test) throws Exception { + + try (MockedStatic staticConfidentialClientApplicationMock = mockStatic(ConfidentialClientApplication.class); MockedConstruction confidentialClientApplicationBuilderMock = mockConstruction(ConfidentialClientApplication.Builder.class, (builder, context) -> { + + when(builder.authority(any())).thenReturn(builder); + when(builder.httpClient(any())).thenReturn(builder); + when(builder.appTokenProvider(any())).thenReturn(builder); + ConfidentialClientApplication application = Mockito.mock(ConfidentialClientApplication.class); + when(application.acquireToken(any(ClientCredentialParameters.class))).thenAnswer(invocation -> { + ClientCredentialParameters argument = (ClientCredentialParameters) invocation.getArguments()[0]; + if (argument.scopes().size() == 1 && request.getScopes().get(0).equals(argument.scopes().iterator().next())) { + return TestUtils.getMockAuthenticationResult(accessToken, expiresOn); + } else { + return CompletableFuture.runAsync(() -> { + throw new MsalServiceException("Invalid request", "InvalidScopes"); + }); + } + }); + when(builder.build()).thenReturn(application); + })) { + // Mocking the static builder to ensure we pass the right thing to it. + staticConfidentialClientApplicationMock.when(() -> ConfidentialClientApplication.builder(eq(clientId), argThat(cred -> ((IClientSecret) cred).clientSecret().equals(secret)))).thenCallRealMethod(); + staticConfidentialClientApplicationMock.when(() -> ConfidentialClientApplication.builder(anyString(), argThat(cred -> !((IClientSecret) cred).clientSecret().equals(secret)))).thenThrow(new MsalServiceException("Invalid clientSecret", "InvalidClientSecret")); + staticConfidentialClientApplicationMock.when(() -> ConfidentialClientApplication.builder(AdditionalMatchers.not(eq(clientId)), any(IClientSecret.class))).thenThrow(new MsalServiceException("Invalid CLIENT_ID", "InvalidClientId")); + + test.run(); + Assert.assertNotNull(confidentialClientApplicationBuilderMock); + } + } private void mockForClientSecret(String secret, TokenRequestContext request, String accessToken, OffsetDateTime expiresOn, Runnable test) throws Exception {