diff --git a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
index 6871e760a793..8130fa7245af 100644
--- a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
+++ b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
@@ -346,6 +346,8 @@
+
+
diff --git a/services-custom/sns-message-manager/pom.xml b/services-custom/sns-message-manager/pom.xml
index 6acfed8b5179..fb5619d5e5c9 100644
--- a/services-custom/sns-message-manager/pom.xml
+++ b/services-custom/sns-message-manager/pom.xml
@@ -68,11 +68,26 @@
sdk-core
${project.version}
+
+ software.amazon.awssdk
+ regions
+ ${project.version}
+
+
+ software.amazon.awssdk
+ sns
+ ${project.version}
+
software.amazon.awssdk
http-client-spi
${project.version}
+
+ software.amazon.awssdk
+ endpoints-spi
+ ${project.version}
+
org.apache.httpcomponents.client5
httpclient5
@@ -80,7 +95,7 @@
org.junit.jupiter
- junit-jupiter-engine
+ junit-jupiter
test
diff --git a/services-custom/sns-message-manager/src/main/java/software/amazon/awssdk/messagemanager/sns/internal/SnsHostProvider.java b/services-custom/sns-message-manager/src/main/java/software/amazon/awssdk/messagemanager/sns/internal/SnsHostProvider.java
new file mode 100644
index 000000000000..28120ecf198c
--- /dev/null
+++ b/services-custom/sns-message-manager/src/main/java/software/amazon/awssdk/messagemanager/sns/internal/SnsHostProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.messagemanager.sns.internal;
+
+import java.net.URI;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.annotations.SdkTestInternalApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.endpoints.Endpoint;
+import software.amazon.awssdk.regions.PartitionMetadata;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sns.endpoints.SnsEndpointParams;
+import software.amazon.awssdk.services.sns.endpoints.SnsEndpointProvider;
+import software.amazon.awssdk.utils.CompletableFutureUtils;
+import software.amazon.awssdk.utils.Logger;
+
+/**
+ * Utility class for determining both the regional endpoint that SNS certificates are expected to be hosted from, as well as the
+ * expected common name (CN) that the certificate from that endpoint must have.
+ */
+@SdkInternalApi
+@ThreadSafe
+public class SnsHostProvider {
+ private static final Logger LOG = Logger.loggerFor(SnsHostProvider.class);
+
+ private final Region region;
+ private final SnsEndpointProvider endpointProvider;
+
+ public SnsHostProvider(Region region) {
+ this(region, SnsEndpointProvider.defaultProvider());
+ }
+
+ @SdkTestInternalApi
+ SnsHostProvider(Region region, SnsEndpointProvider endpointProvider) {
+ this.region = region;
+ this.endpointProvider = endpointProvider;
+ }
+
+ public URI regionalEndpoint() {
+ SnsEndpointParams params = SnsEndpointParams.builder().region(region).build();
+ try {
+ Endpoint endpoint = CompletableFutureUtils.joinLikeSync(endpointProvider.resolveEndpoint(params));
+ URI url = endpoint.url();
+ LOG.debug(() -> String.format("Resolved endpoint %s for region %s", url, region));
+ return url;
+ } catch (SdkClientException e) {
+ throw SdkClientException.create("Unable to resolve SNS endpoint for region " + region, e);
+ }
+ }
+
+ public String signingCertCommonName() {
+ String commonName = signingCertCommonNameInternal();
+ LOG.debug(() -> String.format("Resolved common name %s for region %s", commonName, region));
+ return commonName;
+ }
+
+ private String signingCertCommonNameInternal() {
+ // If we don't know about this region, try to guess common name
+ if (!Region.regions().contains(region)) {
+ // Find the partition where it belongs by checking the region against the published pattern for known partitions.
+ // e.g. 'us-gov-west-3' would match the 'aws-us-gov' partition.
+ // This will return the 'aws' partition if it fails to match any partition.
+ PartitionMetadata partitionMetadata = PartitionMetadata.of(region);
+ return "sns." + partitionMetadata.dnsSuffix();
+ }
+
+ String regionId = region.id();
+
+ switch (regionId) {
+ case "cn-north-1":
+ return "sns-cn-north-1.amazonaws.com.cn";
+ case "cn-northwest-1":
+ return "sns-cn-northwest-1.amazonaws.com.cn";
+ case "us-gov-west-1":
+ case "us-gov-east-1":
+ return "sns-us-gov-west-1.amazonaws.com";
+ case "us-iso-east-1":
+ return "sns-us-iso-east-1.c2s.ic.gov";
+ case "us-isob-east-1":
+ return "sns-us-isob-east-1.sc2s.sgov.gov";
+ case "us-isof-east-1":
+ return "sns-signing.us-isof-east-1.csp.hci.ic.gov";
+ case "us-isof-south-1":
+ return "sns-signing.us-isof-south-1.csp.hci.ic.gov";
+ case "eu-isoe-west-1":
+ return "sns-signing.eu-isoe-west-1.cloud.adc-e.uk";
+ case "eusc-de-east-1":
+ return "sns-signing.eusc-de-east-1.amazonaws.eu";
+ case "ap-east-1":
+ case "ap-east-2":
+ case "ap-south-2":
+ case "ap-southeast-5":
+ case "ap-southeast-6":
+ case "ap-southeast-7":
+ case "me-south-1":
+ case "me-central-1":
+ case "eu-south-1":
+ case "eu-south-2":
+ case "eu-central-2":
+ case "af-south-1":
+ case "ap-southeast-3":
+ case "ap-southeast-4":
+ case "il-central-1":
+ case "ca-west-1":
+ case "mx-central-1":
+ return "sns-signing." + regionId + ".amazonaws.com";
+ default:
+ return "sns.amazonaws.com";
+ }
+ }
+}
diff --git a/services-custom/sns-message-manager/src/test/java/software/amazon/awssdk/messagemanager/sns/internal/SnsHostProviderTest.java b/services-custom/sns-message-manager/src/test/java/software/amazon/awssdk/messagemanager/sns/internal/SnsHostProviderTest.java
new file mode 100644
index 000000000000..e1dcc29b34a2
--- /dev/null
+++ b/services-custom/sns-message-manager/src/test/java/software/amazon/awssdk/messagemanager/sns/internal/SnsHostProviderTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.messagemanager.sns.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sns.endpoints.SnsEndpointParams;
+import software.amazon.awssdk.services.sns.endpoints.SnsEndpointProvider;
+
+public class SnsHostProviderTest {
+
+ @ParameterizedTest
+ @MethodSource("commonNameTestCases")
+ void signingCertCommonName_returnsCorrectNameForRegion(CommonNameTestCase tc) {
+ SnsHostProvider hostProvider = new SnsHostProvider(Region.of(tc.region));
+ assertThat(hostProvider.signingCertCommonName()).isEqualTo(tc.expectedCommonName);
+ }
+
+ @Test
+ void regionalEndpoint_delegatesToEndpointProvider() {
+ SnsEndpointProvider mockProvider = mock(SnsEndpointProvider.class);
+ SnsEndpointProvider realProvider = SnsEndpointProvider.defaultProvider();
+
+ when(mockProvider.resolveEndpoint(any(SnsEndpointParams.class))).thenAnswer(
+ i -> realProvider.resolveEndpoint(i.getArgument(0, SnsEndpointParams.class)));
+
+ ArgumentCaptor paramsCaptor = ArgumentCaptor.forClass(SnsEndpointParams.class);
+
+ Region region = Region.US_WEST_2;
+ SnsHostProvider hostProvider = new SnsHostProvider(region, mockProvider);
+ hostProvider.regionalEndpoint();
+
+ verify(mockProvider).resolveEndpoint(paramsCaptor.capture());
+ assertThat(paramsCaptor.getValue().region()).isEqualTo(region);
+ }
+
+ private static Stream commonNameTestCases() {
+ return Stream.of(
+ // gov regions
+ new CommonNameTestCase("us-gov-west-1", "sns-us-gov-west-1.amazonaws.com"),
+ new CommonNameTestCase("us-gov-east-1", "sns-us-gov-west-1.amazonaws.com"),
+
+ // cn regions
+ new CommonNameTestCase("cn-north-1", "sns-cn-north-1.amazonaws.com.cn"),
+ new CommonNameTestCase("cn-northwest-1", "sns-cn-northwest-1.amazonaws.com.cn"),
+
+ // opt-in regions
+ new CommonNameTestCase("me-south-1", "sns-signing.me-south-1.amazonaws.com"),
+ new CommonNameTestCase("ap-east-1", "sns-signing.ap-east-1.amazonaws.com"),
+ new CommonNameTestCase("me-south-1", "sns-signing.me-south-1.amazonaws.com"),
+ new CommonNameTestCase("ap-east-2", "sns-signing.ap-east-2.amazonaws.com"),
+ new CommonNameTestCase("ap-southeast-5", "sns-signing.ap-southeast-5.amazonaws.com"),
+ new CommonNameTestCase("ap-southeast-6", "sns-signing.ap-southeast-6.amazonaws.com"),
+ new CommonNameTestCase("ap-southeast-7", "sns-signing.ap-southeast-7.amazonaws.com"),
+ new CommonNameTestCase("mx-central-1", "sns-signing.mx-central-1.amazonaws.com"),
+
+ // iso regions
+ new CommonNameTestCase("us-iso-east-1", "sns-us-iso-east-1.c2s.ic.gov"),
+ new CommonNameTestCase("us-isob-east-1", "sns-us-isob-east-1.sc2s.sgov.gov"),
+ new CommonNameTestCase("us-isof-east-1", "sns-signing.us-isof-east-1.csp.hci.ic.gov"),
+ new CommonNameTestCase("us-isof-south-1", "sns-signing.us-isof-south-1.csp.hci.ic.gov"),
+ new CommonNameTestCase("eu-isoe-west-1", "sns-signing.eu-isoe-west-1.cloud.adc-e.uk"),
+
+ //eusc
+ new CommonNameTestCase("eusc-de-east-1", "sns-signing.eusc-de-east-1.amazonaws.eu"),
+
+ // other regions
+ new CommonNameTestCase("us-east-1", "sns.amazonaws.com"),
+ new CommonNameTestCase("us-west-1", "sns.amazonaws.com"),
+
+ // unknown regions
+ new CommonNameTestCase("us-east-9", "sns.amazonaws.com"),
+ new CommonNameTestCase("foo-bar-1", "sns.amazonaws.com"),
+ new CommonNameTestCase("cn-northwest-9", "sns.amazonaws.com.cn")
+
+
+ );
+ }
+
+ private static class CommonNameTestCase {
+ private String region;
+ private String expectedCommonName;
+
+ CommonNameTestCase(String region, String expectedCommonName) {
+ this.region = region;
+ this.expectedCommonName = expectedCommonName;
+ }
+
+ @Override
+ public String toString() {
+ return region + " - " + expectedCommonName;
+ }
+ }
+}
diff --git a/services/sns/src/main/java/software/amazon/awssdk/services/sns/internal/messagemanager/DefaultSnsMessageManager.java b/services/sns/src/main/java/software/amazon/awssdk/services/sns/internal/messagemanager/DefaultSnsMessageManager.java
index ae2b61aa7679..871e9126ab53 100644
--- a/services/sns/src/main/java/software/amazon/awssdk/services/sns/internal/messagemanager/DefaultSnsMessageManager.java
+++ b/services/sns/src/main/java/software/amazon/awssdk/services/sns/internal/messagemanager/DefaultSnsMessageManager.java
@@ -20,7 +20,6 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import software.amazon.awssdk.annotations.SdkInternalApi;
-import software.amazon.awssdk.core.internal.http.loader.DefaultSdkHttpClientBuilder;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.services.sns.messagemanager.MessageManagerConfiguration;
@@ -88,7 +87,7 @@ private DefaultSnsMessageManager(DefaultBuilder builder) {
this.httpClient = configuration.httpClient();
this.shouldCloseHttpClient = false;
} else {
- this.httpClient = new DefaultSdkHttpClientBuilder().buildWithDefaults(createHttpDefaults());
+ this.httpClient = null;
this.shouldCloseHttpClient = true;
}