Skip to content

Commit e3e2b83

Browse files
authored
Add Azure Service Fabric MI Support. (Azure#16810)
1 parent f122f4c commit e3e2b83

File tree

8 files changed

+389
-61
lines changed

8 files changed

+389
-61
lines changed

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

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@
1515
* The Managed Service Identity credential for Azure App Service.
1616
*/
1717
@Immutable
18-
class AppServiceMsiCredential {
18+
class AppServiceMsiCredential extends ManagedIdentityServiceCredential {
1919
private final String identityEndpoint;
2020
private final String msiEndpoint;
2121
private final String msiSecret;
2222
private final String identityHeader;
23-
private final IdentityClient identityClient;
24-
private final String clientId;
2523
private final ClientLogger logger = new ClientLogger(AppServiceMsiCredential.class);
2624

2725
/**
@@ -31,38 +29,20 @@ class AppServiceMsiCredential {
3129
* @param identityClient The identity client to acquire a token with.
3230
*/
3331
AppServiceMsiCredential(String clientId, IdentityClient identityClient) {
32+
super(clientId, identityClient, "AZURE APP SERVICE MSI/IDENTITY ENDPOINT");
3433
Configuration configuration = Configuration.getGlobalConfiguration().clone();
3534
this.identityEndpoint = configuration.get(Configuration.PROPERTY_IDENTITY_ENDPOINT);
3635
this.identityHeader = configuration.get(Configuration.PROPERTY_IDENTITY_HEADER);
3736
this.msiEndpoint = configuration.get(Configuration.PROPERTY_MSI_ENDPOINT);
3837
this.msiSecret = configuration.get(Configuration.PROPERTY_MSI_SECRET);
39-
this.identityClient = identityClient;
40-
this.clientId = clientId;
4138
if (identityEndpoint != null) {
42-
validateEndpointProtocol(this.identityEndpoint, "Identity");
39+
validateEndpointProtocol(this.identityEndpoint, "Identity", logger);
4340
}
4441
if (msiEndpoint != null) {
45-
validateEndpointProtocol(this.msiEndpoint, "MSI");
42+
validateEndpointProtocol(this.msiEndpoint, "MSI", logger);
4643
}
4744
}
4845

49-
private void validateEndpointProtocol(String endpoint, String endpointName) {
50-
if (!(endpoint.startsWith("https") || endpoint.startsWith("http"))) {
51-
throw logger.logExceptionAsError(
52-
new IllegalArgumentException(
53-
String.format("%s endpoint should start with 'https' or 'http' scheme.", endpointName)));
54-
}
55-
}
56-
57-
/**
58-
* Gets the client ID of the user assigned or system assigned identity.
59-
*
60-
* @return The client ID of user assigned or system assigned identity.
61-
*/
62-
public String getClientId() {
63-
return this.clientId;
64-
}
65-
6646
/**
6747
* Gets an access token for a token request.
6848
*

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

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
*/
2121
@Immutable
2222
public final class ManagedIdentityCredential implements TokenCredential {
23-
private final AppServiceMsiCredential appServiceMSICredential;
24-
private final VirtualMachineMsiCredential virtualMachineMSICredential;
23+
private final ManagedIdentityServiceCredential managedIdentityServiceCredential;
2524
private final ClientLogger logger = new ClientLogger(ManagedIdentityCredential.class);
2625

26+
static final String PROPERTY_IDENTITY_SERVER_THUMBPRINT = "IDENTITY_SERVER_THUMBPRINT";
27+
2728

2829
/**
2930
* Creates an instance of the ManagedIdentityCredential.
@@ -36,14 +37,20 @@ public final class ManagedIdentityCredential implements TokenCredential {
3637
.identityClientOptions(identityClientOptions)
3738
.build();
3839
Configuration configuration = Configuration.getGlobalConfiguration().clone();
39-
if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT)
40-
|| (configuration.contains(Configuration.PROPERTY_IDENTITY_ENDPOINT)
41-
&& configuration.contains(Configuration.PROPERTY_IDENTITY_HEADER))) {
42-
appServiceMSICredential = new AppServiceMsiCredential(clientId, identityClient);
43-
virtualMachineMSICredential = null;
40+
if (configuration.contains(Configuration.PROPERTY_IDENTITY_ENDPOINT)) {
41+
if (configuration.contains(Configuration.PROPERTY_IDENTITY_HEADER)) {
42+
if (configuration.contains(PROPERTY_IDENTITY_SERVER_THUMBPRINT)) {
43+
managedIdentityServiceCredential = new ServiceFabricMsiCredential(clientId, identityClient);
44+
} else {
45+
managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, identityClient);
46+
}
47+
} else {
48+
managedIdentityServiceCredential = null;
49+
}
50+
} else if (configuration.contains(Configuration.PROPERTY_MSI_ENDPOINT)) {
51+
managedIdentityServiceCredential = new AppServiceMsiCredential(clientId, identityClient);
4452
} else {
45-
virtualMachineMSICredential = new VirtualMachineMsiCredential(clientId, identityClient);
46-
appServiceMSICredential = null;
53+
managedIdentityServiceCredential = new VirtualMachineMsiCredential(clientId, identityClient);
4754
}
4855
LoggingUtil.logAvailableEnvironmentVariables(logger, configuration);
4956
}
@@ -53,23 +60,22 @@ public final class ManagedIdentityCredential implements TokenCredential {
5360
* @return the client ID of user assigned or system assigned identity.
5461
*/
5562
public String getClientId() {
56-
return this.appServiceMSICredential != null
57-
? this.appServiceMSICredential.getClientId()
58-
: this.virtualMachineMSICredential.getClientId();
63+
return managedIdentityServiceCredential.getClientId();
5964
}
6065

6166
@Override
6267
public Mono<AccessToken> getToken(TokenRequestContext request) {
63-
Mono<AccessToken> accessTokenMono;
64-
if (appServiceMSICredential != null) {
65-
accessTokenMono = appServiceMSICredential.authenticate(request)
66-
.doOnSuccess((t -> logger.info("Azure Identity => Managed Identity environment: MSI_ENDPOINT")));
67-
} else {
68-
accessTokenMono = virtualMachineMSICredential.authenticate(request)
69-
.doOnSuccess((t -> logger.info("Azure Identity => Managed Identity environment: IMDS")));
68+
if (managedIdentityServiceCredential == null) {
69+
return Mono.error(logger.logExceptionAsError(
70+
new CredentialUnavailableException("ManagedIdentityCredential authentication unavailable. "
71+
+ "The Target Azure platform could not be determined from environment variables.")));
7072
}
71-
return accessTokenMono
72-
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
73-
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
73+
return managedIdentityServiceCredential.authenticate(request)
74+
.doOnSuccess((t -> logger.info(String.format("Azure Identity => Managed Identity environment: %s",
75+
managedIdentityServiceCredential.getEnvironment()))))
76+
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
77+
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
7478
}
7579
}
80+
81+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.identity;
5+
6+
import com.azure.core.credential.AccessToken;
7+
import com.azure.core.credential.TokenRequestContext;
8+
import com.azure.core.util.logging.ClientLogger;
9+
import com.azure.identity.implementation.IdentityClient;
10+
import reactor.core.publisher.Mono;
11+
12+
/**
13+
* The Managed Service Identity credential.
14+
*/
15+
abstract class ManagedIdentityServiceCredential {
16+
private final String clientId;
17+
private final String environment;
18+
final IdentityClient identityClient;
19+
20+
/**
21+
* Creates an instance of ManagedIdentityServiceCredential.
22+
* @param clientId the client id of user assigned or system assigned identity
23+
* @param identityClient the identity client to acquire a token with.
24+
* @param environment The service environment of the credential.
25+
*/
26+
ManagedIdentityServiceCredential(String clientId, IdentityClient identityClient, String environment) {
27+
this.identityClient = identityClient;
28+
this.clientId = clientId;
29+
this.environment = environment;
30+
}
31+
32+
/**
33+
* Gets an access token for a token request.
34+
*
35+
* @param request The details of the token request.
36+
* @return A publisher that emits an {@link AccessToken}.
37+
*/
38+
public abstract Mono<AccessToken> authenticate(TokenRequestContext request);
39+
40+
/**
41+
* @return the client ID of user assigned or system assigned identity.
42+
*/
43+
public String getClientId() {
44+
return clientId;
45+
}
46+
47+
/**
48+
* @return the environment of the Maanged Identity.
49+
*/
50+
public String getEnvironment() {
51+
return environment;
52+
}
53+
54+
void validateEndpointProtocol(String endpoint, String endpointName, ClientLogger logger) {
55+
if (!(endpoint.startsWith("https") || endpoint.startsWith("http"))) {
56+
throw logger.logExceptionAsError(
57+
new IllegalArgumentException(
58+
String.format("%s endpoint should start with 'https' or 'http' scheme.", endpointName)));
59+
}
60+
}
61+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.identity;
5+
6+
import com.azure.core.annotation.Immutable;
7+
import com.azure.core.credential.AccessToken;
8+
import com.azure.core.credential.TokenRequestContext;
9+
import com.azure.core.util.Configuration;
10+
import com.azure.core.util.logging.ClientLogger;
11+
import com.azure.identity.implementation.IdentityClient;
12+
import reactor.core.publisher.Mono;
13+
14+
/**
15+
* The Managed Service Identity credential for Azure Service Fabric.
16+
*/
17+
@Immutable
18+
class ServiceFabricMsiCredential extends ManagedIdentityServiceCredential {
19+
private final String identityEndpoint;
20+
private final String identityHeader;
21+
private final String identityServerThumbprint;
22+
private final IdentityClient identityClient;
23+
private final ClientLogger logger = new ClientLogger(ServiceFabricMsiCredential.class);
24+
25+
/**
26+
* Creates an instance of {@link ServiceFabricMsiCredential}.
27+
*
28+
* @param clientId The client ID of user assigned or system assigned identity.
29+
* @param identityClient The identity client to acquire a token with.
30+
*/
31+
ServiceFabricMsiCredential(String clientId, IdentityClient identityClient) {
32+
super(clientId, identityClient, "AZURE SERVICE FABRIC IMDS ENDPOINT");
33+
Configuration configuration = Configuration.getGlobalConfiguration().clone();
34+
this.identityEndpoint = configuration.get(Configuration.PROPERTY_IDENTITY_ENDPOINT);
35+
this.identityHeader = configuration.get(Configuration.PROPERTY_IDENTITY_HEADER);
36+
this.identityServerThumbprint = configuration
37+
.get(ManagedIdentityCredential.PROPERTY_IDENTITY_SERVER_THUMBPRINT);
38+
this.identityClient = identityClient;
39+
if (identityEndpoint != null) {
40+
validateEndpointProtocol(this.identityEndpoint, "Identity", logger);
41+
}
42+
}
43+
44+
/**
45+
* Gets an access token for a token request.
46+
*
47+
* @param request The details of the token request.
48+
* @return A publisher that emits an {@link AccessToken}.
49+
*/
50+
public Mono<AccessToken> authenticate(TokenRequestContext request) {
51+
return identityClient.authenticateToServiceFabricManagedIdentityEndpoint(identityEndpoint, identityHeader,
52+
identityServerThumbprint, request);
53+
}
54+
}

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

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,15 @@
1313
* The Managed Service Identity credential for Virtual Machines.
1414
*/
1515
@Immutable
16-
class VirtualMachineMsiCredential {
17-
18-
private final IdentityClient identityClient;
19-
private final String clientId;
16+
class VirtualMachineMsiCredential extends ManagedIdentityServiceCredential {
2017

2118
/**
2219
* Creates an instance of VirtualMachineMSICredential.
2320
* @param clientId the client id of user assigned or system assigned identity
2421
* @param identityClient the identity client to acquire a token with.
2522
*/
2623
VirtualMachineMsiCredential(String clientId, IdentityClient identityClient) {
27-
this.clientId = clientId;
28-
this.identityClient = identityClient;
29-
}
30-
31-
/**
32-
* @return the client ID of user assigned or system assigned identity.
33-
*/
34-
public String getClientId() {
35-
return this.clientId;
24+
super(clientId, identityClient, "AZURE VM IMDS ENDPOINT");
3625
}
3726

3827
/**

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.azure.identity.CredentialUnavailableException;
2424
import com.azure.identity.DeviceCodeInfo;
2525
import com.azure.identity.implementation.util.CertificateUtil;
26+
import com.azure.identity.implementation.util.IdentitySslUtil;
2627
import com.azure.identity.implementation.util.ScopeUtil;
2728
import com.fasterxml.jackson.databind.JsonNode;
2829
import com.microsoft.aad.msal4j.AuthorizationCodeParameters;
@@ -44,6 +45,7 @@
4445
import com.sun.jna.Platform;
4546
import reactor.core.publisher.Mono;
4647

48+
import javax.net.ssl.HttpsURLConnection;
4749
import java.io.BufferedReader;
4850
import java.io.ByteArrayOutputStream;
4951
import java.io.File;
@@ -118,6 +120,7 @@ public class IdentityClient {
118120
private static final String MSI_ENDPOINT_VERSION = "2017-09-01";
119121
private static final String ADFS_TENANT = "adfs";
120122
private static final String HTTP_LOCALHOST = "http://localhost";
123+
private static final String SERVICE_FABRIC_MANAGED_IDENTITY_API_VERSION = "2019-07-01-preview";
121124
private final ClientLogger logger = new ClientLogger(IdentityClient.class);
122125

123126
private final IdentityClientOptions options;
@@ -745,6 +748,67 @@ public Mono<MsalToken> authenticateWithSharedTokenCache(TokenRequestContext requ
745748
}));
746749
}
747750

751+
752+
/**
753+
* Asynchronously acquire a token from the App Service Managed Service Identity endpoint.
754+
*
755+
* @param identityEndpoint the Identity endpoint to acquire token from
756+
* @param identityHeader the identity header to acquire token with
757+
* @param request the details of the token request
758+
* @return a Publisher that emits an AccessToken
759+
*/
760+
public Mono<AccessToken> authenticateToServiceFabricManagedIdentityEndpoint(String identityEndpoint,
761+
String identityHeader,
762+
String thumbprint,
763+
TokenRequestContext request) {
764+
return Mono.fromCallable(() -> {
765+
766+
HttpsURLConnection connection = null;
767+
String endpoint = identityEndpoint;
768+
String headerValue = identityHeader;
769+
String endpointVersion = SERVICE_FABRIC_MANAGED_IDENTITY_API_VERSION;
770+
771+
String resource = ScopeUtil.scopesToResource(request.getScopes());
772+
StringBuilder payload = new StringBuilder();
773+
774+
payload.append("resource=");
775+
payload.append(URLEncoder.encode(resource, "UTF-8"));
776+
payload.append("&api-version=");
777+
payload.append(URLEncoder.encode(endpointVersion, "UTF-8"));
778+
if (clientId != null) {
779+
payload.append("&client_id=");
780+
payload.append(URLEncoder.encode(clientId, "UTF-8"));
781+
}
782+
783+
try {
784+
785+
URL url = new URL(String.format("%s?%s", endpoint, payload));
786+
connection = (HttpsURLConnection) url.openConnection();
787+
788+
IdentitySslUtil.addTrustedCertificateThumbprint(getClass().getSimpleName(), connection,
789+
thumbprint);
790+
connection.setRequestMethod("GET");
791+
if (headerValue != null) {
792+
connection.setRequestProperty("Secret", headerValue);
793+
}
794+
connection.setRequestProperty("Metadata", "true");
795+
796+
connection.connect();
797+
798+
Scanner s = new Scanner(connection.getInputStream(), StandardCharsets.UTF_8.name())
799+
.useDelimiter("\\A");
800+
801+
String result = s.hasNext() ? s.next() : "";
802+
return SERIALIZER_ADAPTER.deserialize(result, MSIToken.class, SerializerEncoding.JSON);
803+
804+
} finally {
805+
if (connection != null) {
806+
connection.disconnect();
807+
}
808+
}
809+
});
810+
}
811+
748812
/**
749813
* Asynchronously acquire a token from the App Service Managed Service Identity endpoint.
750814
*

0 commit comments

Comments
 (0)