diff --git a/authorization/msi-auth-token-provider-jar/pom.xml b/authorization/msi-auth-token-provider-jar/pom.xml new file mode 100644 index 000000000000..7737c0d60842 --- /dev/null +++ b/authorization/msi-auth-token-provider-jar/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + com.microsoft.azure.msi_auth_token_provider + + azure-authentication-msi-token-provider + jar + 1.0.0-Beta-1 + + Azure Java Client MSI Authorization Token Provoider Library + This package contains the MSI token provider classes for Azure. + https://github.com/Azure/azure-sdk-for-java + + + + The MIT License (MIT) + http://opensource.org/licenses/MIT + repo + + + + + scm:git:https://github.com/Azure/azure-sdk-for-java + scm:git:git@github.com:Azure/azure-sdk-for-java.git + HEAD + + + + UTF-8 + + + + + + microsoft + Microsoft + + + + + + junit + junit + test + 4.12 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + true + true + + + + + + maven-assembly-plugin + + + package + + single + + + + + + jar-with-dependencies + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 8 + 8 + true + true + + true + true + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.8 + + *.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization;*.blob.core.search + + + /** +
* Copyright (c) Microsoft Corporation. All rights reserved. +
* Licensed under the MIT License. See License.txt in the project root for +
* license information. +
*/ + ]]> +
+
+
+
+
+
\ No newline at end of file diff --git a/authorization/msi-auth-token-provider-jar/readme.md b/authorization/msi-auth-token-provider-jar/readme.md new file mode 100644 index 000000000000..84893d844623 --- /dev/null +++ b/authorization/msi-auth-token-provider-jar/readme.md @@ -0,0 +1,68 @@ +# What is this? + +The "msi-auth-token-provider" jar is a library that enables : +* Azure VMs and container instances and +* Web Apps (funcitons included) +Retrieve authentication tokens for syatem/user assigned [managed identities](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview). + +This is a light weight library that does not have many dependencies. + +# Usage +## Dependency +Take a dependency on the jar in you pom file like follows +```xml + + + com.microsoft.azure.msi_auth_token_provider + azure-authentication-msi-token-provider + 1.0.0-beta + + +``` + +## Getting the token + +Add the folowing import statement to get in all the classes in the jar + +```java +import com.microsoft.azure.msiAuthTokenProvider.*; +``` + +### Getting a token for system assigned identity +Use the following code to get the auth token for System assigned identity : + +``` java +... + MSICredentials credsProvider = MSICredentials.getMSICredentials(); + MSIToken token = credsProvider.getToken(null); + String tokenValue = token.accessToken(); +... +``` + +### Getting a token for user assigned identity + +#### Using the client Id for the user assigned identity : +Use the following code to get the auth token for an User assigned identity : +```java +... + MSICredentials credsProvider = MSICredentials.getMSICredentials(); + credsProvider.updateClientId(clientId); + MSIToken token = credsProvider.getToken(null); + String tokenValue = token.accessToken(); +... +``` + +Where `clientId` is retrieved from the User Assigned Identity (This is currently only supported from within the portal). + +#### Using the object Id for the user assigned identity : +Use the following code to get the auth token for an User assigned identity : +```java +... + MSICredentials credsProvider = MSICredentials.getMSICredentials(); + credsProvider.updateObjectId(objectId); + MSIToken token = credsProvider.getToken(null); + String tokenValue = token.accessToken(); +... +``` + +Where `objectId` is retrieved from the User Assigned Identity (This is currently only supported from within the portal). \ No newline at end of file diff --git a/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/AzureMSICredentialException.java b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/AzureMSICredentialException.java new file mode 100644 index 000000000000..a25aa3c6cf2f --- /dev/null +++ b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/AzureMSICredentialException.java @@ -0,0 +1,15 @@ +package com.microsoft.azure.msiAuthTokenProvider; + +public class AzureMSICredentialException extends Exception{ + AzureMSICredentialException(String message) { + super(message); + } + + AzureMSICredentialException(String message, Throwable cause) { + super(message, cause); + } + + AzureMSICredentialException(Throwable cause) { + super(cause); + } +} diff --git a/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIConfigurationForAppService.java b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIConfigurationForAppService.java new file mode 100644 index 000000000000..2cddac281d2c --- /dev/null +++ b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIConfigurationForAppService.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ +package com.microsoft.azure.msiAuthTokenProvider; + +/** + * Defines the configuration to be used for retrieving access token from + * within an app-service with system assigned MSI enabled. + */ +public class MSIConfigurationForAppService { + private final String managementEndpoint; + private String resource; + private String msiEndpoint; + private String msiSecret; + private String clientId; + private String objectId; + + /** + * Creates MSIConfigurationForAppService. + * + * @param managementEndpoint azure management endpoint + */ + public MSIConfigurationForAppService(String managementEndpoint) { + this.managementEndpoint = managementEndpoint; + } + + /** + * Creates MSIConfigurationForAppService. + */ + public MSIConfigurationForAppService() { + this(MSICredentials.DEFAULT_AZURE_MANAGEMENT_ENDPOINT); + } + + /** + * @return the azure management Endpoint. + */ + public String managementEndpoint() { + return this.managementEndpoint; + } + + /** + * @return the audience identifying who will consume the token. + */ + public String resource() { + if (this.resource == null) { + this.resource = this.managementEndpoint; + } + return this.resource; + } + + /** + * @return the endpoint from which token needs to be retrieved. + */ + public String msiEndpoint() { + if (this.msiEndpoint == null) { + this.msiEndpoint = System.getenv("MSI_ENDPOINT"); + } + return this.msiEndpoint; + } + + /** + * @return the secret to use to retrieve the token. + */ + public String msiSecret() { + if (this.msiSecret == null) { + this.msiSecret = System.getenv("MSI_SECRET"); + } + return this.msiSecret; + } + + /** + * @return the object id + */ + public String msiObjectId() { + return this.objectId; + } + + /** + * @return the client id + */ + public String msiClientId() { + return this.clientId; + } + + /** + * Specifies the token audience. + * + * @param resource the audience of the token. + * + * @return MSIConfigurationForAppService + */ + public MSIConfigurationForAppService withResource(String resource) { + this.resource = resource; + return this; + } + + /** + * Specifies the endpoint from which token needs to retrieved. + * + * @param msiEndpoint the token endpoint. + * + * @return MSIConfigurationForAppService + */ + public MSIConfigurationForAppService withMsiEndpoint(String msiEndpoint) { + this.msiEndpoint = msiEndpoint; + return this; + } + + /** + * Specify the client Id (to be used or user assigned identities) + * @param clientId the client ID fot eh user assigned identity + * @return MSIConfigurationForAppService + */ + public MSIConfigurationForAppService withClientId(String clientId) { + this.clientId = clientId; + return this; + } + + /** + * Specify the object Id (to be used or user assigned identities) + * @param objectId the object ID fot eh user assigned identity + * @return MSIConfigurationForAppService + */ + public MSIConfigurationForAppService withObjectId(String objectId) { + this.objectId = objectId; + return this; + } + + /** + * Specifies secret to use to retrieve the token. + * + * @param msiSecret the secret. + * + * @return MSIConfigurationForAppService + */ + public MSIConfigurationForAppService withMsiSecret(String msiSecret) { + this.msiSecret = msiSecret; + return this; + } + + @Override + public MSIConfigurationForAppService clone() { + MSIConfigurationForAppService copy = new MSIConfigurationForAppService(this.managementEndpoint); + if (this.resource() != null) { + copy.withResource(this.resource()); + } + if (this.msiEndpoint() != null) { + copy.withMsiEndpoint(this.msiEndpoint()); + } + if (this.msiSecret() != null) { + copy.withMsiSecret(this.msiSecret()); + } + if (this.msiClientId() != null) { + copy.withClientId(this.msiClientId()); + } + if (this.msiObjectId() != null) { + copy.withObjectId(this.msiObjectId()); + } + return copy; + } +} \ No newline at end of file diff --git a/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIConfigurationForVirtualMachine.java b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIConfigurationForVirtualMachine.java new file mode 100644 index 000000000000..1a179b719497 --- /dev/null +++ b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIConfigurationForVirtualMachine.java @@ -0,0 +1,188 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ +package com.microsoft.azure.msiAuthTokenProvider; + +/** + * Defines the configuration to be used for retrieving access token from + * within a VM with user assigned or system assigned MSI enabled. + */ +public class MSIConfigurationForVirtualMachine { + private final String managementEndpoint; + private String resource; + private MSITokenSource tokenSource = MSITokenSource.IMDS_ENDPOINT; + private String objectId; + private String clientId; + private String identityId; + private int maxRetry = -1; + + /** + * Creates MSIConfigurationForVirtualMachine. + * + * @param managementEndpoint azure management endpoint + */ + public MSIConfigurationForVirtualMachine(String managementEndpoint) { + this.managementEndpoint = managementEndpoint; + } + + /** + * Creates MSIConfigurationForVirtualMachine. + */ + public MSIConfigurationForVirtualMachine() { + this(MSICredentials.DEFAULT_AZURE_MANAGEMENT_ENDPOINT); + } + + /** + * @return the azure management Endpoint. + */ + public String managementEndpoint() { + return this.managementEndpoint; + } + + /** + * @return the token retrieval source (either MSI extension running in VM or IMDS service). + */ + public MSITokenSource tokenSource() { + if (this.tokenSource == null) { + this.tokenSource = MSITokenSource.IMDS_ENDPOINT; + } + return this.tokenSource; + } + /** + * @return the audience identifying who will consume the token. + */ + public String resource() { + if (this.resource == null) { + this.resource = this.managementEndpoint; + } + return this.resource; + } + /** + * @return the principal id of user assigned or system assigned identity. + */ + public String objectId() { + return this.objectId; + } + /** + * @return the client id of user assigned or system assigned identity. + */ + public String clientId() { + return this.clientId; + } + /** + * @return the ARM resource id of the user assigned identity resource. + */ + public String identityId() { + return this.identityId; + } + + /** + * @return the maximum retries allowed. + */ + public int maxRetry() { + return this.maxRetry; + } + + /** + * Specifies the token retrieval source. + * + * @param tokenSource the source of token + * + * @return MSIConfigurationForVirtualMachine + */ + public MSIConfigurationForVirtualMachine withTokenSource(MSITokenSource tokenSource) { + this.tokenSource = tokenSource; + return this; + } + + /** + * Specifies the token audience. + * + * @param resource the audience of the token. + * + * @return MSIConfigurationForVirtualMachine + */ + public MSIConfigurationForVirtualMachine withResource(String resource) { + this.resource = resource; + return this; + } + + /** + * specifies the principal id of user assigned or system assigned identity. + * + * @param objectId the object (principal) id + * @return MSIConfigurationForVirtualMachine + */ + public MSIConfigurationForVirtualMachine withObjectId(String objectId) { + this.objectId = objectId; + return this; + } + + /** + * Specifies the client id of user assigned or system assigned identity. + * + * @param clientId the client id + * @return MSIConfigurationForVirtualMachine + */ + public MSIConfigurationForVirtualMachine withClientId(String clientId) { + this.clientId = clientId; + return this; + } + + /** + * Specifies the ARM resource id of the user assigned identity resource. + * + * @param identityId the identity ARM id + * @return MSIConfigurationForVirtualMachine + */ + public MSIConfigurationForVirtualMachine withIdentityId(String identityId) { + this.identityId = identityId; + return this; + } + + /** + * Specifies the the maximum retries allowed. + * + * @param maxRetry max retry count + * @return MSIConfigurationForVirtualMachine + */ + public MSIConfigurationForVirtualMachine withMaxRetry(int maxRetry) { + this.maxRetry = maxRetry; + return this; + } + + @Override + public MSIConfigurationForVirtualMachine clone() { + MSIConfigurationForVirtualMachine copy = new MSIConfigurationForVirtualMachine(this.managementEndpoint); + if (this.clientId() != null) { + copy.withClientId(this.clientId()); + } + if (this.identityId() != null) { + copy.withIdentityId(this.identityId()); + } + if (this.objectId() != null) { + copy.withObjectId(this.objectId()); + } + if (this.resource() != null) { + copy.withResource(this.resource()); + } + if (this.tokenSource() != null) { + copy.withTokenSource(this.tokenSource()); + } + copy.withMaxRetry(this.maxRetry()); + return copy; + } + + + /** + * The source of MSI token. + */ + public enum MSITokenSource { + /** + * Indicate that token should be retrieved from IMDS service. + */ + IMDS_ENDPOINT + } +} \ No newline at end of file diff --git a/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSICredentials.java b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSICredentials.java new file mode 100644 index 000000000000..eb32fe913e0e --- /dev/null +++ b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSICredentials.java @@ -0,0 +1,374 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +package com.microsoft.azure.msiAuthTokenProvider; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.URL; +import java.net.URLEncoder; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Managed Service Identity token based credentials for use with a REST Service Client. + */ +public final class MSICredentials{ + public static final String DEFAULT_AZURE_MANAGEMENT_ENDPOINT = "https://management.core.windows.net/"; + // + private final List retrySlots = new ArrayList<>(); + // + private final MSIConfigurationForVirtualMachine configForVM; + private final MSIConfigurationForAppService configForAppService; + private final HostType hostType; + private final int maxRetry; + private static final int MAX_RETRY_DEFAULT_LIMIT = 20; + + + class ActiveDirectoryAuthentication { + static final String AZURE_REST_MSI_URL = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01"; + static final String ACCESS_TOKEN_IDENTIFIER = "\"access_token\":\""; + static final String ACCESS_TOKEN_TYPE_IDENTIFIER = "\"token_type\":\""; + static final String ACCESS_TOKEN_EXPIRES_IN_IDENTIFIER = "\"expires_in\":\""; + static final String ACCESS_TOKEN_EXPIRES_ON_IDENTIFIER = "\"expires_on\":\""; + static final String ACCESS_TOKEN_EXPIRES_ON_DATE_FORMAT = "M/d/yyyy h:mm:ss a X"; + } + + /** + * This method checks if the env vars "MSI_ENDPOINT" and "MSI_SECRET" exist. If they do, we return the msi creds class for APP Svcs + * otherwise we return one for VM + * + * @return MSICredentials + */ + public static MSICredentials getMSICredentials() { + return getMSICredentials(DEFAULT_AZURE_MANAGEMENT_ENDPOINT); + } + + /** + * This method checks if the env vars "MSI_ENDPOINT" and "MSI_SECRET" exist. If they do, we return the msi creds class for APP Svcs + * otherwise we return one for VM + * + * @param managementEndpoint Management endpoint in Azure + * @return MSICredentials + */ + public static MSICredentials getMSICredentials(String managementEndpoint) { + //check if we are running in a web app + String websiteName = System.getenv("WEBSITE_SITE_NAME"); + + if (websiteName != null && !websiteName.isEmpty()) { + // We are in a web app... + MSIConfigurationForAppService config = new MSIConfigurationForAppService(managementEndpoint); + return forAppService(config); + } else { + //We are in a vm/container + MSIConfigurationForVirtualMachine config = new MSIConfigurationForVirtualMachine(managementEndpoint); + return forVirtualMachine(config); + } + } + + /** + * Creates MSICredentials for application running on MSI enabled virtual machine. + * + * @return MSICredentials + */ + public static MSICredentials forVirtualMachine() { + return new MSICredentials(new MSIConfigurationForVirtualMachine()); + } + + /** + * Creates MSICredentials for application running on MSI enabled virtual machine. + * + * @param config the configuration to be used for token request. + * @return MSICredentials + */ + public static MSICredentials forVirtualMachine(MSIConfigurationForVirtualMachine config) { + return new MSICredentials(config.clone()); + } + + /** + * Creates MSICredentials for application running on MSI enabled app service. + * + * @return MSICredentials + */ + public static MSICredentials forAppService() { + return new MSICredentials(new MSIConfigurationForAppService()); + } + + /** + * Creates MSICredentials for application running on MSI enabled app service. + * + * @param config the configuration to be used for token request. + * @return MSICredentials + */ + public static MSICredentials forAppService(MSIConfigurationForAppService config) { + return new MSICredentials(config.clone()); + } + + private MSICredentials(MSIConfigurationForVirtualMachine config) { + this.configForVM = config; + this.configForAppService = null; + this.hostType = HostType.VIRTUAL_MACHINE; + this.maxRetry = config.maxRetry() < 0 ? MAX_RETRY_DEFAULT_LIMIT : config.maxRetry(); + // Simplified variant of https://en.wikipedia.org/wiki/Exponential_backoff + for (int x = 0; x < this.maxRetry; x++) { + this.retrySlots.add(500 * ((2 << 1) - 1) / 1000); + } + } + + private MSICredentials(MSIConfigurationForAppService config) { + this.configForAppService = config; + this.configForVM = null; + this.hostType = HostType.APP_SERVICE; + this.maxRetry = -1; + } + + /** + * Updates the client Id for the associated config (vm or app) + * Specifying a null value will clear out the old value + * @param clientId + */ + public void updateClientId(String clientId) { + if (configForVM != null) { + configForVM.withClientId(clientId); + } else { + configForAppService.withClientId(clientId); + } + } + + /** + * Updates the object Id for the associated config (vm or app) + * Specifying a null value will clear out the old value + * @param objectId + */ + public void updateObjectId(String objectId) { + if (configForVM != null) { + configForVM.withObjectId(objectId); + } else { + configForAppService.withObjectId(objectId); + } + } + + public MSIToken getToken(String tokenAudience) throws IOException, AzureMSICredentialException{ + switch (hostType) { + case VIRTUAL_MACHINE: + return this.retrieveTokenFromIDMSWithRetry(tokenAudience == null ? this.configForVM.resource() : tokenAudience); + case APP_SERVICE: + return this.getTokenForAppService(tokenAudience); + default: + throw new IllegalArgumentException("unknown host type:" + hostType); + } + } + + private MSIToken getTokenForAppService(String tokenAudience) throws IOException, AzureMSICredentialException { + String urlString = null; + + if (this.configForAppService.msiEndpoint() == null || this.configForAppService.msiEndpoint().isEmpty()) { + //the web app does not have MSI set, return file not found + throw new FileNotFoundException("Managed identity not found/configured"); + } + + if (this.configForAppService.msiClientId() != null && !this.configForAppService.msiClientId().isEmpty()) { + urlString = String.format("%s?resource=%s&clientid=%s&api-version=2017-09-01", this.configForAppService.msiEndpoint(), + tokenAudience == null ? this.configForAppService.resource() : tokenAudience, + this.configForAppService.msiClientId()); + } else if (this.configForAppService.msiObjectId() != null && !this.configForAppService.msiObjectId().isEmpty()) { + urlString = String.format("%s?resource=%s&objectid=%s&api-version=2017-09-01", this.configForAppService.msiEndpoint(), + tokenAudience == null ? this.configForAppService.resource() : tokenAudience, + this.configForAppService.msiObjectId()); + } else { + urlString = String.format("%s?resource=%s&api-version=2017-09-01", this.configForAppService.msiEndpoint(), + tokenAudience == null ? this.configForAppService.resource() : tokenAudience); + } + URL url = new URL(urlString); + HttpURLConnection connection = null; + + try { + connection = (HttpURLConnection) url.openConnection(); + + connection.setRequestMethod("GET"); + connection.setRequestProperty("Secret", this.configForAppService.msiSecret()); + connection.setRequestProperty("Metadata", "true"); + + connection.connect(); + + InputStream stream = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"), 100); + String result = reader.readLine(); + + return getMsiTokenFromResult(result, HostType.APP_SERVICE); + } catch (IOException ioEx){ + if (ioEx.getMessage().contains("Server returned HTTP response code: 400 for URL")) { + throw new AzureMSICredentialException("Managed identity not found/configured", ioEx); + } else throw ioEx; + } catch (Exception e){ + if (e.getCause()!= null && e.getCause() instanceof SocketException && e.getCause().getMessage().contains("Permission denied: connect")) { + throw new AzureMSICredentialException("Managed identity not found/configured", e); + } else throw e; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + private MSIToken retrieveTokenFromIDMSWithRetry(String tokenAudience) throws AzureMSICredentialException, IOException { + StringBuilder payload = new StringBuilder(); + final int imdsUpgradeTimeInMs = 70 * 1000; + + // + try { + payload.append("api-version"); + payload.append("="); + payload.append(URLEncoder.encode("2018-02-01", "UTF-8")); + payload.append("&"); + payload.append("resource"); + payload.append("="); + payload.append(URLEncoder.encode(tokenAudience, "UTF-8")); + if (this.configForVM.objectId() != null) { + payload.append("&"); + payload.append("object_id"); + payload.append("="); + payload.append(URLEncoder.encode(this.configForVM.objectId(), "UTF-8")); + } else if (this.configForVM.clientId() != null) { + payload.append("&"); + payload.append("client_id"); + payload.append("="); + payload.append(URLEncoder.encode(this.configForVM.clientId(), "UTF-8")); + } else if (this.configForVM.identityId() != null) { + payload.append("&"); + payload.append("msi_res_id"); + payload.append("="); + payload.append(URLEncoder.encode(this.configForVM.identityId(), "UTF-8")); + } + } catch (IOException exception) { + throw new AzureMSICredentialException(exception); + } + + int retry = 1; + while (retry <= maxRetry) { + URL url = new URL(String.format("http://169.254.169.254/metadata/identity/oauth2/token?%s", payload.toString())); + // + HttpURLConnection connection = null; + // + try { + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Metadata", "true"); + connection.connect(); + InputStream stream = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"), 100); + String result = reader.readLine(); + return getMsiTokenFromResult(result, HostType.VIRTUAL_MACHINE); + } catch (Exception exception) { + int responseCode = connection.getResponseCode(); + if (responseCode == 410 || responseCode == 429 || responseCode == 404 || (responseCode >= 500 && responseCode <= 599)) { + int retryTimeoutInMs = retrySlots.get(new Random().nextInt(retry)); + // Error code 410 indicates IMDS upgrade is in progress, which can take up to 70s + // + retryTimeoutInMs = (responseCode == 410 && retryTimeoutInMs < imdsUpgradeTimeInMs) ? imdsUpgradeTimeInMs : retryTimeoutInMs; + retry++; + if (retry > maxRetry) { + break; + } else { + sleep(retryTimeoutInMs); + } + } else { + throw new AzureMSICredentialException("Couldn't acquire access token from IMDS, verify your objectId, clientId or msiResourceId", exception); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + // + if (retry > maxRetry) { + throw new AzureMSICredentialException(String.format("MSI: Failed to acquire tokens after retrying %s times", maxRetry)); + } + return null; + } + + private static MSIToken getMsiTokenFromResult(String result, HostType hostType) throws AzureMSICredentialException{ + try { + return new MSIToken(getTokenFromResult(result), getTokenTypeFromResult(result), getExpiryTimeFromResult(result, hostType)); + } catch (ParseException pe) { + throw new AzureMSICredentialException(pe.getMessage(), pe); + } + } + + private static String getTokenFromResult(String result) { + int startIndex_AT = result.indexOf(ActiveDirectoryAuthentication.ACCESS_TOKEN_IDENTIFIER) + + ActiveDirectoryAuthentication.ACCESS_TOKEN_IDENTIFIER.length(); + + return (result.substring(startIndex_AT, result.indexOf("\"", startIndex_AT + 1))); + } + + private static String getTokenTypeFromResult(String result) { + int startIndex_AT = result.indexOf(ActiveDirectoryAuthentication.ACCESS_TOKEN_TYPE_IDENTIFIER) + + ActiveDirectoryAuthentication.ACCESS_TOKEN_TYPE_IDENTIFIER.length(); + + return (result.substring(startIndex_AT, result.indexOf("\"", startIndex_AT + 1))); + } + + private int getIndexInString(String sourceText, String subString) throws ParseException { + int index = sourceText.indexOf(subString); + + if (index < 0) { + throw new ParseException("Text to search not found in source text", 0); + } + + return index; + } + + private static Date getExpiryTimeFromResult(String result, HostType hostType) throws ParseException { + Calendar cal = new Calendar.Builder().setInstant(new Date()).build(); + if (hostType == HostType.VIRTUAL_MACHINE) { + int startIndex_ATX = result + .indexOf(ActiveDirectoryAuthentication.ACCESS_TOKEN_EXPIRES_IN_IDENTIFIER) + + ActiveDirectoryAuthentication.ACCESS_TOKEN_EXPIRES_IN_IDENTIFIER.length(); + String accessTokenExpiry = result.substring(startIndex_ATX, + result.indexOf("\"", startIndex_ATX + 1)); + cal.add(Calendar.SECOND, Integer.parseInt(accessTokenExpiry)); + } else { + int startIndex_ATX = result + .indexOf(ActiveDirectoryAuthentication.ACCESS_TOKEN_EXPIRES_ON_IDENTIFIER) + + ActiveDirectoryAuthentication.ACCESS_TOKEN_EXPIRES_ON_IDENTIFIER.length(); + String accessTokenExpiry = result.substring(startIndex_ATX, + result.indexOf("\"", startIndex_ATX + 1)); + + DateFormat df = new SimpleDateFormat( + ActiveDirectoryAuthentication.ACCESS_TOKEN_EXPIRES_ON_DATE_FORMAT); + cal = new Calendar.Builder().setInstant(df.parse(accessTokenExpiry)).build(); + } + + return cal.getTime(); + } + + private static void sleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + /** + * The host in which application is running. + */ + private enum HostType { + /** + * indicate that host is an Azure virtual machine. + */ + VIRTUAL_MACHINE, + /** + * indicate that host is an Azure app-service instance. + */ + APP_SERVICE + } +} \ No newline at end of file diff --git a/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIToken.java b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIToken.java new file mode 100644 index 000000000000..3903f824d6f5 --- /dev/null +++ b/authorization/msi-auth-token-provider-jar/src/main/java/com/microsoft/azure/msiAuthTokenProvider/MSIToken.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +package com.microsoft.azure.msiAuthTokenProvider; + + +import java.util.Calendar; +import java.util.Date; + +/** + * Type representing response from the local MSI token provider. + */ +public class MSIToken { + private String tokenType; + + private String accessToken; + + private Date expiresOn; + + public MSIToken(String accessToken, String tokenType, Date expiresOn) { + this.accessToken = accessToken; + this.tokenType = tokenType; + this.expiresOn = expiresOn; + } + + public String accessToken() { + return accessToken; + } + + public String tokenType() { + return tokenType; + } + + public Date expiresOn() { + return expiresOn; + } + + public boolean isExpired() { + //get time now + Calendar today = Calendar.getInstance(); + today.set(Calendar.HOUR_OF_DAY, 0); + Date timeNow = today.getTime(); + + return (timeNow.after(expiresOn)); + } +} \ No newline at end of file