Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@
<Class name="~software\.amazon\.awssdk\.auth\.credentials\.ContainerCredentialsProvider" />
<Class name="~software\.amazon\.awssdk\.auth\.credentials\.InstanceProfileCredentialsProvider" />

<Class name="~software\.amazon\.awssdk\.messagemanager\.sns\.internal\.SnsHostProvider" />

<!-- test modules are allowed to make blocking call as parts of their testing -->
<Class name="~.*testutils.*" />
<Class name="~.*s3benchmarks.*" />
Expand Down
17 changes: 16 additions & 1 deletion services-custom/sns-message-manager/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,34 @@
<artifactId>sdk-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>regions</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sns</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>http-client-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>endpoints-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${httpcomponents.client5.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// 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();
Copy link
Contributor

@Fred1155 Fred1155 Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If new regions is added and not updated it here (for example we forget), this would silently produce "sns.amazonaws.com". Is this expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by added?

Copy link
Contributor

@Fred1155 Fred1155 Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a region is added in Region.regions()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the region is in the list, it wouldn't enter this code block, it would go through the switch statement and produce sns.amazonaws.com. That's expected behavior

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Yeh I meant to ask the behavior in the switch statement, commented on the wrong line number.

}

String regionId = region.id();

Copy link
Contributor

@Fred1155 Fred1155 Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw there's a TODO comment in v1:
//TODO SNS team will use a consistent pattern for certificate naming. Then remove the special handling based on region
is it worth to port this TODO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK they have implemented that pattern (same for opt-in regions) so it's not necessary.

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";
}
}
}
Original file line number Diff line number Diff line change
@@ -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 {

Check warning on line 33 in services-custom/sns-message-manager/src/test/java/software/amazon/awssdk/messagemanager/sns/internal/SnsHostProviderTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZz-PsgzYHA_WabFWeyu&open=AZz-PsgzYHA_WabFWeyu&pullRequest=6802

@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<SnsEndpointParams> 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<CommonNameTestCase> commonNameTestCases() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ported from the v1 manager tests.

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"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: seems to be dupilcate test case with line 71

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I double checked the v1 test that I copied this from and it's duplicated there too.

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,7 +87,7 @@
this.httpClient = configuration.httpClient();
this.shouldCloseHttpClient = false;
} else {
this.httpClient = new DefaultSdkHttpClientBuilder().buildWithDefaults(createHttpDefaults());
this.httpClient = null;
this.shouldCloseHttpClient = true;
}

Expand Down Expand Up @@ -259,7 +258,7 @@
/**
* Creates HTTP defaults for the SNS message manager.
*/
private static AttributeMap createHttpDefaults() {

Check warning on line 261 in services/sns/src/main/java/software/amazon/awssdk/services/sns/internal/messagemanager/DefaultSnsMessageManager.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused private "createHttpDefaults" method.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZz-PsbAYHA_WabFWeyR&open=AZz-PsbAYHA_WabFWeyR&pullRequest=6802
return AttributeMap.builder()
.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, java.time.Duration.ofSeconds(10))
.put(SdkHttpConfigurationOption.READ_TIMEOUT, java.time.Duration.ofSeconds(30))
Expand Down
Loading