diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt
index 73d4dac8e44f..17e06411afa7 100644
--- a/eng/versioning/version_client.txt
+++ b/eng/versioning/version_client.txt
@@ -134,9 +134,9 @@ com.azure:azure-health-deidentification;1.0.0;1.1.0-beta.2
com.azure:azure-health-insights-clinicalmatching;1.0.0-beta.1;1.0.0-beta.2
com.azure:azure-health-insights-cancerprofiling;1.0.0-beta.1;1.0.0-beta.2
com.azure:azure-health-insights-radiologyinsights;1.1.5;1.2.0-beta.1
-com.azure:azure-identity;1.18.1;1.19.0-beta.2
+com.azure:azure-identity;1.18.1;1.18.2
com.azure:azure-identity-extensions;1.2.6;1.3.0-beta.1
-com.azure:azure-identity-broker;1.1.18;1.2.0-beta.1
+com.azure:azure-identity-broker;1.1.18;1.1.19
com.azure:azure-identity-broker-samples;1.0.0-beta.1;1.0.0-beta.1
com.azure:azure-identity-perf;1.0.0-beta.1;1.0.0-beta.1
com.azure:azure-iot-deviceupdate;1.0.31;1.1.0-beta.1
diff --git a/sdk/e2e/pom.xml b/sdk/e2e/pom.xml
index 160b37d81062..5f67e8f09465 100644
--- a/sdk/e2e/pom.xml
+++ b/sdk/e2e/pom.xml
@@ -39,7 +39,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
com.azure
diff --git a/sdk/identity/azure-identity-broker-samples/pom.xml b/sdk/identity/azure-identity-broker-samples/pom.xml
index a10f87f1ec9b..e1c3decd07bd 100644
--- a/sdk/identity/azure-identity-broker-samples/pom.xml
+++ b/sdk/identity/azure-identity-broker-samples/pom.xml
@@ -34,12 +34,12 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
com.azure
azure-identity-broker
- 1.2.0-beta.1
+ 1.1.19
org.openjfx
diff --git a/sdk/identity/azure-identity-broker/CHANGELOG.md b/sdk/identity/azure-identity-broker/CHANGELOG.md
index e3de2b6bc2b6..7a9fc9feeb35 100644
--- a/sdk/identity/azure-identity-broker/CHANGELOG.md
+++ b/sdk/identity/azure-identity-broker/CHANGELOG.md
@@ -1,14 +1,12 @@
# Release History
-## 1.2.0-beta.1 (Unreleased)
+## 1.1.19 (2026-01-20)
-### Features Added
-
-### Breaking Changes
+### Other Changes
-### Bugs Fixed
+#### Dependency Updates
-### Other Changes
+- Upgraded `azure-identity` from `1.18.1` to version `1.18.2`.
## 1.1.18 (2025-10-13)
diff --git a/sdk/identity/azure-identity-broker/pom.xml b/sdk/identity/azure-identity-broker/pom.xml
index 8cd360d58ad6..9772ac8c4fa5 100644
--- a/sdk/identity/azure-identity-broker/pom.xml
+++ b/sdk/identity/azure-identity-broker/pom.xml
@@ -7,7 +7,7 @@
com.azure
azure-identity-broker
- 1.2.0-beta.1
+ 1.1.19
Microsoft Azure Identity Brokered Authentication Library
This module contains brokered authentication extensions for Microsoft Azure Identity.
@@ -36,7 +36,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
diff --git a/sdk/identity/azure-identity-perf/pom.xml b/sdk/identity/azure-identity-perf/pom.xml
index efc51614d446..6b5fde77af28 100644
--- a/sdk/identity/azure-identity-perf/pom.xml
+++ b/sdk/identity/azure-identity-perf/pom.xml
@@ -25,7 +25,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
com.azure
diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md
index 645c58d43caf..223835f88472 100644
--- a/sdk/identity/azure-identity/CHANGELOG.md
+++ b/sdk/identity/azure-identity/CHANGELOG.md
@@ -1,23 +1,16 @@
# Release History
-## 1.19.0-beta.2 (Unreleased)
-
-### Features Added
-
-### Breaking Changes
-
-### Bugs Fixed
+## 1.18.2 (2026-01-20)
### Other Changes
-- Removed unused jetty, redisson, and lettuce-core dependencies.
-## 1.19.0-beta.1 (2025-11-14)
+- Removed unused jetty, redisson, and lettuce-core dependencies.
-### Features Added
-- Added `enableAzureTokenProxy()` method to `WorkloadIdentityCredentialBuilder` to enable custom token proxy support for Azure Kubernetes clusters. When enabled, the credential attempts to use a custom token proxy configured through environment variables (`AZURE_KUBERNETES_TOKEN_PROXY`, `AZURE_KUBERNETES_CA_FILE`, `AZURE_KUBERNETES_CA_DATA`, `AZURE_KUBERNETES_SNI_NAME`).
+#### Dependency Updates
-### Other Changes
-- Ported the authentication flow of WorkloadIdentityCredential to use Msal4j.
+- Upgraded `azure-core` from `1.57.0` to version `1.57.1`.
+- Upgraded `azure-core-http-netty` from `1.16.2` to version `1.16.3`.
+- Upgraded `azure-json` from `1.5.0` to version `1.5.1`.
## 1.18.1 (2025-10-13)
diff --git a/sdk/identity/azure-identity/pom.xml b/sdk/identity/azure-identity/pom.xml
index a8f1c6a65b1f..8e60b6db447a 100644
--- a/sdk/identity/azure-identity/pom.xml
+++ b/sdk/identity/azure-identity/pom.xml
@@ -6,7 +6,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
Microsoft Azure client library for Identity
This module contains client library for Microsoft Azure Identity.
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredentialBuilder.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredentialBuilder.java
index a19bd61d66ae..95f16fd9628d 100644
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredentialBuilder.java
+++ b/sdk/identity/azure-identity/src/main/java/com/azure/identity/WorkloadIdentityCredentialBuilder.java
@@ -6,9 +6,6 @@
import com.azure.core.util.Configuration;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
-import com.azure.identity.implementation.customtokenproxy.CustomTokenProxyConfiguration;
-import com.azure.identity.implementation.customtokenproxy.CustomTokenProxyHttpClient;
-import com.azure.identity.implementation.customtokenproxy.ProxyConfig;
import com.azure.identity.implementation.util.ValidationUtil;
import static com.azure.identity.ManagedIdentityCredential.AZURE_FEDERATED_TOKEN_FILE;
@@ -50,7 +47,6 @@
public class WorkloadIdentityCredentialBuilder extends AadCredentialBuilderBase {
private static final ClientLogger LOGGER = new ClientLogger(WorkloadIdentityCredentialBuilder.class);
private String tokenFilePath;
- private boolean enableTokenProxy;
/**
* Creates an instance of a WorkloadIdentityCredentialBuilder.
@@ -70,19 +66,6 @@ public WorkloadIdentityCredentialBuilder tokenFilePath(String tokenFilePath) {
return this;
}
- /**
- * Enables the custom token proxy feature for clusters running in Azure.
- * When enabled, the credential will attempt to use a custom token proxy configured through
- * environment variables (AZURE_KUBERNETES_TOKEN_PROXY, AZURE_KUBERNETES_CA_FILE,
- * AZURE_KUBERNETES_CA_DATA, AZURE_KUBERNETES_SNI_NAME).
- *
- * @return An updated instance of this builder with Azure token proxy enabled.
- */
- public WorkloadIdentityCredentialBuilder enableAzureTokenProxy() {
- this.enableTokenProxy = true;
- return this;
- }
-
/**
* Creates new {@link WorkloadIdentityCredential} with the configured options set.
*
@@ -105,13 +88,6 @@ public WorkloadIdentityCredential build() {
ValidationUtil.validate(this.getClass().getSimpleName(), LOGGER, "Client ID", clientIdInput, "Tenant ID",
tenantIdInput, "Service Token File Path", federatedTokenFilePathInput);
- if (enableTokenProxy) {
- ProxyConfig proxyConfig = CustomTokenProxyConfiguration.parseAndValidate(configuration);
- if (proxyConfig != null) {
- identityClientOptions.setHttpClient(new CustomTokenProxyHttpClient(proxyConfig));
- }
- }
-
return new WorkloadIdentityCredential(tenantIdInput, clientIdInput, federatedTokenFilePathInput,
identityClientOptions.clone());
}
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/SniSslSocketFactory.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/SniSslSocketFactory.java
deleted file mode 100644
index fb7eff32c362..000000000000
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/SniSslSocketFactory.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.azure.identity.implementation;
-
-import com.azure.core.util.CoreUtils;
-import javax.net.ssl.SNIServerName;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import java.io.IOException;
-import java.net.Socket;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.net.InetAddress;
-
-public final class SniSslSocketFactory extends SSLSocketFactory {
- private final SSLSocketFactory sslSocketFactory;
- private final String sniName;
-
- public SniSslSocketFactory(SSLSocketFactory sslSocketFactory, String sniName) {
- this.sslSocketFactory = sslSocketFactory;
- this.sniName = sniName;
- }
-
- @Override
- public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
- Socket sslSocket = (SSLSocket) sslSocketFactory.createSocket(s, host, port, autoClose);
- configureSni(sslSocket);
- return sslSocket;
- }
-
- @Override
- public Socket createSocket(String host, int port) throws IOException {
- Socket sslSocket = sslSocketFactory.createSocket(host, port);
- configureSni(sslSocket);
- return sslSocket;
- }
-
- @Override
- public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException {
- Socket sslSocket = sslSocketFactory.createSocket(host, port, localAddress, localPort);
- configureSni(sslSocket);
- return sslSocket;
- }
-
- @Override
- public Socket createSocket(InetAddress host, int port) throws IOException {
- Socket sslSocket = sslSocketFactory.createSocket(host, port);
- configureSni(sslSocket);
- return sslSocket;
- }
-
- @Override
- public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
- throws IOException {
- Socket sslSocket = sslSocketFactory.createSocket(address, port, localAddress, localPort);
- configureSni(sslSocket);
- return sslSocket;
- }
-
- @Override
- public String[] getDefaultCipherSuites() {
- return sslSocketFactory.getDefaultCipherSuites();
- }
-
- @Override
- public String[] getSupportedCipherSuites() {
- return sslSocketFactory.getSupportedCipherSuites();
- }
-
- private void configureSni(Socket socket) {
- if (socket instanceof SSLSocket && !CoreUtils.isNullOrEmpty(sniName)) {
- SSLSocket sslSocket = (SSLSocket) socket;
- SSLParameters sslParameters = sslSocket.getSSLParameters();
- sslParameters.setServerNames(Collections.singletonList(new RawSniServerName(sniName)));
- sslSocket.setSSLParameters(sslParameters);
- }
- }
-
- private static final class RawSniServerName extends SNIServerName {
- RawSniServerName(String sniHost) {
- super(0, sniHost.getBytes(StandardCharsets.UTF_8));
- }
- }
-}
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyConfiguration.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyConfiguration.java
deleted file mode 100644
index 9c789aa758a6..000000000000
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyConfiguration.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.azure.identity.implementation.customtokenproxy;
-
-import com.azure.core.util.logging.ClientLogger;
-
-import java.net.URI;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.net.URISyntaxException;
-
-import com.azure.core.util.Configuration;
-import com.azure.core.util.CoreUtils;
-
-public final class CustomTokenProxyConfiguration {
-
- private static final ClientLogger LOGGER = new ClientLogger(CustomTokenProxyConfiguration.class);
-
- private static final String AZURE_KUBERNETES_TOKEN_PROXY = "AZURE_KUBERNETES_TOKEN_PROXY";
- private static final String AZURE_KUBERNETES_CA_FILE = "AZURE_KUBERNETES_CA_FILE";
- private static final String AZURE_KUBERNETES_CA_DATA = "AZURE_KUBERNETES_CA_DATA";
- private static final String AZURE_KUBERNETES_SNI_NAME = "AZURE_KUBERNETES_SNI_NAME";
-
- private CustomTokenProxyConfiguration() {
- }
-
- public static ProxyConfig parseAndValidate(Configuration configuration) {
- String tokenProxyUrl = configuration.get(AZURE_KUBERNETES_TOKEN_PROXY);
- String caFile = configuration.get(AZURE_KUBERNETES_CA_FILE);
- String caData = configuration.get(AZURE_KUBERNETES_CA_DATA);
- String sniName = configuration.get(AZURE_KUBERNETES_SNI_NAME);
-
- if (CoreUtils.isNullOrEmpty(tokenProxyUrl)) {
- if (!CoreUtils.isNullOrEmpty(sniName)
- || !CoreUtils.isNullOrEmpty(caFile)
- || !CoreUtils.isNullOrEmpty(caData)) {
- throw LOGGER.logExceptionAsError(new IllegalArgumentException(
- "AZURE_KUBERNETES_TOKEN_PROXY is not set but other custom endpoint-related environment variables are present"));
- }
- return null;
- }
-
- if (!CoreUtils.isNullOrEmpty(caFile) && !CoreUtils.isNullOrEmpty(caData)) {
- throw LOGGER.logExceptionAsError(new IllegalArgumentException(
- "Only one of AZURE_KUBERNETES_CA_FILE or AZURE_KUBERNETES_CA_DATA can be set."));
- }
-
- URL proxyUrl = validateProxyUrl(tokenProxyUrl);
-
- byte[] caCertBytes = null;
- if (!CoreUtils.isNullOrEmpty(caData)) {
- caCertBytes = caData.getBytes(StandardCharsets.UTF_8);
- }
-
- ProxyConfig config = new ProxyConfig(proxyUrl, sniName, caFile, caCertBytes);
- return config;
- }
-
- private static URL validateProxyUrl(String endpoint) {
- if (CoreUtils.isNullOrEmpty(endpoint)) {
- throw LOGGER.logExceptionAsError(new IllegalArgumentException("Proxy endpoint cannot be null or empty"));
- }
-
- try {
- URI tokenProxy = new URI(endpoint);
-
- if (!"https".equals(tokenProxy.getScheme())) {
- throw LOGGER.logExceptionAsError(new IllegalArgumentException(
- "Custom token endpoint must use https scheme, got: " + tokenProxy.getScheme()));
- }
-
- if (tokenProxy.getRawUserInfo() != null) {
- throw LOGGER.logExceptionAsError(
- new IllegalArgumentException("Custom token endpoint URL must not contain user info: " + endpoint));
- }
-
- if (tokenProxy.getRawQuery() != null) {
- throw LOGGER.logExceptionAsError(
- new IllegalArgumentException("Custom token endpoint URL must not contain a query: " + endpoint));
- }
-
- if (tokenProxy.getRawFragment() != null) {
- throw LOGGER.logExceptionAsError(
- new IllegalArgumentException("Custom token endpoint URL must not contain a fragment: " + endpoint));
- }
-
- if (tokenProxy.getRawPath() == null || tokenProxy.getRawPath().isEmpty()) {
- tokenProxy = new URI(tokenProxy.getScheme(), null, tokenProxy.getHost(), tokenProxy.getPort(), "/",
- null, null);
- }
-
- return tokenProxy.toURL();
-
- } catch (URISyntaxException | IllegalArgumentException e) {
- throw LOGGER.logExceptionAsError(new IllegalArgumentException("Failed to normalize proxy URL path", e));
- } catch (Exception e) {
- throw new RuntimeException("Unexpected error while validating proxy URL: " + endpoint, e);
- }
- }
-
-}
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyHttpClient.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyHttpClient.java
deleted file mode 100644
index ef2c7f0446a6..000000000000
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyHttpClient.java
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.azure.identity.implementation.customtokenproxy;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.KeyStore;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManagerFactory;
-
-import com.azure.core.http.HttpClient;
-import com.azure.core.http.HttpRequest;
-import com.azure.core.http.HttpResponse;
-import com.azure.core.util.BinaryData;
-import com.azure.core.util.Context;
-import com.azure.core.util.CoreUtils;
-import com.azure.core.util.logging.ClientLogger;
-import com.azure.identity.implementation.SniSslSocketFactory;
-
-import reactor.core.publisher.Mono;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-public class CustomTokenProxyHttpClient implements HttpClient {
-
- private static final ClientLogger LOGGER = new ClientLogger(CustomTokenProxyHttpClient.class);
-
- private final ProxyConfig proxyConfig;
- private volatile SSLContext cachedSSLContext;
- private volatile byte[] cachedFileContentHash;
- private volatile int cachedFileContentLength;
- private volatile SSLSocketFactory cachedSslSocketFactory;
- private final URL proxyUrl;
- private final String sniName;
- private final byte[] caData;
- private final String caFile;
-
- public CustomTokenProxyHttpClient(ProxyConfig proxyConfig) {
- this.proxyConfig = proxyConfig;
- this.proxyUrl = proxyConfig.getTokenProxyUrl();
- this.sniName = proxyConfig.getSniName();
- this.caData = proxyConfig.getCaData();
- this.caFile = proxyConfig.getCaFile();
- }
-
- @Override
- public Mono send(HttpRequest request) {
- return Mono.fromCallable(() -> sendSync(request, Context.NONE));
- }
-
- @Override
- public HttpResponse sendSync(HttpRequest request, Context context) {
- try {
- HttpURLConnection connection = createConnection(request);
- return new CustomTokenProxyHttpResponse(request, connection);
- } catch (IOException e) {
- throw LOGGER.logExceptionAsError(new RuntimeException("Failed to create connection to token proxy", e));
- }
- }
-
- private HttpURLConnection createConnection(HttpRequest request) throws IOException {
- URL updatedUrl = rewriteTokenRequestForProxy(request.getUrl());
- HttpsURLConnection connection = (HttpsURLConnection) updatedUrl.openConnection();
- try {
- SSLSocketFactory sslSocketFactory = getSSLSocketFactory();
- connection.setSSLSocketFactory(sslSocketFactory);
- if (!CoreUtils.isNullOrEmpty(sniName)) {
- connection.setHostnameVerifier(sniAwareVerifier(sniName, proxyUrl));
- }
- } catch (Exception e) {
- throw LOGGER.logExceptionAsError(new RuntimeException("Failed to set up SSL context for token proxy", e));
- }
-
- String method = request.getHttpMethod().toString();
- connection.setRequestMethod(method);
- connection.setInstanceFollowRedirects(false);
- connection.setConnectTimeout(10_000);
- connection.setReadTimeout(20_000);
- connection.setDoOutput(true);
-
- BinaryData bodyData = request.getBodyAsBinaryData();
-
- request.getHeaders().forEach(header -> {
- connection.addRequestProperty(header.getName(), header.getValue());
- });
-
- if (bodyData != null) {
- byte[] bytes = bodyData.toBytes();
- if (bytes != null && bytes.length > 0) {
- try (OutputStream os = connection.getOutputStream()) {
- os.write(bytes);
- os.flush();
- }
- }
- }
- return connection;
- }
-
- private URL rewriteTokenRequestForProxy(URL originalUrl) throws MalformedURLException {
- try {
- String originalPath = originalUrl.getPath();
- String originalQuery = originalUrl.getQuery();
-
- String tokenProxyBase = proxyUrl.toString();
- if (!tokenProxyBase.endsWith("/")) {
- tokenProxyBase += "/";
- }
-
- URI combined = URI.create(tokenProxyBase)
- .resolve(originalPath.startsWith("/") ? originalPath.substring(1) : originalPath);
-
- String combinedStr = combined.toString();
- if (originalQuery != null && !originalQuery.isEmpty()) {
- combinedStr += "?" + originalQuery;
- }
-
- return URI.create(combinedStr).toURL();
-
- } catch (Exception e) {
- throw LOGGER.logExceptionAsError(new RuntimeException("Failed to rewrite token request for proxy", e));
- }
- }
-
- private SSLSocketFactory getSSLSocketFactory() {
- SSLContext sslContext = getSSLContext();
- if (cachedSslSocketFactory == null) {
- synchronized (this) {
- if (cachedSslSocketFactory == null) {
- SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
- if (!CoreUtils.isNullOrEmpty(sniName)) {
- sslSocketFactory = new SniSslSocketFactory(sslSocketFactory, sniName);
- }
- cachedSslSocketFactory = sslSocketFactory;
- }
- }
- }
- return cachedSslSocketFactory;
- }
-
- private SSLContext getSSLContext() {
- try {
- // If no CA override provided, use default
- if (CoreUtils.isNullOrEmpty(caFile) && (caData == null || caData.length == 0)) {
- if (cachedSSLContext == null) {
- synchronized (this) {
- if (cachedSSLContext == null) {
- cachedSSLContext = SSLContext.getDefault();
- cachedSslSocketFactory = null;
- }
- }
- }
- return cachedSSLContext;
- }
-
- // If CA data provided, use it
- if (CoreUtils.isNullOrEmpty(caFile)) {
- if (cachedSSLContext == null) {
- synchronized (this) {
- if (cachedSSLContext == null) {
- cachedSSLContext = createSslContextFromBytes(caData);
- cachedSslSocketFactory = null;
- }
- }
- }
- return cachedSSLContext;
- }
-
- // If CA file provided, read it (and re-read if it changes)
- Path path = Paths.get(caFile);
- if (!Files.exists(path)) {
- throw LOGGER.logExceptionAsError(new RuntimeException("CA file not found: " + caFile));
- }
-
- byte[] currentContent = Files.readAllBytes(path);
- int currentLength = currentContent.length;
- byte[] currentHash = generateSHA256Hash(currentContent);
-
- synchronized (this) {
- if (currentLength == 0) {
- if (cachedSSLContext == null) {
- throw LOGGER.logExceptionAsError(new IllegalStateException("CA file " + caFile + " is empty"));
- }
- LOGGER.warning("CA file " + caFile + " is empty, using cached SSL context from previous load");
- return cachedSSLContext;
- }
-
- if (cachedSSLContext == null
- || currentLength != cachedFileContentLength
- || !Arrays.equals(currentHash, cachedFileContentHash)) {
- cachedSSLContext = createSslContextFromBytes(currentContent);
- cachedFileContentLength = currentLength;
- cachedFileContentHash = currentHash;
- cachedSslSocketFactory = null;
- }
- }
- return cachedSSLContext;
-
- } catch (Exception e) {
- throw LOGGER.logExceptionAsError(new RuntimeException("Failed to initialize SSLContext for proxy", e));
- }
- }
-
- // Create SSLContext from byte array containing PEM certificate data
- private SSLContext createSslContextFromBytes(byte[] certificateData) {
- try (InputStream inputStream = new ByteArrayInputStream(certificateData)) {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
-
- List certificates = new ArrayList<>();
- while (true) {
- try {
- X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
- certificates.add(cert);
- } catch (CertificateException e) {
- break;
- }
- }
-
- if (certificates.isEmpty()) {
- throw LOGGER.logExceptionAsError(new IllegalStateException("No valid certificates found"));
- }
- return createSslContext(certificates);
- } catch (Exception e) {
- throw LOGGER.logExceptionAsError(new RuntimeException("Failed to create SSLContext from bytes", e));
- }
- }
-
- private SSLContext createSslContext(List certificates) {
- try {
- KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
- keystore.load(null, null);
- int index = 1;
- for (X509Certificate caCert : certificates) {
- keystore.setCertificateEntry("ca-cert-" + index, caCert);
- index++;
- }
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- tmf.init(keystore);
-
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, tmf.getTrustManagers(), null);
- return context;
- } catch (Exception e) {
- throw LOGGER.logExceptionAsError(new RuntimeException("Failed to create SSLContext", e));
- }
- }
-
- private static HostnameVerifier sniAwareVerifier(String sniName, URL customProxyUrl) {
- return (urlHost, session) -> {
- String peerHost = session.getPeerHost();
- String proxyHost = customProxyUrl.getHost();
-
- if (peerHost.equalsIgnoreCase(proxyHost)) {
- if (!CoreUtils.isNullOrEmpty(sniName)) {
- try {
- Certificate[] certificates = session.getPeerCertificates();
- if (certificates.length > 0 && certificates[0] instanceof X509Certificate) {
- X509Certificate cert = (X509Certificate) certificates[0];
- return certificateContainsDnsName(cert, sniName);
- }
- return false;
- } catch (SSLPeerUnverifiedException e) {
- return false;
- }
- }
- return true;
- }
-
- return false;
- };
- }
-
- private static boolean certificateContainsDnsName(X509Certificate cert, String dnsName) {
- try {
- Collection> sanList = cert.getSubjectAlternativeNames();
- if (sanList != null) {
- for (List> san : sanList) {
- if (san.size() >= 2 && san.get(0).equals(2)) {
- String certDnsName = (String) san.get(1);
- if (certDnsName.equalsIgnoreCase(dnsName)) {
- return true;
- }
- }
- }
- }
- } catch (CertificateParsingException e) {
- return false;
- }
- return false;
- }
-
- private static byte[] generateSHA256Hash(byte[] data) {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] encodedHash = digest.digest(data);
- return encodedHash;
- } catch (NoSuchAlgorithmException e) {
- throw LOGGER.logExceptionAsError(new RuntimeException("SHA-256 algorithm not found", e));
- }
- }
-}
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyHttpResponse.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyHttpResponse.java
deleted file mode 100644
index f31b859985e1..000000000000
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/CustomTokenProxyHttpResponse.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.azure.identity.implementation.customtokenproxy;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Map;
-
-import com.azure.core.http.HttpHeaderName;
-import com.azure.core.http.HttpHeaders;
-import com.azure.core.http.HttpRequest;
-import com.azure.core.http.HttpResponse;
-import com.azure.core.util.logging.ClientLogger;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-public final class CustomTokenProxyHttpResponse extends HttpResponse {
-
- private static final ClientLogger LOGGER = new ClientLogger(CustomTokenProxyHttpResponse.class);
-
- // private final HttpRequest request;
- private final int statusCode;
- private final HttpHeaders headers;
- private final HttpURLConnection connection;
- private byte[] cachedResponseBodyBytes;
-
- public CustomTokenProxyHttpResponse(HttpRequest request, HttpURLConnection connection) {
- super(request);
- this.connection = connection;
- this.statusCode = extractStatusCode(connection);
- this.headers = extractHeaders(connection);
- }
-
- private HttpHeaders extractHeaders(HttpURLConnection connection) {
- HttpHeaders headers = new HttpHeaders();
- for (Map.Entry> entry : connection.getHeaderFields().entrySet()) {
- String headerName = entry.getKey();
- if (headerName != null) {
- for (String headerValue : entry.getValue()) {
- headers.add(HttpHeaderName.fromString(headerName), headerValue);
- }
- }
- }
- return headers;
- }
-
- private int extractStatusCode(HttpURLConnection connection) {
- try {
- return connection.getResponseCode();
- } catch (IOException e) {
- throw LOGGER
- .logExceptionAsError(new RuntimeException("Failed to get status code from token proxy response", e));
- }
- }
-
- @Override
- public int getStatusCode() {
- return statusCode;
- }
-
- @Override
- public String getHeaderValue(String name) {
- return headers.getValue(HttpHeaderName.fromString(name));
- }
-
- @Override
- public HttpHeaders getHeaders() {
- return headers;
- }
-
- @Override
- public Mono getBodyAsByteArray() {
- return Mono.fromCallable(() -> {
- if (cachedResponseBodyBytes != null) {
- return cachedResponseBodyBytes;
- }
-
- InputStream stream = getResponseStream();
- if (stream == null) {
- cachedResponseBodyBytes = new byte[0];
- return cachedResponseBodyBytes;
- }
-
- try (InputStream notNullStream = stream) {
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- int n;
- byte[] temp = new byte[4096];
- while ((n = notNullStream.read(temp)) != -1) {
- buffer.write(temp, 0, n);
- }
- cachedResponseBodyBytes = buffer.toByteArray();
- return cachedResponseBodyBytes;
- }
- });
- }
-
- @Override
- public Flux getBody() {
- return getBodyAsByteArray().flatMapMany(bytes -> Flux.just(ByteBuffer.wrap(bytes)));
- }
-
- @Override
- public Mono getBodyAsString() {
- return getBodyAsString(StandardCharsets.UTF_8);
- }
-
- @Override
- public Mono getBodyAsString(Charset charset) {
- return getBodyAsByteArray().map(bytes -> new String(bytes, charset));
- }
-
- @Override
- public void close() {
- connection.disconnect();
- }
-
- private InputStream getResponseStream() throws IOException {
- try {
- return connection.getInputStream();
- } catch (IOException e) {
- return connection.getErrorStream();
- }
- }
-
-}
diff --git a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/ProxyConfig.java b/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/ProxyConfig.java
deleted file mode 100644
index 171087bac6e9..000000000000
--- a/sdk/identity/azure-identity/src/main/java/com/azure/identity/implementation/customtokenproxy/ProxyConfig.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.azure.identity.implementation.customtokenproxy;
-
-import java.net.URL;
-
-public class ProxyConfig {
- private final URL tokenProxyUrl;
- private final String sniName;
- private final String caFile;
- private final byte[] caData;
-
- public ProxyConfig(URL tokenProxyUrl, String sniName, String caFile, byte[] caData) {
- this.tokenProxyUrl = tokenProxyUrl;
- this.sniName = sniName;
- this.caFile = caFile;
- this.caData = caData;
- }
-
- public URL getTokenProxyUrl() {
- return tokenProxyUrl;
- }
-
- public String getSniName() {
- return sniName;
- }
-
- public String getCaFile() {
- return caFile;
- }
-
- public byte[] getCaData() {
- return caData;
- }
-}
diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialIdentityBindingTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialIdentityBindingTest.java
deleted file mode 100644
index b5c4ac25672b..000000000000
--- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialIdentityBindingTest.java
+++ /dev/null
@@ -1,637 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package com.azure.identity;
-
-import com.azure.core.credential.AccessToken;
-import com.azure.core.credential.TokenRequestContext;
-import com.azure.core.test.utils.TestConfigurationSource;
-import com.azure.core.util.Configuration;
-import com.azure.identity.util.TestUtils;
-import io.netty.bootstrap.ServerBootstrap;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioServerSocketChannel;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpObjectAggregator;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpServerCodec;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-import io.netty.handler.ssl.SslHandler;
-import io.netty.handler.ssl.SslProvider;
-import io.netty.util.CharsetUtil;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-import reactor.test.StepVerifier;
-
-import javax.net.ssl.SSLEngine;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.KeyStore;
-import java.security.PrivateKey;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.time.OffsetDateTime;
-import java.util.Base64;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Collection;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * E2E test for WorkloadIdentityCredential with AKS Custom Token Proxy using Netty mock server.
- * The certificate is loaded from pre-generated certificate from resources, which was generated as follows-
- * keytool -genkeypair -alias test-cert -keyalg RSA -keysize 2048 -validity 3650 -keystore src/test/resources/test-aks-cert.p12 -storetype PKCS12
- * -storepass password -keypass password -dname "CN=AKS-Proxy-Test" -ext "SAN=DNS:test-aks-proxy.ests.aks"
- *
- */
-public class WorkloadIdentityCredentialIdentityBindingTest {
-
- private static final String AKS_SNI_NAME = "test-aks-proxy.ests.aks";
-
- private static final String TEST_CLIENT_ID = "test-client-id";
- private static final String TEST_TENANT_ID = "test-tenant-id";
- private static final String MOCK_ACCESS_TOKEN = "mock_access_token_from_aks_proxy";
- private static final String MISMATCHED_CERT_SAN = "wrong-hostname.example.com";
-
- @TempDir
- Path tempDir;
-
- private NioEventLoopGroup bossGroup;
- private NioEventLoopGroup workerGroup;
- private Channel serverChannel;
- private String serverBaseUrl;
- private Path tokenFilePath;
- private Path caCertFilePath;
- private String caCertPemData;
- private AtomicInteger tokenRequestCount;
- private AtomicInteger metadataRequestCount;
- private X509Certificate certificate;
-
- @BeforeEach
- public void setUp() throws Exception {
- tokenRequestCount = new AtomicInteger(0);
- metadataRequestCount = new AtomicInteger(0);
-
- KeyStore keyStore = KeyStore.getInstance("PKCS12");
- try (InputStream keyStoreStream = getClass().getResourceAsStream("/test-aks-cert.p12")) {
- if (keyStoreStream == null) {
- throw new IllegalStateException("Test certificate not found in resources: /test-aks-cert.p12");
- }
- keyStore.load(keyStoreStream, "password".toCharArray());
- }
-
- PrivateKey privateKey = (PrivateKey) keyStore.getKey("test-cert", "password".toCharArray());
- certificate = (X509Certificate) keyStore.getCertificate("test-cert");
-
- caCertPemData = toPemFormat(certificate);
-
- caCertFilePath = tempDir.resolve("ca-cert.pem");
- Files.write(caCertFilePath, caCertPemData.getBytes(StandardCharsets.UTF_8));
-
- tokenFilePath = tempDir.resolve("token.jwt");
- String mockJwt = createMockFederatedToken();
- Files.write(tokenFilePath, mockJwt.getBytes(StandardCharsets.UTF_8));
-
- startNettyHttpsServer(privateKey, certificate);
- }
-
- @AfterEach
- public void cleanup() {
- if (serverChannel != null) {
- serverChannel.close().syncUninterruptibly();
- }
- if (bossGroup != null) {
- bossGroup.shutdownGracefully();
- }
- if (workerGroup != null) {
- workerGroup.shutdownGracefully();
- }
- }
-
- @Test
- public void testAksProxyWithCaFile() throws CertificateParsingException {
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- List dnsNames = fetchCertificateSAN(certificate);
-
- assertTrue(dnsNames.contains("test-aks-proxy.ests.aks"),
- "Certificate should contain the SNI name 'test-aks-proxy.ests.aks'");
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- AccessToken token = credential.getTokenSync(request);
-
- assertNotNull(token, "Access token should not be null");
- assertEquals(MOCK_ACCESS_TOKEN, token.getToken(), "Token value should match mock response");
- assertTrue(token.getExpiresAt().isAfter(OffsetDateTime.now()), "Token should not be expired");
- assertEquals(1, tokenRequestCount.get(), "Server should have received exactly one token request");
- }
-
- @Test
- public void testAksProxyWithCaFileAsync() {
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- StepVerifier.create(credential.getToken(request))
- .expectNextMatches(token -> MOCK_ACCESS_TOKEN.equals(token.getToken())
- && token.getExpiresAt().isAfter(OffsetDateTime.now()))
- .verifyComplete();
-
- assertEquals(1, tokenRequestCount.get(), "Server should have received exactly one token request");
- }
-
- @Test
- public void testAksProxyWithCaData() {
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_DATA", caCertPemData)
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- AccessToken token = credential.getTokenSync(request);
-
- assertNotNull(token, "Access token should not be null");
- assertEquals(MOCK_ACCESS_TOKEN, token.getToken(), "Token value should match mock response");
- assertEquals(1, tokenRequestCount.get(), "Server should have received exactly one token request");
- }
-
- @Test
- public void testAksProxyWithInvalidTokenFile() {
- Path nonExistentTokenFile = tempDir.resolve("non-existent-token.jwt");
-
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", nonExistentTokenFile.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(nonExistentTokenFile.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- Exception exception = assertThrows(Exception.class, () -> credential.getTokenSync(request));
-
- assertNotNull(exception, "Should throw exception when token file doesn't exist");
- assertEquals(0, tokenRequestCount.get(), "No token request should reach the server with invalid token file");
- }
-
- @Test
- public void testAksProxyWithInvalidCaCertificate() throws Exception {
- String invalidCertData = "-----BEGIN CERTIFICATE-----\nINVALID_BASE64_DATA\n-----END CERTIFICATE-----";
- Path invalidCaCertFile = tempDir.resolve("invalid-ca.pem");
- Files.write(invalidCaCertFile, invalidCertData.getBytes(StandardCharsets.UTF_8));
-
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_FILE", invalidCaCertFile.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- Exception exception = assertThrows(Exception.class, () -> {
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- credential.getTokenSync(request);
- });
-
- assertNotNull(exception, "Should throw exception when CA certificate is invalid");
- assertEquals(0, tokenRequestCount.get(), "No token request should succeed with invalid CA certificate");
- }
-
- @Test
- public void testAksProxyWithHttpScheme() {
- String httpProxyUrl = serverBaseUrl.replace("https://", "http://");
-
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", httpProxyUrl)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- Exception exception = assertThrows(Exception.class, () -> {
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(httpProxyUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- credential.getTokenSync(request);
- });
-
- assertNotNull(exception, "Should throw exception when proxy URL uses HTTP instead of HTTPS");
- assertEquals(0, tokenRequestCount.get(), "No token request should be made with HTTP proxy URL");
- }
-
- @Test
- public void testAksProxyWithMalformedUrl() {
- String malformedUrl = "not-a-valid-url-at-all";
-
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", malformedUrl)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- Exception exception = assertThrows(Exception.class, () -> {
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(malformedUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- credential.getTokenSync(request);
- });
-
- assertNotNull(exception, "Should throw exception when proxy URL is malformed");
- assertEquals(0, tokenRequestCount.get(), "No token request should be made with malformed proxy URL");
- }
-
- @Test
- public void testAksProxyUnreachable() {
- String unreachableProxyUrl = "https://localhost:19999";
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", unreachableProxyUrl)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(unreachableProxyUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- Exception exception = assertThrows(Exception.class, () -> credential.getTokenSync(request));
-
- assertNotNull(exception, "Should throw exception when proxy server is unreachable");
- assertEquals(0, tokenRequestCount.get(), "No token request should succeed when proxy is unreachable");
- }
-
- @Test
- public void testAksProxyWithEmptyTokenFile() throws Exception {
- Path emptyTokenFile = tempDir.resolve("empty-token.jwt");
- Files.write(emptyTokenFile, new byte[0]);
-
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", emptyTokenFile.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(emptyTokenFile.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- Exception exception = assertThrows(Exception.class, () -> credential.getTokenSync(request));
-
- assertNotNull(exception, "Should throw exception when token file is empty");
- assertEquals(0, tokenRequestCount.get(), "No token request should be made with empty token file");
- }
-
- @Test
- public void testAksProxyWithUrlEncodedCharactersInPath() throws Exception {
- String encodedPath = "/api%2Fv1/token";
- String proxyUrlWithEncoding = serverBaseUrl + encodedPath;
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", proxyUrlWithEncoding)
- .put("AZURE_KUBERNETES_CA_FILE", caCertFilePath.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(proxyUrlWithEncoding)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- AccessToken token = credential.getTokenSync(request);
-
- assertNotNull(token, "Token should not be null");
- assertEquals(MOCK_ACCESS_TOKEN, token.getToken(), "Token value should match mock token");
- assertTrue(token.getExpiresAt().isAfter(OffsetDateTime.now()), "Token should not be expired");
- assertEquals(1, tokenRequestCount.get(), "Token request should be made once");
- }
-
- @Test
- public void testAksProxyWithCaFileButNoSni() throws Exception {
- KeyStore keyStore = KeyStore.getInstance("PKCS12");
- try (InputStream keyStoreStream = getClass().getResourceAsStream("/test-aks-cert-no-sni.p12")) {
- assertNotNull(keyStoreStream, "Mismatched certificate not found: /test-aks-cert-no-sni.p12");
- keyStore.load(keyStoreStream, "password".toCharArray());
- }
-
- PrivateKey privateKey = (PrivateKey) keyStore.getKey("test-cert", "password".toCharArray());
- X509Certificate certificate = (X509Certificate) keyStore.getCertificate("test-cert");
-
- String noSniCertPemData = toPemFormat(certificate);
- Path noSniCaCertFile = tempDir.resolve("ca-cert-no-sni.pem");
- Files.write(noSniCaCertFile, noSniCertPemData.getBytes(StandardCharsets.UTF_8));
-
- startNettyHttpsServer(privateKey, certificate);
-
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_FILE", noSniCaCertFile.toString())
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- AccessToken token = credential.getTokenSync(request);
-
- assertNotNull(token, "Access token should not be null");
- assertEquals(MOCK_ACCESS_TOKEN, token.getToken(), "Token value should match mock response");
- assertEquals(1, tokenRequestCount.get(), "Server should have received exactly one token request");
- }
-
- @Test
- public void testAksProxyWithMismatchedSniAndCertificate() throws Exception {
- KeyStore keyStore = KeyStore.getInstance("PKCS12");
- try (InputStream keyStoreStream = getClass().getResourceAsStream("/test-aks-cert-mismatch.p12")) {
- assertNotNull(keyStoreStream, "Mismatched certificate not found: /test-aks-cert-mismatch.p12");
- keyStore.load(keyStoreStream, "password".toCharArray());
- }
-
- PrivateKey privateKey = (PrivateKey) keyStore.getKey("test-cert", "password".toCharArray());
- X509Certificate certificate = (X509Certificate) keyStore.getCertificate("test-cert");
-
- List dnsNames = fetchCertificateSAN(certificate);
-
- assertTrue(dnsNames.contains(MISMATCHED_CERT_SAN),
- "Certificate should contain DNS name 'wrong-hostname.example.com'");
- assertFalse(dnsNames.contains(AKS_SNI_NAME),
- "Certificate should NOT contain the SNI name 'test-aks-proxy.ests.aks'");
-
- String mismatchCertPemData = toPemFormat(certificate);
- Path mismatchCaCertFile = tempDir.resolve("ca-cert-mismatch.pem");
- Files.write(mismatchCaCertFile, mismatchCertPemData.getBytes(StandardCharsets.UTF_8));
-
- startNettyHttpsServer(privateKey, certificate);
-
- Configuration configuration = TestUtils
- .createTestConfiguration(new TestConfigurationSource().put("AZURE_KUBERNETES_TOKEN_PROXY", serverBaseUrl)
- .put("AZURE_KUBERNETES_CA_FILE", mismatchCaCertFile.toString())
- .put("AZURE_KUBERNETES_SNI_NAME", AKS_SNI_NAME)
- .put(Configuration.PROPERTY_AZURE_CLIENT_ID, TEST_CLIENT_ID)
- .put(Configuration.PROPERTY_AZURE_TENANT_ID, TEST_TENANT_ID)
- .put("AZURE_FEDERATED_TOKEN_FILE", tokenFilePath.toString()));
-
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId(TEST_TENANT_ID)
- .clientId(TEST_CLIENT_ID)
- .tokenFilePath(tokenFilePath.toString())
- .configuration(configuration)
- .authorityHost(serverBaseUrl)
- .enableAzureTokenProxy()
- .disableInstanceDiscovery()
- .build();
-
- TokenRequestContext request = new TokenRequestContext().addScopes("https://management.azure.com/.default");
-
- Exception exception = assertThrows(Exception.class, () -> credential.getTokenSync(request));
-
- assertNotNull(exception, "Should throw exception when SNI doesn't match certificate SAN");
-
- String exceptionMessage = exception.toString().toLowerCase();
- assertTrue(exceptionMessage.contains("failed to create connection"),
- "Exception should be connection failure related, got: " + exception.getMessage());
-
- assertEquals(0, tokenRequestCount.get(), "No token request should succeed when SNI doesn't match certificate");
- }
-
- private List fetchCertificateSAN(X509Certificate certificate) throws CertificateParsingException {
- Collection> sanCollection = certificate.getSubjectAlternativeNames();
-
- List dnsNames = new ArrayList<>();
- for (List> san : sanCollection) {
- if (san.size() >= 2 && san.get(0).equals(2)) {
- dnsNames.add((String) san.get(1));
- }
- }
- return dnsNames;
- }
-
- private void startNettyHttpsServer(PrivateKey privateKey, X509Certificate certificate) throws Exception {
- SslContext sslContext
- = SslContextBuilder.forServer(privateKey, certificate).sslProvider(SslProvider.OPENSSL).build();
-
- bossGroup = new NioEventLoopGroup(1);
- workerGroup = new NioEventLoopGroup();
-
- ServerBootstrap bootstrap = new ServerBootstrap();
- bootstrap.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializer() {
- @Override
- protected void initChannel(SocketChannel ch) {
- ChannelPipeline pipeline = ch.pipeline();
-
- SslHandler sslHandler = sslContext.newHandler(ch.alloc());
-
- sslHandler.handshakeFuture().addListener(future -> {
- if (future.isSuccess()) {
- SSLEngine engine = sslHandler.engine();
- }
- });
-
- pipeline.addLast(sslHandler);
- pipeline.addLast(new HttpServerCodec());
- pipeline.addLast(new HttpObjectAggregator(65536));
- pipeline.addLast(new HttpRequestHandler());
- }
- });
-
- serverChannel = bootstrap.bind("localhost", 0).sync().channel();
- InetSocketAddress socketAddress = (InetSocketAddress) serverChannel.localAddress();
- int port = socketAddress.getPort();
- serverBaseUrl = "https://localhost:" + port;
- }
-
- private class HttpRequestHandler extends SimpleChannelInboundHandler {
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
- String uri = request.uri();
- String responseBody;
-
- if (uri.contains("/.well-known/openid-configuration")) {
- metadataRequestCount.incrementAndGet();
- String base = serverBaseUrl + "/" + TEST_TENANT_ID;
- responseBody = String
- .format("{%n" + " \"token_endpoint\": \"%s/oauth2/v2.0/token\",%n" + " \"issuer\": \"%s/v2.0\",%n"
- + " \"authorization_endpoint\": \"%s/oauth2/v2.0/authorize\"%n" + "}", base, base, base);
- } else {
- tokenRequestCount.incrementAndGet();
- responseBody = String.format(
- "{\"token_type\":\"Bearer\",\"expires_in\":3600,\"ext_expires_in\":3600,\"access_token\":\"%s\"}",
- MOCK_ACCESS_TOKEN);
- }
-
- ByteBuf content = Unpooled.copiedBuffer(responseBody, CharsetUtil.UTF_8);
- FullHttpResponse response
- = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
- response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
- response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
-
- ctx.writeAndFlush(response);
- }
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
- cause.printStackTrace();
- ctx.close();
- }
- }
-
- private String toPemFormat(X509Certificate certificate) throws Exception {
- Base64.Encoder encoder = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8));
- byte[] encoded = encoder.encode(certificate.getEncoded());
- return "-----BEGIN CERTIFICATE-----\n" + new String(encoded, StandardCharsets.UTF_8)
- + "\n-----END CERTIFICATE-----\n";
- }
-
- private String createMockFederatedToken() {
- String header = Base64.getUrlEncoder()
- .withoutPadding()
- .encodeToString("{\"alg\":\"RS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
-
- long exp = System.currentTimeMillis() / 1000 + 3600;
- String payload = Base64.getUrlEncoder()
- .withoutPadding()
- .encodeToString(String.format(
- "{\"aud\":\"api://AzureADTokenExchange\",\"exp\":%d,\"iss\":\"kubernetes.io/serviceaccount\",\"sub\":\"system:serviceaccount:default:workload-identity-sa\"}",
- exp).getBytes(StandardCharsets.UTF_8));
-
- String signature
- = Base64.getUrlEncoder().withoutPadding().encodeToString("mock-signature".getBytes(StandardCharsets.UTF_8));
-
- return header + "." + payload + "." + signature;
- }
-}
diff --git a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java b/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java
index 5e5d76948e8f..d2404d9b302b 100644
--- a/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java
+++ b/sdk/identity/azure-identity/src/test/java/com/azure/identity/WorkloadIdentityCredentialTest.java
@@ -35,23 +35,6 @@
public class WorkloadIdentityCredentialTest {
private static final String CLIENT_ID = UUID.randomUUID().toString();
- private static final String ENV_PROXY_URL = "AZURE_KUBERNETES_TOKEN_PROXY";
- private static final String ENV_CA_FILE = "AZURE_KUBERNETES_CA_FILE";
- private static final String ENV_CA_DATA = "AZURE_KUBERNETES_CA_DATA";
- private static final String ENV_SNI_NAME = "AZURE_KUBERNETES_SNI_NAME";
- private static final String CA_DATA
- = "-----BEGIN CERTIFICATE-----\n" + "MIICMzCCAZygAwIBAgIJALiPnVsvq8dsMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV\n"
- + "BAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNVBAcTA2ZvbzEMMAoGA1UEChMDZm9v\n"
- + "MQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2ZvbzAeFw0xMzAzMTkxNTQwMTlaFw0x\n"
- + "ODAzMTgxNTQwMTlaMFMxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNV\n"
- + "BAcTA2ZvbzEMMAoGA1UEChMDZm9vMQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2Zv\n"
- + "bzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzdGfxi9CNbMf1UUcvDQh7MYB\n"
- + "OveIHyc0E0KIbhjK5FkCBU4CiZrbfHagaW7ZEcN0tt3EvpbOMxxc/ZQU2WN/s/wP\n"
- + "xph0pSfsfFsTKM4RhTWD2v4fgk+xZiKd1p0+L4hTtpwnEw0uXRVd0ki6muwV5y/P\n"
- + "+5FHUeldq+pgTcgzuK8CAwEAAaMPMA0wCwYDVR0PBAQDAgLkMA0GCSqGSIb3DQEB\n"
- + "BQUAA4GBAJiDAAtY0mQQeuxWdzLRzXmjvdSuL9GoyT3BF/jSnpxz5/58dba8pWen\n"
- + "v3pj4P3w5DoOso0rzkZy2jEsEitlVM2mLSbQpMM+MUVQCQoiG6W9xuCFuxSrwPIS\n"
- + "pAqEAuV4DNoxQKKWmhVv+J0ptMWD25Pnpxeq5sXzghfJnslJlQND\n" + "-----END CERTIFICATE-----\n";
@Test
public void testWorkloadIdentityFlow(@TempDir Path tempDir) throws IOException {
@@ -209,340 +192,4 @@ public void testFileReadingError(@TempDir Path tempDir) {
assertTrue(error.getCause() instanceof IOException); // Original IOException from Files.readAllBytes
}).verify();
}
-
- @Test
- public void testProxyEnabledWithProxyUrlGetsToken(@TempDir Path tempDir) throws IOException {
- // setup
- String endpoint = "https://localhost";
- String token1 = "token1";
- String proxyUrl = "https://token-proxy.example.com";
-
- TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, proxyUrl));
-
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- try (MockedConstruction identityClientMock
- = mockConstruction(IdentityClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty());
- when(identityClient.authenticateWithConfidentialClient(any(TokenRequestContext.class)))
- .thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
- })) {
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId("dummy-tenantid")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
-
- StepVerifier.create(credential.getToken(request1))
- .expectNextMatches(token -> token1.equals(token.getToken())
- && expiresAt.getSecond() == token.getExpiresAt().getSecond())
- .verifyComplete();
-
- assertNotNull(identityClientMock);
- }
- }
-
- @Test
- public void testProxyEnabledWithoutProxyUrlGetsToken(@TempDir Path tempDir) throws IOException {
- // setup
- String endpoint = "https://localhost";
- String token1 = "token1";
- TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint));
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- try (MockedConstruction identityClientMock
- = mockConstruction(IdentityClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty());
- when(identityClient.authenticateWithConfidentialClient(any(TokenRequestContext.class)))
- .thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
- })) {
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId("dummy-tenantid")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
-
- StepVerifier.create(credential.getToken(request1))
- .expectNextMatches(token -> token1.equals(token.getToken())
- && expiresAt.getSecond() == token.getExpiresAt().getSecond())
- .verifyComplete();
-
- assertNotNull(identityClientMock);
- }
- }
-
- @Test
- public void testProxyEnabledInvalidProxyUrlSchemeFailure(@TempDir Path tempDir) throws IOException {
- String endpoint = "https://localhost";
-
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, "http://not-https.example.com"));
-
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- new WorkloadIdentityCredentialBuilder().tenantId("tenant")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
- });
- }
-
- @Test
- public void testProxyUrlWithQueryFailure(@TempDir Path tempDir) throws IOException {
- String endpoint = "https://login.microsoftonline.com";
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, "https://proxy.example.com?x=y"));
-
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- new WorkloadIdentityCredentialBuilder().tenantId("tenant")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
- });
- }
-
- @Test
- public void testProxyUrlWithFragmentFailure(@TempDir Path tempDir) throws IOException {
- String endpoint = "https://login.microsoftonline.com";
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, "https://proxy.example.com#frag"));
-
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- new WorkloadIdentityCredentialBuilder().tenantId("tenant")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
- });
- }
-
- @Test
- public void testProxyUrlWithUserInfoFailure(@TempDir Path tempDir) throws IOException {
- String endpoint = "https://login.microsoftonline.com";
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, "https://user:pass@proxy.example.com"));
-
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- new WorkloadIdentityCredentialBuilder().tenantId("tenant")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
- });
- }
-
- @Test
- public void testCaFileAndCaDataPresentFailure(@TempDir Path tempDir) throws IOException {
- String endpoint = "https://login.microsoftonline.com";
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- Path caFile = tempDir.resolve("ca.crt");
- Files.write(caFile,
- "-----BEGIN CERTIFICATE-----\nMIIB...==\n-----END CERTIFICATE-----\n".getBytes(StandardCharsets.UTF_8));
-
- String caData = "-----BEGIN CERTIFICATE-----\nMIIB...==\n-----END CERTIFICATE-----";
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, "https://proxy.example.com")
- .put(ENV_CA_FILE, caFile.toString())
- .put(ENV_CA_DATA, caData));
-
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- new WorkloadIdentityCredentialBuilder().tenantId("tenant")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
- });
- }
-
- @Test
- public void testProxyEnabledWithProxyUrlGetsTokenSync(@TempDir Path tempDir) throws IOException {
- // setup
- String endpoint = "https://localhost";
- String token1 = "token1";
- String proxyUrl = "https://token-proxy.example.com";
-
- TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, proxyUrl));
-
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- try (MockedConstruction identitySyncClientMock
- = mockConstruction(IdentitySyncClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithConfidentialClientCache(any()))
- .thenThrow(new IllegalStateException("Test"));
- when(identityClient.authenticateWithConfidentialClient(any(TokenRequestContext.class)))
- .thenReturn(TestUtils.getMockAccessTokenSync(token1, expiresAt));
- })) {
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId("dummy-tenantid")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
-
- AccessToken token = credential.getTokenSync(request1);
-
- assertTrue(token1.equals(token.getToken()));
- assertTrue(expiresAt.getSecond() == token.getExpiresAt().getSecond());
- assertNotNull(identitySyncClientMock);
- }
- }
-
- @Test
- public void testProxyUrlWithCaDataAcquiresToken(@TempDir Path tempDir) throws IOException {
- String endpoint = "https://login.microsoftonline.com";
- String token1 = "token-ca-data";
- TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
-
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, "https://token-proxy.example.com")
- .put(ENV_CA_DATA, CA_DATA));
-
- try (MockedConstruction mocked
- = mockConstruction(IdentityClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty());
- when(identityClient.authenticateWithConfidentialClient(any(TokenRequestContext.class)))
- .thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
- })) {
- WorkloadIdentityCredential cred = new WorkloadIdentityCredentialBuilder().tenantId("tenant")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
-
- StepVerifier.create(cred.getToken(request1))
- .expectNextMatches(token -> token1.equals(token.getToken())
- && token.getExpiresAt().getSecond() == expiresAt.getSecond())
- .verifyComplete();
-
- assertNotNull(mocked);
- }
- }
-
- @Test
- public void testProxyUrlWithCaFileGetsToken(@TempDir Path tempDir) throws IOException {
- String endpoint = "https://login.microsoftonline.com";
- String token1 = "tok-ca-file";
- TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
-
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
- Path caFile = tempDir.resolve("ca.crt");
-
- Files.write(caFile, CA_DATA.getBytes(StandardCharsets.UTF_8));
-
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, "https://token-proxy.example.com")
- .put(ENV_CA_FILE, caFile.toString()));
-
- try (MockedConstruction mocked
- = mockConstruction(IdentityClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty());
- when(identityClient.authenticateWithConfidentialClient(any(TokenRequestContext.class)))
- .thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
- })) {
- WorkloadIdentityCredential cred = new WorkloadIdentityCredentialBuilder().tenantId("tenant")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
-
- StepVerifier.create(cred.getToken(request1))
- .expectNextMatches(token -> token1.equals(token.getToken())
- && token.getExpiresAt().getSecond() == expiresAt.getSecond())
- .verifyComplete();
-
- assertNotNull(mocked);
- }
- }
-
- @Test
- public void testProxyEnabledWithSniNameGetsToken(@TempDir Path tempDir) throws IOException {
- // setup
- String endpoint = "https://localhost";
- String token1 = "token1";
- String proxyUrl = "https://token-proxy.example.com";
- String sniName = "615f3b8ad7eb011a09ed3b762e404de43ebc7ade0802a34c9fd322b688c3a655.ests.aks";
-
- TokenRequestContext request1 = new TokenRequestContext().addScopes("https://management.azure.com/.default");
- OffsetDateTime expiresAt = OffsetDateTime.now(ZoneOffset.UTC).plusHours(1);
- Configuration configuration = TestUtils.createTestConfiguration(
- new TestConfigurationSource().put(Configuration.PROPERTY_AZURE_AUTHORITY_HOST, endpoint)
- .put(ENV_PROXY_URL, proxyUrl)
- .put(ENV_SNI_NAME, sniName));
-
- Path tokenFile = tempDir.resolve("token.txt");
- Files.write(tokenFile, "dummy-token".getBytes(StandardCharsets.UTF_8));
-
- try (MockedConstruction identityClientMock
- = mockConstruction(IdentityClient.class, (identityClient, context) -> {
- when(identityClient.authenticateWithConfidentialClientCache(any())).thenReturn(Mono.empty());
- when(identityClient.authenticateWithConfidentialClient(any(TokenRequestContext.class)))
- .thenReturn(TestUtils.getMockAccessToken(token1, expiresAt));
- })) {
- WorkloadIdentityCredential credential = new WorkloadIdentityCredentialBuilder().tenantId("dummy-tenantid")
- .clientId(CLIENT_ID)
- .tokenFilePath(tokenFile.toString())
- .configuration(configuration)
- .enableAzureTokenProxy()
- .build();
-
- StepVerifier.create(credential.getToken(request1))
- .expectNextMatches(token -> token1.equals(token.getToken())
- && expiresAt.getSecond() == token.getExpiresAt().getSecond())
- .verifyComplete();
-
- assertNotNull(identityClientMock);
- }
- }
-
}
diff --git a/sdk/identity/azure-identity/src/test/resources/test-aks-cert-mismatch.p12 b/sdk/identity/azure-identity/src/test/resources/test-aks-cert-mismatch.p12
deleted file mode 100644
index 6c7fc720babb..000000000000
Binary files a/sdk/identity/azure-identity/src/test/resources/test-aks-cert-mismatch.p12 and /dev/null differ
diff --git a/sdk/identity/azure-identity/src/test/resources/test-aks-cert-no-sni.p12 b/sdk/identity/azure-identity/src/test/resources/test-aks-cert-no-sni.p12
deleted file mode 100644
index 1beb531f69fc..000000000000
Binary files a/sdk/identity/azure-identity/src/test/resources/test-aks-cert-no-sni.p12 and /dev/null differ
diff --git a/sdk/identity/azure-identity/src/test/resources/test-aks-cert.p12 b/sdk/identity/azure-identity/src/test/resources/test-aks-cert.p12
deleted file mode 100644
index 5191a8ac662a..000000000000
Binary files a/sdk/identity/azure-identity/src/test/resources/test-aks-cert.p12 and /dev/null differ
diff --git a/sdk/identity/live-test-apps/identity-test-container/pom.xml b/sdk/identity/live-test-apps/identity-test-container/pom.xml
index 97313c8c350c..21c706f9ae31 100644
--- a/sdk/identity/live-test-apps/identity-test-container/pom.xml
+++ b/sdk/identity/live-test-apps/identity-test-container/pom.xml
@@ -18,7 +18,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
diff --git a/sdk/identity/live-test-apps/identity-test-function/pom.xml b/sdk/identity/live-test-apps/identity-test-function/pom.xml
index af64e98ce2f0..b33947531f6f 100644
--- a/sdk/identity/live-test-apps/identity-test-function/pom.xml
+++ b/sdk/identity/live-test-apps/identity-test-function/pom.xml
@@ -24,7 +24,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
com.azure
diff --git a/sdk/identity/live-test-apps/identity-test-vm/pom.xml b/sdk/identity/live-test-apps/identity-test-vm/pom.xml
index b8e29689d724..08c2b82db7ad 100644
--- a/sdk/identity/live-test-apps/identity-test-vm/pom.xml
+++ b/sdk/identity/live-test-apps/identity-test-vm/pom.xml
@@ -18,7 +18,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
diff --git a/sdk/identity/live-test-apps/identity-test-webapp/pom.xml b/sdk/identity/live-test-apps/identity-test-webapp/pom.xml
index 19656db302b7..c23be3382417 100644
--- a/sdk/identity/live-test-apps/identity-test-webapp/pom.xml
+++ b/sdk/identity/live-test-apps/identity-test-webapp/pom.xml
@@ -29,7 +29,7 @@
com.azure
azure-identity
- 1.19.0-beta.2
+ 1.18.2
com.azure