From e173f3734d6a5149095e55313064fdeb54a6ff27 Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Mon, 20 Oct 2025 13:52:04 -0700 Subject: [PATCH 1/2] add workload identity support --- .../azure-security-keyvault-jca/CHANGELOG.md | 3 + .../implementation/utils/AccessTokenUtil.java | 112 +++++++++++++++++- 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md index 8cdd8472c5d4..0d074a1ee0c3 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md +++ b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md @@ -3,6 +3,9 @@ ## 2.11.0-beta.1 (Unreleased) ### Features Added +- Added support for Azure Workload Identity authentication for Azure Kubernetes Service (AKS) workloads. + - Automatically detects and uses federated token authentication when `AZURE_FEDERATED_TOKEN_FILE`, `AZURE_CLIENT_ID`, and `AZURE_TENANT_ID` environment variables are present. + - Provides credential-free authentication for AKS pods configured with Workload Identity-enabled service accounts. ### Breaking Changes diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java index e7895c39958b..503304949292 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AccessTokenUtil.java @@ -77,6 +77,11 @@ public final class AccessTokenUtil { private static final String PROPERTY_IDENTITY_ENDPOINT = "IDENTITY_ENDPOINT"; private static final String PROPERTY_IDENTITY_HEADER = "IDENTITY_HEADER"; + private static final String PROPERTY_AZURE_FEDERATED_TOKEN_FILE = "AZURE_FEDERATED_TOKEN_FILE"; + private static final String PROPERTY_AZURE_CLIENT_ID = "AZURE_CLIENT_ID"; + private static final String PROPERTY_AZURE_TENANT_ID = "AZURE_TENANT_ID"; + private static final String PROPERTY_AZURE_AUTHORITY_HOST = "AZURE_AUTHORITY_HOST"; + private static final String DEFAULT_AUTHORITY_HOST = "https://login.microsoftonline.com/"; /** * Get an access token for a managed identity. @@ -90,11 +95,14 @@ public static AccessToken getAccessToken(String resource, String identity) { AccessToken result; /* + * Azure Workload Identity (AKS): AZURE_FEDERATED_TOKEN_FILE, AZURE_CLIENT_ID, AZURE_TENANT_ID * App Service 2017-09-01: MSI_ENDPOINT, MSI_SECRET * Azure Container App 2019-08-01: IDENTITY_ENDPOINT, IDENTITY_HEADER, see more from https://learn.microsoft.com/en-us/azure/container-apps/managed-identity?tabs=cli%2Chttp#rest-endpoint-reference * Azure Virtual Machine 2018-02-01, see more from https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http */ - if (System.getenv("WEBSITE_SITE_NAME") != null && !System.getenv("WEBSITE_SITE_NAME").isEmpty()) { + if (isWorkloadIdentityAvailable()) { + result = getAccessTokenWithWorkloadIdentity(resource); + } else if (System.getenv("WEBSITE_SITE_NAME") != null && !System.getenv("WEBSITE_SITE_NAME").isEmpty()) { result = getAccessTokenOnAppService(resource, identity); } else if (System.getenv(PROPERTY_IDENTITY_ENDPOINT) != null && !System.getenv(PROPERTY_IDENTITY_ENDPOINT).isEmpty()) { @@ -168,6 +176,108 @@ public static AccessToken getAccessToken(String resource, String aadAuthenticati return result; } + /** + * Check if Azure Workload Identity environment is available. + * + * @return true if Workload Identity environment variables are present, false otherwise. + */ + private static boolean isWorkloadIdentityAvailable() { + return System.getenv(PROPERTY_AZURE_FEDERATED_TOKEN_FILE) != null + && !System.getenv(PROPERTY_AZURE_FEDERATED_TOKEN_FILE).isEmpty() + && System.getenv(PROPERTY_AZURE_CLIENT_ID) != null + && !System.getenv(PROPERTY_AZURE_CLIENT_ID).isEmpty() + && System.getenv(PROPERTY_AZURE_TENANT_ID) != null + && !System.getenv(PROPERTY_AZURE_TENANT_ID).isEmpty(); + } + + /** + * Get the access token using Azure Workload Identity (AKS federated token). + * + * @param resource The resource. + * @return The authorization token. + */ + private static AccessToken getAccessTokenWithWorkloadIdentity(String resource) { + LOGGER.entering("AccessTokenUtil", "getAccessTokenWithWorkloadIdentity", resource); + LOGGER.info("Getting access token using Azure Workload Identity (federated token)"); + + AccessToken result = null; + + try { + String tokenFilePath = System.getenv(PROPERTY_AZURE_FEDERATED_TOKEN_FILE); + String clientId = System.getenv(PROPERTY_AZURE_CLIENT_ID); + String tenantId = System.getenv(PROPERTY_AZURE_TENANT_ID); + String authorityHost = System.getenv(PROPERTY_AZURE_AUTHORITY_HOST); + + if (authorityHost == null || authorityHost.isEmpty()) { + authorityHost = DEFAULT_AUTHORITY_HOST; + } + + LOGGER.log(INFO, "Using Workload Identity with client ID: {0}", clientId); + LOGGER.log(INFO, "Using federated token file: {0}", tokenFilePath); + + // Read the federated token from the file + String federatedToken = readTokenFromFile(tokenFilePath); + + if (federatedToken == null || federatedToken.isEmpty()) { + LOGGER.log(WARNING, "Failed to read federated token from file: {0}", tokenFilePath); + return null; + } + + // Build the OAuth2 token endpoint URL + StringBuilder oauth2Url = new StringBuilder(); + oauth2Url.append(addTrailingSlashIfRequired(authorityHost)) + .append(tenantId) + .append("/oauth2/v2.0/token"); + + // Build the request body for client assertion flow + StringBuilder requestBody = new StringBuilder(); + requestBody.append("grant_type=client_credentials") + .append("&client_id=").append(clientId) + .append("&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer") + .append("&client_assertion=").append(federatedToken) + .append("&scope=").append(resource); + + // If resource doesn't end with /.default, append it + if (!resource.endsWith("/.default")) { + requestBody.append("/.default"); + } + + String body = HttpUtil.post(oauth2Url.toString(), requestBody.toString(), "application/x-www-form-urlencoded"); + + if (body != null) { + try { + result = JsonConverterUtil.fromJson(AccessToken::fromJson, body); + } catch (IOException e) { + LOGGER.log(WARNING, "Failed to parse access token response.", e); + } + } + } catch (IOException e) { + LOGGER.log(WARNING, "Failed to read federated token file for Workload Identity.", e); + } + + LOGGER.exiting("AccessTokenUtil", "getAccessTokenWithWorkloadIdentity", result); + + return result; + } + + /** + * Read the federated token from the specified file. + * + * @param tokenFilePath The path to the token file. + * @return The token content. + * @throws IOException If the file cannot be read. + */ + private static String readTokenFromFile(String tokenFilePath) throws IOException { + LOGGER.entering("AccessTokenUtil", "readTokenFromFile", tokenFilePath); + + java.nio.file.Path path = java.nio.file.Paths.get(tokenFilePath); + String token = new String(java.nio.file.Files.readAllBytes(path), java.nio.charset.StandardCharsets.UTF_8).trim(); + + LOGGER.exiting("AccessTokenUtil", "readTokenFromFile", "[token read successfully]"); + + return token; + } + /** * Get the access token on Azure App Service. * From 56b548534f6c6aacdaebb523fb80dca1a48ac09e Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Mon, 3 Nov 2025 23:03:56 -0800 Subject: [PATCH 2/2] update README --- .../azure-security-keyvault-jca/README.md | 68 ++++++++++++++++--- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/sdk/keyvault/azure-security-keyvault-jca/README.md b/sdk/keyvault/azure-security-keyvault-jca/README.md index b9dfaa71856c..1ea83eee6715 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/README.md +++ b/sdk/keyvault/azure-security-keyvault-jca/README.md @@ -77,13 +77,51 @@ The JCA library supports SSL/TLS and mTLS (Mutual TLS) to enhance security in se The JCA library provides support for Java Archive (JAR) signing, ensuring the integrity and authenticity of JAR files using certificates stored in Azure Key Vault. ## Examples + +### Authentication Methods +The JCA provider supports three authentication methods, which are automatically selected based on the configuration: + +#### 1. Service Principal (Client Credentials) +Use this method when you have explicit credentials (tenant ID, client ID, client secret): +```java +System.setProperty("azure.keyvault.uri", ""); +System.setProperty("azure.keyvault.tenant-id", ""); +System.setProperty("azure.keyvault.client-id", ""); +System.setProperty("azure.keyvault.client-secret", ""); +``` + +#### 2. Managed Identity +Use this method when running on Azure services (VMs, App Service, Container Apps) with Managed Identity enabled. **Only set the Key Vault URI**: +```java +// System-assigned managed identity +System.setProperty("azure.keyvault.uri", ""); + +// User-assigned managed identity (specify the object ID) +System.setProperty("azure.keyvault.uri", ""); +System.setProperty("azure.keyvault.managed-identity", ""); +``` + +#### 3. Workload Identity (AKS) +Use this method when running in Azure Kubernetes Service with Workload Identity enabled. **Only set the Key Vault URI** - the federated credentials are automatically detected from environment variables (`AZURE_FEDERATED_TOKEN_FILE`, `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`): +```java +System.setProperty("azure.keyvault.uri", ""); +``` + +**Authentication Selection Logic:** +- If `tenant-id`, `client-id`, and `client-secret` are set → Service Principal authentication +- If only `azure.keyvault.uri` is set → Automatic detection: + - **Workload Identity** (if `AZURE_FEDERATED_TOKEN_FILE` environment variable exists) + - **Managed Identity** on App Service (if `MSI_ENDPOINT` environment variable exists) + - **Managed Identity** on Container Apps/AKS (if `IDENTITY_ENDPOINT` environment variable exists) + - **Managed Identity** on VM (via IMDS endpoint at 169.254.169.254) + ### Exposed Options The JCA library supports configuring the following options: -* `azure.keyvault.uri`: The Azure Key Vault endpoint to retrieve certificates. -* `azure.keyvault.tenant-id`: The Microsoft Entra ID tenant ID required for authentication. -* `azure.keyvault.client-id`: The client/application ID used for authentication. -* `azure.keyvault.client-secret`: The client secret for authentication when using client credentials. -* `azure.keyvault.managed-identity`: Indicates whether Managed Identity authentication is enabled. +* `azure.keyvault.uri`: **(Required)** The Azure Key Vault endpoint to retrieve certificates. +* `azure.keyvault.tenant-id`: The Microsoft Entra ID tenant ID (required for Service Principal authentication). +* `azure.keyvault.client-id`: The client/application ID (required for Service Principal authentication). +* `azure.keyvault.client-secret`: The client secret (required for Service Principal authentication). +* `azure.keyvault.managed-identity`: The user-assigned managed identity object ID (optional, for user-assigned managed identity). * `azure.cert-path.well-known`: The path where the well-known certificate is stored. * `azure.cert-path.custom`: The path where the custom certificate is stored. * `azure.keyvault.jca.refresh-certificates-when-have-un-trust-certificate`: Indicates whether to refresh certificates when have untrusted certificate. @@ -139,7 +177,10 @@ while (true) { } ``` -Note if you want to use Azure Managed Identity, you should set the value of `azure.keyvault.uri`, and the rest of the parameters would be `null`. +**Note:** +- **For Service Principal authentication**: Set all four properties (`uri`, `tenant-id`, `client-id`, `client-secret`) +- **For Managed Identity authentication**: Only set `azure.keyvault.uri` (and optionally `managed-identity` for user-assigned identity) +- **For Workload Identity authentication (AKS)**: Only set `azure.keyvault.uri` - the provider automatically detects Workload Identity environment variables #### Client side SSL If you are looking to integrate the JCA provider for client side socket connections, see the Apache HTTP client example below. @@ -187,7 +228,10 @@ try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(mana System.out.println(result); ``` -Note if you want to use Azure managed identity, you should set the value of `azure.keyvault.uri`, and the rest of the parameters would be `null`. +**Note:** +- **For Service Principal authentication**: Set all four properties (`uri`, `tenant-id`, `client-id`, `client-secret`) +- **For Managed Identity authentication**: Only set `azure.keyvault.uri` (and optionally `managed-identity` for user-assigned identity) +- **For Workload Identity authentication (AKS)**: Only set `azure.keyvault.uri` - the provider automatically detects Workload Identity environment variables ### mTLS #### Server side mTLS @@ -237,7 +281,10 @@ while (true) { } ``` -Note if you want to use Azure Managed Identity, you should set the value of `azure.keyvault.uri`, and the rest of the parameters would be `null`. +**Note:** +- **For Service Principal authentication**: Set all four properties (`uri`, `tenant-id`, `client-id`, `client-secret`) +- **For Managed Identity authentication**: Only set `azure.keyvault.uri` (and optionally `managed-identity` for user-assigned identity) +- **For Workload Identity authentication (AKS)**: Only set `azure.keyvault.uri` - the provider automatically detects Workload Identity environment variables #### Client side mTLS If you are looking to integrate the JCA provider for client side socket connections, see the Apache HTTP client example below. @@ -291,7 +338,10 @@ try (CloseableHttpClient client = HttpClients.custom().setConnectionManager(mana System.out.println(result); ``` -Note if you want to use Azure managed identity, you should set the value of `azure.keyvault.uri`, and the rest of the parameters would be `null`. +**Note:** +- **For Service Principal authentication**: Set all four properties (`uri`, `tenant-id`, `client-id`, `client-secret`) +- **For Managed Identity authentication**: Only set `azure.keyvault.uri` (and optionally `managed-identity` for user-assigned identity) +- **For Workload Identity authentication (AKS)**: Only set `azure.keyvault.uri` - the provider automatically detects Workload Identity environment variables ### Jarsigner You can use the JCA provider to sign JAR files using certificates stored in Azure Key Vault by the following commands: