From 90fc833fc8c60f780d0cd5d60a3f65b385d9434e Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 26 May 2026 13:48:07 -0700 Subject: [PATCH 1/7] Fail fast at Apache5HttpClient construction when SecurityManager is active and jdk.net.NetworkPermission setOption.TCP_KEEPIDLE, setOption.TCP_KEEPINTERVAL, setOption.TCP_KEEPCOUNT are not granted --- .../feature-ApacheHTTPClient5-6dda086.json | 6 ++++ .../http/apache5/Apache5HttpClient.java | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .changes/next-release/feature-ApacheHTTPClient5-6dda086.json diff --git a/.changes/next-release/feature-ApacheHTTPClient5-6dda086.json b/.changes/next-release/feature-ApacheHTTPClient5-6dda086.json new file mode 100644 index 000000000000..d55ea8f3e6ed --- /dev/null +++ b/.changes/next-release/feature-ApacheHTTPClient5-6dda086.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Apache HTTP Client 5", + "contributor": "", + "description": "Fail fast at Apache5HttpClient construction when SecurityManager is active and jdk.net.NetworkPermission setOption.TCP_KEEPIDLE, setOption.TCP_KEEPINTERVAL, setOption.TCP_KEEPCOUNT are not granted." +} diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java index 6a4b738fdac7..7fe0b1d906ff 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java @@ -101,6 +101,7 @@ import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.NoOpMetricCollector; import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.ClassLoaderHelper; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Validate; @@ -543,6 +544,12 @@ public interface Builder extends SdkHttpClient.Builder authSchemeRegistry; private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build(); @@ -744,8 +751,37 @@ public void setAuthSchemeProviderRegistry(Registry authScheme public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge( SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); + checkTcpKeepAlivePermissions(); return new Apache5HttpClient(this, resolvedOptions); } + + /** + * Fails fast if a SecurityManager is active but denies the {@code jdk.net.NetworkPermission} entries + * that Apache HC5 requires for its default TCP keepalive socket options. + * No-op when no SecurityManager is installed (including Java 24+). + */ + private static void checkTcpKeepAlivePermissions() { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return; + } + + try { + Class permClass = ClassLoaderHelper.loadClass("jdk.net.NetworkPermission", Apache5HttpClient.class); + for (String permName : REQUIRED_TCP_KEEPALIVE_PERMISSIONS) { + java.security.Permission perm = + (java.security.Permission) permClass.getConstructor(String.class).newInstance(permName); + sm.checkPermission(perm); + } + } catch (SecurityException e) { + throw new IllegalStateException( + "Apache5HttpClient requires jdk.net.NetworkPermission for \"" + + String.join("\", \"", REQUIRED_TCP_KEEPALIVE_PERMISSIONS) + + "\" when a SecurityManager is active.", e); + } catch (Exception e) { + log.warn(() -> "Unable to verify TCP keepalive permissions: " + e.getMessage(), e); + } + } } private static class ApacheConnectionManagerFactory { From 5049b73ed0b22f204b2e80a7f79ae57e53bfb517 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 26 May 2026 14:22:19 -0700 Subject: [PATCH 2/7] Update Junits --- .../Apache5HttpClientSecurityManagerTest.java | 123 ++++++++++++++++++ .../http/apache5/security-manager-test.policy | 15 +++ 2 files changed, 138 insertions(+) create mode 100644 http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java create mode 100644 http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test.policy diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java new file mode 100644 index 000000000000..c8b36ac31767 --- /dev/null +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.http.apache5; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.security.Permission; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests that Apache5HttpClient fails fast at construction time when a SecurityManager + * denies jdk.net.NetworkPermission for TCP keepalive extended options. + */ +@EnabledForJreRange(max = JRE.JAVA_17) +class Apache5HttpClientSecurityManagerTest { + + @AfterEach + void tearDown() { + System.setSecurityManager(null); + System.clearProperty("java.security.policy"); + java.security.Policy.getPolicy().refresh(); + } + + @Test + void buildWithDefaults_whenStandardPermissionsGrantedButNetworkPermissionMissing_shouldThrowIllegalStateException() { + System.setProperty("java.security.policy", "=" + getPolicyUrl()); + java.security.Policy.getPolicy().refresh(); + System.setSecurityManager(new SecurityManager()); + + assertThatThrownBy(() -> Apache5HttpClient.builder().build()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("jdk.net.NetworkPermission"); + } + + private String getPolicyUrl() { + return getClass().getResource("security-manager-test.policy").toExternalForm(); + } + + @ParameterizedTest + @MethodSource("partiallyGrantedPermissions") + void buildWithDefaults_whenNotAllPermissionsGranted_shouldThrowIllegalStateException(Set grantedPermissions) { + System.setSecurityManager(new GrantOnlyNetworkPermissionSecurityManager(grantedPermissions)); + + assertThatThrownBy(() -> Apache5HttpClient.builder().build()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("jdk.net.NetworkPermission"); + } + + @Test + void buildWithDefaults_whenAllPermissionsGranted_shouldSucceed() { + Set allGranted = new HashSet<>(Arrays.asList( + "setOption.TCP_KEEPIDLE", "setOption.TCP_KEEPINTERVAL", "setOption.TCP_KEEPCOUNT")); + System.setSecurityManager(new GrantOnlyNetworkPermissionSecurityManager(allGranted)); + assertThatNoException().isThrownBy(() -> { + Apache5HttpClient.builder().build().close(); + }); + } + + @Test + void buildWithDefaults_whenNoSecurityManager_shouldSucceed() { + assertThatNoException().isThrownBy(() -> { + Apache5HttpClient.builder().build().close(); + }); + } + + static Stream partiallyGrantedPermissions() { + return Stream.of( + // 0 out of 3 granted + Arguments.of(new HashSet<>()), + // 1 out of 3 granted + Arguments.of(new HashSet<>(Arrays.asList("setOption.TCP_KEEPIDLE"))), + Arguments.of(new HashSet<>(Arrays.asList("setOption.TCP_KEEPINTERVAL"))), + Arguments.of(new HashSet<>(Arrays.asList("setOption.TCP_KEEPCOUNT"))), + // 2 out of 3 granted + Arguments.of(new HashSet<>(Arrays.asList("setOption.TCP_KEEPIDLE", "setOption.TCP_KEEPINTERVAL"))), + Arguments.of(new HashSet<>(Arrays.asList("setOption.TCP_KEEPIDLE", "setOption.TCP_KEEPCOUNT"))), + Arguments.of(new HashSet<>(Arrays.asList("setOption.TCP_KEEPINTERVAL", "setOption.TCP_KEEPCOUNT"))) + ); + } + + /** + * SecurityManager that only grants specific jdk.net.NetworkPermission entries and denies the rest. + */ + private static class GrantOnlyNetworkPermissionSecurityManager extends SecurityManager { + private final Set grantedPermissions; + + GrantOnlyNetworkPermissionSecurityManager(Set grantedPermissions) { + this.grantedPermissions = grantedPermissions; + } + + @Override + public void checkPermission(Permission perm) { + if ("jdk.net.NetworkPermission".equals(perm.getClass().getName()) + && !grantedPermissions.contains(perm.getName())) { + throw new SecurityException("Denied: " + perm.getName()); + } + } + } +} diff --git a/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test.policy b/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test.policy new file mode 100644 index 000000000000..39f450d6d442 --- /dev/null +++ b/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test.policy @@ -0,0 +1,15 @@ +grant { + permission java.util.PropertyPermission "*", "read,write"; + permission java.io.FilePermission "<>", "read,write"; + permission java.lang.RuntimePermission "getenv.*"; + permission "java.lang.RuntimePermission" "accessDeclaredMembers"; + permission "javax.net.ssl.SSLPermission" "setDefaultSSLContext"; + permission "java.net.SocketPermission" "*", "connect,resolve"; + + // Needed for test to remove the security manager + permission java.lang.RuntimePermission "setSecurityManager"; + + // jdk.net.NetworkPermission for setOption.TCP_KEEPIDLE, setOption.TCP_KEEPINTERVAL, + // setOption.TCP_KEEPCOUNT is explicitly NOT granted to test that Apache5HttpClient + // fails fast when these permissions are missing. +}; From 697b152a803e34f53d05cf59f8f3b02c22b0c72d Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 26 May 2026 14:48:47 -0700 Subject: [PATCH 3/7] update junit test cases and handle case where we can get security exception other than one expected --- .../http/apache5/Apache5HttpClient.java | 36 +++++++++++---- .../Apache5HttpClientSecurityManagerTest.java | 44 +++++++++++++++++-- ...nager-test-with-network-permissions.policy | 17 +++++++ 3 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java index 7fe0b1d906ff..e1c1cdef3188 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java @@ -544,7 +544,7 @@ public interface Builder extends SdkHttpClient.Builder authScheme public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge( SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); - checkTcpKeepAlivePermissions(); + checkTcpSocketOptionPermissions(); return new Apache5HttpClient(this, resolvedOptions); } @@ -760,7 +760,7 @@ public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { * that Apache HC5 requires for its default TCP keepalive socket options. * No-op when no SecurityManager is installed (including Java 24+). */ - private static void checkTcpKeepAlivePermissions() { + private static void checkTcpSocketOptionPermissions() { SecurityManager sm = System.getSecurityManager(); if (sm == null) { return; @@ -768,20 +768,38 @@ private static void checkTcpKeepAlivePermissions() { try { Class permClass = ClassLoaderHelper.loadClass("jdk.net.NetworkPermission", Apache5HttpClient.class); - for (String permName : REQUIRED_TCP_KEEPALIVE_PERMISSIONS) { + for (String permName : REQUIRED_TCP_SOCKET_OPTION_PERMISSIONS) { java.security.Permission perm = (java.security.Permission) permClass.getConstructor(String.class).newInstance(permName); sm.checkPermission(perm); } } catch (SecurityException e) { - throw new IllegalStateException( - "Apache5HttpClient requires jdk.net.NetworkPermission for \"" - + String.join("\", \"", REQUIRED_TCP_KEEPALIVE_PERMISSIONS) - + "\" when a SecurityManager is active.", e); + if (isTcpSocketOptionPermissionDenied(e)) { + throw new IllegalStateException( + "Apache5HttpClient requires jdk.net.NetworkPermission for \"" + + String.join("\", \"", REQUIRED_TCP_SOCKET_OPTION_PERMISSIONS) + + "\" when a SecurityManager is active.", e); + } + log.debug(() -> "SecurityManager denied a non-TCP socket option permission during " + + "verification: " + e.getMessage(), e); } catch (Exception e) { - log.warn(() -> "Unable to verify TCP keepalive permissions: " + e.getMessage(), e); + log.debug(() -> "Could not verify jdk.net.NetworkPermission for TCP socket options: " + e.getMessage(), e); } } + + private static boolean isTcpSocketOptionPermissionDenied(SecurityException e) { + String message = e.getMessage(); + if (message == null) { + return false; + } + for (String perm : REQUIRED_TCP_SOCKET_OPTION_PERMISSIONS) { + if (message.contains(perm)) { + return true; + } + } + return false; + } + } private static class ApacheConnectionManagerFactory { diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java index c8b36ac31767..6d930a0b7a1b 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.http.apache5; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -23,6 +24,7 @@ import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; +import org.apache.logging.log4j.Level; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledForJreRange; @@ -30,6 +32,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.testutils.LogCaptor; /** * Tests that Apache5HttpClient fails fast at construction time when a SecurityManager @@ -47,7 +50,7 @@ void tearDown() { @Test void buildWithDefaults_whenStandardPermissionsGrantedButNetworkPermissionMissing_shouldThrowIllegalStateException() { - System.setProperty("java.security.policy", "=" + getPolicyUrl()); + System.setProperty("java.security.policy", "=" + getPolicyUrl("security-manager-test.policy")); java.security.Policy.getPolicy().refresh(); System.setSecurityManager(new SecurityManager()); @@ -56,8 +59,42 @@ void buildWithDefaults_whenStandardPermissionsGrantedButNetworkPermissionMissing .hasMessageContaining("jdk.net.NetworkPermission"); } - private String getPolicyUrl() { - return getClass().getResource("security-manager-test.policy").toExternalForm(); + @Test + void buildWithDefaults_whenPolicyGrantsNetworkPermissions_shouldSucceed() { + System.setProperty("java.security.policy", "=" + getPolicyUrl("security-manager-test-with-network-permissions.policy")); + java.security.Policy.getPolicy().refresh(); + System.setSecurityManager(new SecurityManager()); + + assertThatNoException().isThrownBy(() -> { + Apache5HttpClient.builder().build().close(); + }); + } + + private String getPolicyUrl(String policyFileName) { + return getClass().getResource(policyFileName).toExternalForm(); + } + + @Test + void buildWithDefaults_whenUnrelatedSecurityExceptionThrown_shouldNotThrow() { + System.setSecurityManager(new SecurityManager() { + @Override + public void checkPermission(Permission perm) { + if ("jdk.net.NetworkPermission".equals(perm.getClass().getName())) { + throw new SecurityException("access denied: some.unrelated.permission"); + } + } + }); + + try (LogCaptor logCaptor = LogCaptor.create(Level.DEBUG)) { + assertThatNoException().isThrownBy(() -> { + Apache5HttpClient.builder().build().close(); + }); + assertThat(logCaptor.loggedEvents()).anySatisfy(logEvent -> { + assertThat(logEvent.getLevel()).isEqualTo(Level.DEBUG); + assertThat(logEvent.getMessage().getFormattedMessage()) + .contains("SecurityManager denied a non-TCP socket option permission"); + }); + } } @ParameterizedTest @@ -120,4 +157,5 @@ public void checkPermission(Permission perm) { } } } + } diff --git a/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy b/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy new file mode 100644 index 000000000000..20da662ae219 --- /dev/null +++ b/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy @@ -0,0 +1,17 @@ +grant { + permission java.util.PropertyPermission "*", "read,write"; + permission java.io.FilePermission "<>", "read,write"; + permission java.lang.RuntimePermission "getenv.*"; + permission "java.lang.RuntimePermission" "accessDeclaredMembers"; + permission "java.lang.RuntimePermission" "modifyThread"; + permission "javax.net.ssl.SSLPermission" "setDefaultSSLContext"; + permission "java.net.SocketPermission" "*", "connect,resolve"; + + // Needed for test to remove the security manager + permission java.lang.RuntimePermission "setSecurityManager"; + + // TCP socket option permissions required by Apache HC5 + permission "jdk.net.NetworkPermission" "setOption.TCP_KEEPIDLE"; + permission "jdk.net.NetworkPermission" "setOption.TCP_KEEPINTERVAL"; + permission "jdk.net.NetworkPermission" "setOption.TCP_KEEPCOUNT"; +}; From bcefb092cfe550d93b2f7a1a05bcc51c8a04b173 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 27 May 2026 08:44:48 -0700 Subject: [PATCH 4/7] Update for loop --- .../http/apache5/Apache5HttpClient.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java index e1c1cdef3188..be13e91a26c9 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java @@ -31,6 +31,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; +import java.util.Arrays; import java.util.Iterator; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -780,24 +781,16 @@ private static void checkTcpSocketOptionPermissions() { + String.join("\", \"", REQUIRED_TCP_SOCKET_OPTION_PERMISSIONS) + "\" when a SecurityManager is active.", e); } - log.debug(() -> "SecurityManager denied a non-TCP socket option permission during " - + "verification: " + e.getMessage(), e); + log.debug(() -> "SecurityManager denied a non-TCP socket option permission during verification: " + + e.getMessage(), e); } catch (Exception e) { log.debug(() -> "Could not verify jdk.net.NetworkPermission for TCP socket options: " + e.getMessage(), e); } } - private static boolean isTcpSocketOptionPermissionDenied(SecurityException e) { - String message = e.getMessage(); - if (message == null) { - return false; - } - for (String perm : REQUIRED_TCP_SOCKET_OPTION_PERMISSIONS) { - if (message.contains(perm)) { - return true; - } - } - return false; + private static boolean isTcpSocketOptionPermissionDenied(SecurityException securityException) { + String message = securityException.getMessage(); + return message != null && Arrays.stream(REQUIRED_TCP_SOCKET_OPTION_PERMISSIONS).anyMatch(message::contains); } } From a6ea0eb3d0b8b6dd5750ca87179deaffcbdaca5f Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 27 May 2026 12:04:02 -0700 Subject: [PATCH 5/7] Handle PR comemnt to add test case with actual request --- .../ApacheSecurityManagerHttpCallTest.java | 35 +++++++ .../http/apache/security-manager-test.policy | 8 ++ ...he5SecurityManagerClientCreationTest.java} | 19 +--- .../Apache5SecurityManagerHttpCallTest.java | 35 +++++++ ...ecurity-manager-test-with-http-call.policy | 13 +++ ...nager-test-with-network-permissions.policy | 17 ---- ...SdkHttpClientSecurityManagerTestSuite.java | 95 +++++++++++++++++++ 7 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheSecurityManagerHttpCallTest.java create mode 100644 http-clients/apache-client/src/test/resources/software/amazon/awssdk/http/apache/security-manager-test.policy rename http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/{Apache5HttpClientSecurityManagerTest.java => Apache5SecurityManagerClientCreationTest.java} (89%) create mode 100644 http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5SecurityManagerHttpCallTest.java create mode 100644 http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-http-call.policy delete mode 100644 http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy create mode 100644 test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java diff --git a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheSecurityManagerHttpCallTest.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheSecurityManagerHttpCallTest.java new file mode 100644 index 000000000000..7dd40f46dd10 --- /dev/null +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheSecurityManagerHttpCallTest.java @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.http.apache; + +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientSecurityManagerTestSuite; + +@EnabledForJreRange(max = JRE.JAVA_17) +class ApacheSecurityManagerHttpCallTest extends SdkHttpClientSecurityManagerTestSuite { + + @Override + protected SdkHttpClient createHttpClient() { + return ApacheHttpClient.builder().build(); + } + + @Override + protected String getPolicyFileUrl() { + return getClass().getResource("security-manager-test.policy").toExternalForm(); + } +} diff --git a/http-clients/apache-client/src/test/resources/software/amazon/awssdk/http/apache/security-manager-test.policy b/http-clients/apache-client/src/test/resources/software/amazon/awssdk/http/apache/security-manager-test.policy new file mode 100644 index 000000000000..361f250aef9a --- /dev/null +++ b/http-clients/apache-client/src/test/resources/software/amazon/awssdk/http/apache/security-manager-test.policy @@ -0,0 +1,8 @@ +grant { + permission java.util.PropertyPermission "*", "read,write"; + permission java.lang.RuntimePermission "modifyThread"; + permission java.lang.RuntimePermission "setContextClassLoader"; + permission java.lang.RuntimePermission "setSecurityManager"; + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; + permission java.net.SocketPermission "*", "connect,accept"; +}; diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5SecurityManagerClientCreationTest.java similarity index 89% rename from http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java rename to http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5SecurityManagerClientCreationTest.java index 6d930a0b7a1b..7c27e677e59b 100644 --- a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5HttpClientSecurityManagerTest.java +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5SecurityManagerClientCreationTest.java @@ -39,7 +39,7 @@ * denies jdk.net.NetworkPermission for TCP keepalive extended options. */ @EnabledForJreRange(max = JRE.JAVA_17) -class Apache5HttpClientSecurityManagerTest { +class Apache5SecurityManagerClientCreationTest { @AfterEach void tearDown() { @@ -50,7 +50,7 @@ void tearDown() { @Test void buildWithDefaults_whenStandardPermissionsGrantedButNetworkPermissionMissing_shouldThrowIllegalStateException() { - System.setProperty("java.security.policy", "=" + getPolicyUrl("security-manager-test.policy")); + System.setProperty("java.security.policy", "=" + getPolicyUrl()); java.security.Policy.getPolicy().refresh(); System.setSecurityManager(new SecurityManager()); @@ -59,19 +59,8 @@ void buildWithDefaults_whenStandardPermissionsGrantedButNetworkPermissionMissing .hasMessageContaining("jdk.net.NetworkPermission"); } - @Test - void buildWithDefaults_whenPolicyGrantsNetworkPermissions_shouldSucceed() { - System.setProperty("java.security.policy", "=" + getPolicyUrl("security-manager-test-with-network-permissions.policy")); - java.security.Policy.getPolicy().refresh(); - System.setSecurityManager(new SecurityManager()); - - assertThatNoException().isThrownBy(() -> { - Apache5HttpClient.builder().build().close(); - }); - } - - private String getPolicyUrl(String policyFileName) { - return getClass().getResource(policyFileName).toExternalForm(); + private String getPolicyUrl() { + return getClass().getResource("security-manager-test.policy").toExternalForm(); } @Test diff --git a/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5SecurityManagerHttpCallTest.java b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5SecurityManagerHttpCallTest.java new file mode 100644 index 000000000000..1d105321a5d2 --- /dev/null +++ b/http-clients/apache5-client/src/test/java/software/amazon/awssdk/http/apache5/Apache5SecurityManagerHttpCallTest.java @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.http.apache5; + +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpClientSecurityManagerTestSuite; + +@EnabledForJreRange(max = JRE.JAVA_17) +class Apache5SecurityManagerHttpCallTest extends SdkHttpClientSecurityManagerTestSuite { + + @Override + protected SdkHttpClient createHttpClient() { + return Apache5HttpClient.builder().build(); + } + + @Override + protected String getPolicyFileUrl() { + return getClass().getResource("security-manager-test-with-http-call.policy").toExternalForm(); + } +} diff --git a/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-http-call.policy b/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-http-call.policy new file mode 100644 index 000000000000..d0964d39fd5d --- /dev/null +++ b/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-http-call.policy @@ -0,0 +1,13 @@ +grant { + permission java.util.PropertyPermission "*", "read,write"; + permission java.lang.RuntimePermission "modifyThread"; + permission java.lang.RuntimePermission "setContextClassLoader"; + permission java.lang.RuntimePermission "setSecurityManager"; + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; + permission java.net.SocketPermission "*", "connect,accept"; + + // Required by Apache HC5 for TCP socket options (not needed by Apache HC4) + permission jdk.net.NetworkPermission "setOption.TCP_KEEPIDLE"; + permission jdk.net.NetworkPermission "setOption.TCP_KEEPINTERVAL"; + permission jdk.net.NetworkPermission "setOption.TCP_KEEPCOUNT"; +}; diff --git a/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy b/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy deleted file mode 100644 index 20da662ae219..000000000000 --- a/http-clients/apache5-client/src/test/resources/software/amazon/awssdk/http/apache5/security-manager-test-with-network-permissions.policy +++ /dev/null @@ -1,17 +0,0 @@ -grant { - permission java.util.PropertyPermission "*", "read,write"; - permission java.io.FilePermission "<>", "read,write"; - permission java.lang.RuntimePermission "getenv.*"; - permission "java.lang.RuntimePermission" "accessDeclaredMembers"; - permission "java.lang.RuntimePermission" "modifyThread"; - permission "javax.net.ssl.SSLPermission" "setDefaultSSLContext"; - permission "java.net.SocketPermission" "*", "connect,resolve"; - - // Needed for test to remove the security manager - permission java.lang.RuntimePermission "setSecurityManager"; - - // TCP socket option permissions required by Apache HC5 - permission "jdk.net.NetworkPermission" "setOption.TCP_KEEPIDLE"; - permission "jdk.net.NetworkPermission" "setOption.TCP_KEEPINTERVAL"; - permission "jdk.net.NetworkPermission" "setOption.TCP_KEEPCOUNT"; -}; diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java new file mode 100644 index 000000000000..cb5be0025124 --- /dev/null +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java @@ -0,0 +1,95 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.http; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.WireMockServer; +import java.net.URI; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +/** + * Base test suite that verifies an HTTP client can construct and execute requests + * under a SecurityManager with the appropriate permissions granted via a policy file. + * + *

Subclasses provide the HTTP client implementation and a policy file path. + * The policy file for Apache 4.x does not need jdk.net.NetworkPermission entries, + * while Apache 5.x requires them for TCP_KEEPIDLE/KEEPINTERVAL/KEEPCOUNT.

+ */ +@EnabledForJreRange(max = JRE.JAVA_17) +public abstract class SdkHttpClientSecurityManagerTestSuite { + + private WireMockServer server; + + @BeforeEach + void setUpServer() { + server = new WireMockServer(wireMockConfig().dynamicPort()); + server.start(); + server.stubFor(get(urlPathEqualTo("/")) + .willReturn(aResponse().withStatus(200).withBody("ok"))); + } + + @AfterEach + void tearDownServer() { + System.setSecurityManager(null); + System.clearProperty("java.security.policy"); + java.security.Policy.getPolicy().refresh(); + server.stop(); + } + + /** + * Creates the HTTP client to test. + */ + protected abstract SdkHttpClient createHttpClient(); + + /** + * Returns the policy file URL to use. Subclasses load from their own resource path. + */ + protected abstract String getPolicyFileUrl(); + + @Test + void httpCall_whenSecurityManagerActiveWithCorrectPermissions_shouldSucceed() throws Exception { + System.setProperty("java.security.policy", "=" + getPolicyFileUrl()); + java.security.Policy.getPolicy().refresh(); + System.setSecurityManager(new SecurityManager()); + + SdkHttpClient client = createHttpClient(); + try { + SdkHttpFullRequest request = SdkHttpFullRequest.builder() + .uri(URI.create("http://localhost:" + server.port() + "/")) + .method(SdkHttpMethod.GET) + .build(); + HttpExecuteResponse response = client.prepareRequest( + HttpExecuteRequest.builder().request(request).build()).call(); + + assertThat(response.httpResponse().statusCode()).isEqualTo(200); + } finally { + client.close(); + } + } + + protected int serverPort() { + return server.port(); + } +} From bb45965d943d23521100301afd9d2e59dffcb415 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 27 May 2026 13:05:42 -0700 Subject: [PATCH 6/7] checkstyle --- .../awssdk/http/SdkHttpClientSecurityManagerTestSuite.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java index cb5be0025124..7a0da421f232 100644 --- a/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java +++ b/test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientSecurityManagerTestSuite.java @@ -69,7 +69,7 @@ void tearDownServer() { protected abstract String getPolicyFileUrl(); @Test - void httpCall_whenSecurityManagerActiveWithCorrectPermissions_shouldSucceed() throws Exception { + void httpCallSucceedsWhenSecurityManagerActiveWithCorrectPermissions() throws Exception { System.setProperty("java.security.policy", "=" + getPolicyFileUrl()); java.security.Policy.getPolicy().refresh(); System.setSecurityManager(new SecurityManager()); From c56ae8e6469057512b421ec53dad3a7bb3c082d0 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Thu, 28 May 2026 10:00:41 -0700 Subject: [PATCH 7/7] Handle review comment --- .../amazon/awssdk/http/apache5/Apache5HttpClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java index be13e91a26c9..963413a4b32b 100644 --- a/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java +++ b/http-clients/apache5-client/src/main/java/software/amazon/awssdk/http/apache5/Apache5HttpClient.java @@ -28,6 +28,7 @@ import java.net.InetAddress; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.Permission; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; @@ -770,8 +771,7 @@ private static void checkTcpSocketOptionPermissions() { try { Class permClass = ClassLoaderHelper.loadClass("jdk.net.NetworkPermission", Apache5HttpClient.class); for (String permName : REQUIRED_TCP_SOCKET_OPTION_PERMISSIONS) { - java.security.Permission perm = - (java.security.Permission) permClass.getConstructor(String.class).newInstance(permName); + Permission perm = (Permission) permClass.getConstructor(String.class).newInstance(permName); sm.checkPermission(perm); } } catch (SecurityException e) {