diff --git a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md index 034766972361..90d533449de1 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md +++ b/sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md @@ -8,6 +8,9 @@ ### Bugs Fixed +- Fixed an issue where the internal HTTP client did not honor JVM proxy system properties. + ([#28801](https://github.com/Azure/azure-sdk-for-java/issues/28801)) + ### Other Changes ## 2.11.0 (2026-02-28) diff --git a/sdk/keyvault/azure-security-keyvault-jca/README.md b/sdk/keyvault/azure-security-keyvault-jca/README.md index f31a7ca5e90a..564cd1a9b172 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/README.md +++ b/sdk/keyvault/azure-security-keyvault-jca/README.md @@ -544,6 +544,26 @@ az role assignment create \ -J-Dazure.keyvault.client-secret=${CLIENT_SECRET} ``` +If you run `jarsigner` behind a proxy, pass the standard JVM proxy system properties with `-J`: + +```bash +jarsigner -keystore NONE -storetype AzureKeyVault \ + -signedjar signerjar.jar ${PARAM_YOUR_JAR_FILE_PATH} "${CERT_NAME}" \ + -verbose -storepass "" \ + -providerName AzureKeyVault \ + -providerClass com.azure.security.keyvault.jca.KeyVaultJcaProvider \ + -J-Dazure.keyvault.uri=${KEYVAULT_URL} \ + -J-Dazure.keyvault.tenant-id=${TENANT} \ + -J-Dazure.keyvault.client-id=${CLIENT_ID} \ + -J-Dazure.keyvault.client-secret=${CLIENT_SECRET} \ + -J-Dhttps.proxyHost=proxy.company.local \ + -J-Dhttps.proxyPort=8080 \ + '-J-Dhttp.nonProxyHosts=169.254.169.254|localhost|127.*' +``` + +`http.nonProxyHosts` may be needed for local or managed identity endpoints, such as `169.254.169.254`, +that should bypass the proxy. + replace ${PARAM_YOUR_JAR_FILE_PATH} with the path of your jar file, replace ${PARAM_JCA_PROVIDER_JAR_PATH} with the path of the jca provider jar. Check your output, if you see the `jar signed` message, it means the jar is signed successfully. @@ -681,5 +701,3 @@ This project has adopted the [Microsoft Open Source Code of Conduct][microsoft_c [jca_reference_guide]: https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html [microsoft_code_of_conduct]: https://opensource.microsoft.com/codeofconduct/ [non-exportable]: https://learn.microsoft.com/azure/key-vault/certificates/about-certificates#exportable-or-non-exportable-key - - diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java index 4f4480cc0d52..0026709ccd69 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtil.java @@ -186,7 +186,7 @@ private static CloseableHttpClient buildClient() { .register("https", sslConnectionSocketFactory) .build()); - return HttpClients.custom().setConnectionManager(manager).build(); + return HttpClients.custom().useSystemProperties().setConnectionManager(manager).build(); } public static String validateUri(String uri, String propertyName) { diff --git a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java index d50c99d7a68e..96966ae613de 100644 --- a/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java +++ b/sdk/keyvault/azure-security-keyvault-jca/src/test/java/com/azure/security/keyvault/jca/implementation/utils/HttpUtilTest.java @@ -5,6 +5,18 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.DEFAULT_USER_AGENT_VALUE_PREFIX; import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.VERSION; @@ -18,6 +30,40 @@ public void getUserAgentPrefixTest() { assertEquals(DEFAULT_USER_AGENT_VALUE_PREFIX + VERSION, HttpUtil.USER_AGENT_VALUE); } + @Test + @ResourceLock(Resources.SYSTEM_PROPERTIES) + public void getUsesJvmProxySystemProperties() throws Exception { + String previousProxyHost = System.getProperty("http.proxyHost"); + String previousProxyPort = System.getProperty("http.proxyPort"); + String previousNonProxyHosts = System.getProperty("http.nonProxyHosts"); + + try (ServerSocket proxyServer = new ServerSocket(0)) { + CountDownLatch requestReceived = new CountDownLatch(1); + AtomicReference requestLine = new AtomicReference<>(); + AtomicReference proxyFailure = new AtomicReference<>(); + + Thread proxyThread + = new Thread(() -> handleProxyRequest(proxyServer, requestLine, requestReceived, proxyFailure)); + proxyThread.setDaemon(true); + proxyThread.start(); + + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", String.valueOf(proxyServer.getLocalPort())); + System.clearProperty("http.nonProxyHosts"); + + String response = HttpUtil.get("http://azure-keyvault-jca-proxy-test.invalid/path", null); + + assertTrue(requestReceived.await(5, TimeUnit.SECONDS), "Expected proxy server to receive the request."); + assertNull(proxyFailure.get(), "Proxy server failed while handling the request."); + assertEquals("proxied", response); + assertEquals("GET http://azure-keyvault-jca-proxy-test.invalid/path HTTP/1.1", requestLine.get()); + } finally { + restoreProperty("http.proxyHost", previousProxyHost); + restoreProperty("http.proxyPort", previousProxyPort); + restoreProperty("http.nonProxyHosts", previousNonProxyHosts); + } + } + @Test @Disabled("Disable this because it will cause pipeline failure: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=1196171&view=logs&j=4a83f3be-c53d-53dd-7954-86872056fb11&t=54174aae-5a55-579d-08e2-94fb446f7b77&l=29") public void testHttpUtilGet() { @@ -35,4 +81,39 @@ public void testHttpUtilGet1() { assertNotNull(result); assertFalse(result.isEmpty()); } + + private static void handleProxyRequest(ServerSocket proxyServer, AtomicReference requestLine, + CountDownLatch requestReceived, AtomicReference proxyFailure) { + try (Socket socket = proxyServer.accept(); + BufferedReader reader + = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); + OutputStream outputStream = socket.getOutputStream()) { + + requestLine.set(reader.readLine()); + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) { + break; + } + } + + byte[] body = "proxied".getBytes(StandardCharsets.UTF_8); + outputStream.write( + ("HTTP/1.1 200 OK\r\nContent-Length: " + body.length + "\r\n\r\n").getBytes(StandardCharsets.UTF_8)); + outputStream.write(body); + outputStream.flush(); + requestReceived.countDown(); + } catch (Exception e) { + proxyFailure.set(e); + requestReceived.countDown(); + } + } + + private static void restoreProperty(String name, String value) { + if (value == null) { + System.clearProperty(name); + } else { + System.setProperty(name, value); + } + } }