Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Token Caching Support For Managed Identity #30282

Merged
merged 13 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sdk/identity/azure-identity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.12.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
<version>1.13.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
Expand Down Expand Up @@ -104,7 +104,7 @@
<rules>
<bannedDependencies>
<includes>
<include>com.microsoft.azure:msal4j:[1.12.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j:[1.13.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j;external_dependency} -->
<include>com.microsoft.azure:msal4j-persistence-extension:[1.1.0]</include> <!-- {x-include-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
<include>net.java.dev.jna:jna-platform:[5.6.0]</include> <!-- {x-include-update;net.java.dev.jna:jna-platform;external_dependency} -->
<include>org.linguafranca.pwdb:KeePassJava2:[2.1.4]</include> <!-- {x-include-update;org.linguafranca.pwdb:KeePassJava2;external_dependency} -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ public Mono<AccessToken> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ class AppServiceMsiCredential extends ManagedIdentityServiceCredential {
* @return A publisher that emits an {@link AccessToken}.
*/
public Mono<AccessToken> authenticate(TokenRequestContext request) {
return identityClient.authenticateToManagedIdentityEndpoint(identityEndpoint, identityHeader,
msiEndpoint, msiSecret,
request);
return identityClient.authenticateWithManagedIdentityConfidentialClient(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ public Mono<AccessToken> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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:
*
Expand All @@ -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) {
Expand All @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is NONE really a valid value? should this be an error?

Copy link
Member Author

@g2vinay g2vinay Aug 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed NONE on second design iteration.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I'm not sure that's entirely right either - now won't the default value be VM? We probably need a NONE or DEFAULT to indicate the unchosen value?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah in our code flow we default to VM, that's our default based on the logic we follow.
None or Unchosen are invalid states to have and are equivalent to the value being null.

return clientOptions.setManagedIdentityType(ManagedIdentityType.NONE);
}
}

/**
* Gets the client ID of user assigned or system assigned identity.
* @return the client ID of user assigned or system assigned identity.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ class ServiceFabricMsiCredential extends ManagedIdentityServiceCredential {
* @return A publisher that emits an {@link AccessToken}.
*/
public Mono<AccessToken> authenticate(TokenRequestContext request) {
return identityClient.authenticateToServiceFabricManagedIdentityEndpoint(identityEndpoint, identityHeader,
identityServerThumbprint, request);
return identityClient.authenticateWithManagedIdentityConfidentialClient(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ class VirtualMachineMsiCredential extends ManagedIdentityServiceCredential {
* @return A publisher that emits an {@link AccessToken}.
*/
public Mono<AccessToken> authenticate(TokenRequestContext request) {
return identityClient.authenticateToIMDSEndpoint(request);
return identityClient.authenticateWithManagedIdentityConfidentialClient(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,7 @@
import com.azure.identity.implementation.util.ScopeUtil;
import com.azure.identity.implementation.util.LoggingUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.microsoft.aad.msal4j.AuthorizationCodeParameters;
import com.microsoft.aad.msal4j.ClaimsRequest;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.DeviceCodeFlowParameters;
import com.microsoft.aad.msal4j.IAccount;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.IClientCredential;
import com.microsoft.aad.msal4j.InteractiveRequestParameters;
import com.microsoft.aad.msal4j.MsalInteractionRequiredException;
import com.microsoft.aad.msal4j.OnBehalfOfParameters;
import com.microsoft.aad.msal4j.Prompt;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.RefreshTokenParameters;
import com.microsoft.aad.msal4j.SilentParameters;
import com.microsoft.aad.msal4j.UserNamePasswordParameters;
import com.microsoft.aad.msal4j.*;
g2vinay marked this conversation as resolved.
Show resolved Hide resolved
import com.sun.jna.Platform;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
Expand Down Expand Up @@ -137,6 +121,7 @@ public class IdentityClient {
private HttpPipelineAdapter httpPipelineAdapter;
private final SynchronizedAccessor<PublicClientApplication> publicClientApplicationAccessor;
private final SynchronizedAccessor<ConfidentialClientApplication> confidentialClientApplicationAccessor;
private final SynchronizedAccessor<ConfidentialClientApplication> managedIdentityConfidentialClientApplicationAccessor;
private final SynchronizedAccessor<String> clientAssertionAccessor;


Expand Down Expand Up @@ -182,6 +167,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);
Expand Down Expand Up @@ -241,6 +229,26 @@ private Mono<ConfidentialClientApplication> getConfidentialClientApplication() {

applicationBuilder.sendX5c(options.isIncludeX5c());


if (!options.getManagedIdentityType().equals(ManagedIdentityType.NONE)) {
applicationBuilder.appTokenProvider(appTokenProviderParameters -> {
TokenRequestContext trc = new TokenRequestContext()
.setScopes(new ArrayList<>(appTokenProviderParameters.scopes))
.setClaims(appTokenProviderParameters.claims)
.setTenantId(appTokenProviderParameters.tenantId);

Mono<AccessToken> accessTokenMono = getTokenFromTargetManagedIdentity(options.getManagedIdentityType(), trc);

return accessTokenMono.toFuture().thenApply(accessToken -> {
TokenProviderResult result = new TokenProviderResult();
result.setAccessToken(accessToken.getToken());
result.setTenantId(trc.getTenantId());
result.setExpiresInSeconds(accessToken.getExpiresAt().toEpochSecond());
return result;
});
});
}

initializeHttpPipelineAdapter();
if (httpPipelineAdapter != null) {
applicationBuilder.httpClient(httpPipelineAdapter);
Expand Down Expand Up @@ -277,6 +285,74 @@ private Mono<ConfidentialClientApplication> getConfidentialClientApplication() {
});
}

private Mono<ConfidentialClientApplication> 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.
g2vinay marked this conversation as resolved.
Show resolved Hide resolved
IClientCredential credential = ClientCredentialFactory.createFromSecret("dummy-secret");
ConfidentialClientApplication.Builder applicationBuilder =
ConfidentialClientApplication.builder(clientId == null ? IdentityConstants.DEVELOPER_SINGLE_SIGN_ON_ID
: clientId, credential);
try {
applicationBuilder = applicationBuilder.authority(authorityUrl);
} catch (MalformedURLException e) {
return Mono.error(LOGGER.logExceptionAsWarning(new IllegalStateException(e)));
}

applicationBuilder.appTokenProvider(appTokenProviderParameters -> {
TokenRequestContext trc = new TokenRequestContext()
.setScopes(new ArrayList<>(appTokenProviderParameters.scopes))
.setClaims(appTokenProviderParameters.claims)
.setTenantId(appTokenProviderParameters.tenantId);

Mono<AccessToken> accessTokenMono = getTokenFromTargetManagedIdentity(options.getManagedIdentityType(), trc);
g2vinay marked this conversation as resolved.
Show resolved Hide resolved

return accessTokenMono.toFuture().thenApply(accessToken -> {
g2vinay marked this conversation as resolved.
Show resolved Hide resolved
TokenProviderResult result = new TokenProviderResult();
result.setAccessToken(accessToken.getToken());
result.setTenantId(trc.getTenantId());
result.setExpiresInSeconds(accessToken.getExpiresAt().toEpochSecond());
return result;
});
});


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);
});
}

private Mono<AccessToken> getTokenFromTargetManagedIdentity(ManagedIdentityType managedIdentityType, TokenRequestContext trc) {
g2vinay marked this conversation as resolved.
Show resolved Hide resolved
g2vinay marked this conversation as resolved.
Show resolved Hide resolved
ManagedIdentityParameters parameters = options.getManagedIdentityParameters();
switch (managedIdentityType) {
case APP_SERVICE:
return authenticateToManagedIdentityEndpoint(parameters.getIdentityEndpoint(),
parameters.getIdentityHeader(), parameters.getMsiEndpoint(), parameters.getMsiSecret(), trc);
case SERVICE_FABRIC:
return authenticateToServiceFabricManagedIdentityEndpoint(parameters.getIdentityEndpoint(),
parameters.getIdentityHeader(), parameters.getIdentityServerThumbprint(), trc);
case ARC:
return authenticateToArcManagedIdentityEndpoint(parameters.getIdentityEndpoint(), trc);
case AKS:
return authenticateWithExchangeToken(trc);
default:
return authenticateToIMDSEndpoint(trc);
}
}

private Mono<String> parseClientAssertion() {
return Mono.fromCallable(() -> {
if (clientAssertionFilePath != null) {
Expand Down Expand Up @@ -632,7 +708,6 @@ public Mono<AccessToken> authenticateWithOBO(TokenRequestContext request) {
.map(MsalToken::new));
}


private Mono<AccessToken> getAccessTokenFromPowerShell(TokenRequestContext request,
PowershellManager powershellManager) {
return powershellManager.initSession()
Expand Down Expand Up @@ -705,6 +780,18 @@ public Mono<AccessToken> authenticateWithConfidentialClient(TokenRequestContext
)).map(MsalToken::new);
}

public Mono<AccessToken> 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<HttpPipelinePolicy> policies = new ArrayList<>();
HttpLogOptions httpLogOptions = new HttpLogOptions();
Expand Down
Loading