Skip to content

Commit 897bbde

Browse files
authored
Fix handling of FIPS pseudo regions (#3460)
This commit adds support for automatically transforming a FIPS pesudo region (e.g. "fips-us-west-2"), into a standard region name + setting the fips_enabled flag to true.
1 parent cacdb6d commit 897bbde

File tree

6 files changed

+204
-10
lines changed

6 files changed

+204
-10
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Adds support for automatically transforming a FIPS pesudo region (e.g. \"fips-us-west-2\"), into a standard region name + setting the `fips_enabled` flag to `true`."
6+
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import software.amazon.awssdk.utils.AttributeMap;
5656
import software.amazon.awssdk.utils.CollectionUtils;
5757
import software.amazon.awssdk.utils.Logger;
58+
import software.amazon.awssdk.utils.Pair;
59+
import software.amazon.awssdk.utils.StringUtils;
5860

5961
/**
6062
* An SDK-internal implementation of the methods in {@link AwsClientBuilder}, {@link AwsAsyncClientBuilder} and
@@ -80,6 +82,9 @@ public abstract class AwsDefaultClientBuilder<BuilderT extends AwsClientBuilder<
8082
implements AwsClientBuilder<BuilderT, ClientT> {
8183
private static final Logger log = Logger.loggerFor(AwsClientBuilder.class);
8284
private static final String DEFAULT_ENDPOINT_PROTOCOL = "https";
85+
private static final String[] FIPS_SEARCH = {"fips-", "-fips"};
86+
private static final String[] FIPS_REPLACE = {"", ""};
87+
8388
private final AutoDefaultsModeDiscovery autoDefaultsModeDiscovery;
8489

8590
protected AwsDefaultClientBuilder() {
@@ -374,7 +379,17 @@ private RetryPolicy resolveAwsRetryPolicy(SdkClientConfiguration config) {
374379

375380
@Override
376381
public final BuilderT region(Region region) {
377-
clientConfiguration.option(AwsClientOption.AWS_REGION, region);
382+
Region regionToSet = region;
383+
Boolean fipsEnabled = null;
384+
385+
if (region != null) {
386+
Pair<Region, Optional<Boolean>> transformedRegion = transformFipsPseudoRegionIfNecessary(region);
387+
regionToSet = transformedRegion.left();
388+
fipsEnabled = transformedRegion.right().orElse(null);
389+
}
390+
391+
clientConfiguration.option(AwsClientOption.AWS_REGION, regionToSet);
392+
clientConfiguration.option(AwsClientOption.FIPS_ENDPOINT_ENABLED, fipsEnabled);
378393
return thisBuilder();
379394
}
380395

@@ -433,4 +448,20 @@ public final BuilderT defaultsMode(DefaultsMode defaultsMode) {
433448
public final void setDefaultsMode(DefaultsMode defaultsMode) {
434449
defaultsMode(defaultsMode);
435450
}
451+
452+
/**
453+
* If the region is a FIPS pseudo region (contains "fips"), this method returns a pair of values, the left side being the
454+
* region with the "fips" string removed, and the right being {@code true}. Otherwise, the region is returned
455+
* unchanged, and the right will be empty.
456+
*/
457+
private static Pair<Region, Optional<Boolean>> transformFipsPseudoRegionIfNecessary(Region region) {
458+
String id = region.id();
459+
String newId = StringUtils.replaceEach(id, FIPS_SEARCH, FIPS_REPLACE);
460+
if (!newId.equals(id)) {
461+
log.info(() -> String.format("Replacing input region %s with %s and setting fipsEnabled to true", id, newId));
462+
return Pair.of(Region.of(newId), Optional.of(true));
463+
}
464+
465+
return Pair.of(region, Optional.empty());
466+
}
436467
}

core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import org.mockito.junit.MockitoJUnitRunner;
4343
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
4444
import software.amazon.awssdk.auth.signer.Aws4Signer;
45-
import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
45+
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
4646
import software.amazon.awssdk.awscore.internal.defaultsmode.AutoDefaultsModeDiscovery;
4747
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
4848
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
@@ -104,6 +104,17 @@ public void buildWithRegionShouldHaveCorrectEndpointAndSigningRegion() {
104104
assertThat(client.clientConfiguration.option(SERVICE_SIGNING_NAME)).isEqualTo(SIGNING_NAME);
105105
}
106106

107+
@Test
108+
public void buildWithFipsRegionThenNonFipsFipsEnabledFlagUnset() {
109+
TestClient client = testClientBuilder()
110+
.region(Region.of("us-west-2-fips")) // first call to setter sets the flag
111+
.region(Region.of("us-west-2"))// second call should clear
112+
.build();
113+
114+
assertThat(client.clientConfiguration.option(AwsClientOption.AWS_REGION)).isEqualTo(Region.US_WEST_2);
115+
assertThat(client.clientConfiguration.option(AwsClientOption.FIPS_ENDPOINT_ENABLED)).isNull();
116+
}
117+
107118
@Test
108119
public void buildWithEndpointShouldHaveCorrectEndpointAndSigningRegion() {
109120
TestClient client = testClientBuilder().region(Region.US_WEST_1).endpointOverride(ENDPOINT).build();
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.awscore.client.builder;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.ArgumentMatchers.any;
20+
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.when;
22+
import static software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption.ENABLE_DEFAULT_REGION_DETECTION;
23+
import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.SIGNER;
24+
25+
import java.time.Duration;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.MethodSource;
31+
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
32+
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
33+
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
34+
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
35+
import software.amazon.awssdk.core.signer.Signer;
36+
import software.amazon.awssdk.http.SdkHttpClient;
37+
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
38+
import software.amazon.awssdk.regions.Region;
39+
import software.amazon.awssdk.utils.AttributeMap;
40+
41+
public class FipsPseudoRegionTest {
42+
private static final AttributeMap MOCK_DEFAULTS = AttributeMap
43+
.builder()
44+
.put(SdkHttpConfigurationOption.READ_TIMEOUT, Duration.ofSeconds(10))
45+
.build();
46+
47+
private static final String ENDPOINT_PREFIX = "s3";
48+
private static final String SIGNING_NAME = "demo";
49+
private static final String SERVICE_NAME = "Demo";
50+
51+
private SdkHttpClient.Builder defaultHttpClientBuilder;
52+
53+
54+
@BeforeEach
55+
public void setup() {
56+
defaultHttpClientBuilder = mock(SdkHttpClient.Builder.class);
57+
when(defaultHttpClientBuilder.buildWithDefaults(any())).thenReturn(mock(SdkHttpClient.class));
58+
}
59+
60+
@ParameterizedTest
61+
@MethodSource("testCases")
62+
public void verifyRegion(TestCase tc) {
63+
TestClient client = testClientBuilder().region(tc.inputRegion).build();
64+
65+
assertThat(client.clientConfiguration.option(AwsClientOption.AWS_REGION)).isEqualTo(tc.expectedClientRegion);
66+
assertThat(client.clientConfiguration.option(AwsClientOption.FIPS_ENDPOINT_ENABLED)).isTrue();
67+
}
68+
69+
public static List<TestCase> testCases() {
70+
List<TestCase> testCases = new ArrayList<>();
71+
72+
testCases.add(new TestCase(Region.of("fips-us-west-2"), Region.of("us-west-2")));
73+
testCases.add(new TestCase(Region.of("us-west-2-fips"), Region.of("us-west-2")));
74+
testCases.add(new TestCase(Region.of("rekognition-fips.us-west-2"), Region.of("rekognition.us-west-2")));
75+
testCases.add(new TestCase(Region.of("rekognition.fips-us-west-2"), Region.of("rekognition.us-west-2")));
76+
testCases.add(new TestCase(Region.of("query-fips-us-west-2"), Region.of("query-us-west-2")));
77+
testCases.add(new TestCase(Region.of("fips-fips-us-west-2"), Region.of("us-west-2")));
78+
testCases.add(new TestCase(Region.of("fips-us-west-2-fips"), Region.of("us-west-2")));
79+
80+
81+
return testCases;
82+
}
83+
84+
private AwsClientBuilder<TestClientBuilder, TestClient> testClientBuilder() {
85+
ClientOverrideConfiguration overrideConfig =
86+
ClientOverrideConfiguration.builder()
87+
.putAdvancedOption(SIGNER, mock(Signer.class))
88+
.putAdvancedOption(ENABLE_DEFAULT_REGION_DETECTION, false)
89+
.build();
90+
91+
return new TestClientBuilder().credentialsProvider(AnonymousCredentialsProvider.create())
92+
.overrideConfiguration(overrideConfig);
93+
}
94+
95+
private static class TestCase {
96+
private Region inputRegion;
97+
private Region expectedClientRegion;
98+
99+
public TestCase(Region inputRegion, Region expectedClientRegion) {
100+
this.inputRegion = inputRegion;
101+
this.expectedClientRegion = expectedClientRegion;
102+
}
103+
}
104+
105+
private static class TestClient {
106+
private final SdkClientConfiguration clientConfiguration;
107+
108+
public TestClient(SdkClientConfiguration clientConfiguration) {
109+
this.clientConfiguration = clientConfiguration;
110+
}
111+
}
112+
113+
private class TestClientBuilder extends AwsDefaultClientBuilder<TestClientBuilder, TestClient>
114+
implements AwsClientBuilder<TestClientBuilder, TestClient> {
115+
116+
public TestClientBuilder() {
117+
super(defaultHttpClientBuilder, null, null);
118+
}
119+
120+
@Override
121+
protected TestClient buildClient() {
122+
return new TestClient(super.syncClientConfiguration());
123+
}
124+
125+
@Override
126+
protected String serviceEndpointPrefix() {
127+
return ENDPOINT_PREFIX;
128+
}
129+
130+
@Override
131+
protected String signingName() {
132+
return SIGNING_NAME;
133+
}
134+
135+
@Override
136+
protected String serviceName() {
137+
return SERVICE_NAME;
138+
}
139+
140+
@Override
141+
protected AttributeMap serviceHttpConfig() {
142+
return MOCK_DEFAULTS;
143+
}
144+
}
145+
}

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/endpoints/S3AccessPointEndpointResolver.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private void validateConfiguration(S3EndpointResolverContext context, S3AccessPo
125125
if (s3Resource.region().isPresent()) {
126126
validateRegion(s3Resource, serviceConfig, clientRegion, context.fipsEnabled());
127127
} else {
128-
validateGlobalConfiguration(serviceConfig, clientRegion);
128+
validateGlobalConfiguration(context);
129129
}
130130
validatePartition(s3Resource, clientRegion);
131131
}
@@ -170,15 +170,16 @@ private boolean clientRegionDiffersFromArnRegion(Region clientRegion, String arn
170170
return !removeFipsIfNeeded(clientRegion.id()).equals(arnRegion);
171171
}
172172

173-
private void validateGlobalConfiguration(S3Configuration serviceConfiguration, Region region) {
173+
private void validateGlobalConfiguration(S3EndpointResolverContext context) {
174+
S3Configuration serviceConfiguration = context.serviceConfiguration();
174175
Validate.isTrue(serviceConfiguration.multiRegionEnabled(), "An Access Point ARN without a region value was passed as "
175176
+ "a bucket parameter but multi-region is disabled. Check "
176177
+ "client configuration, environment variables and system "
177178
+ "configuration for multi-region disable configurations.");
178179

179180
Validate.isFalse(isDualstackEnabled(serviceConfiguration), S3_CONFIG_ERROR_MESSAGE,
180181
"dualstack, if the ARN contains no region.");
181-
Validate.isFalse(isFipsRegion(region.toString()), S3_CONFIG_ERROR_MESSAGE,
182+
Validate.isFalse(isFipsEnabled(context), S3_CONFIG_ERROR_MESSAGE,
182183
"a FIPS enabled region, if the ARN contains no region.");
183184
}
184185

@@ -275,7 +276,7 @@ private static Optional<String> resolveSigningService(S3Resource resource) {
275276
return Optional.empty();
276277
}
277278

278-
private boolean isFipsEnabled(S3EndpointResolverContext context) {
279+
private static boolean isFipsEnabled(S3EndpointResolverContext context) {
279280
return context.fipsEnabled() || isFipsRegion(context.region().toString());
280281
}
281282

services/s3/src/test/java/software/amazon/awssdk/services/s3/S3EndpointResolutionTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ public void accessPointArn_NonFips_usGovEast1_region_FipsUsGovEast1_useArnRegion
748748
mockHttpClient.stubNextResponse(mockListObjectsResponse());
749749
S3Client s3Client = clientBuilder().region(Region.of("fips-us-gov-east-1"))
750750
.serviceConfiguration(S3Configuration.builder().useArnRegionEnabled(false).build()).build();
751-
String accessPointArn = "arn:aws:s3:us-gov-east-1:123456789012:accesspoint:foobar";
751+
String accessPointArn = "arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:foobar";
752752
s3Client.getObject(GetObjectRequest.builder().bucket(accessPointArn).key("someKey").build());
753753
assertThat(mockHttpClient.getLastRequest().getUri().getHost())
754754
.isEqualTo("foobar-123456789012.s3-accesspoint-fips.us-gov-east-1.amazonaws.com");
@@ -759,7 +759,7 @@ public void accessPointArn_NonFips_usGovEast1_region_FipsUsGovEast1_useArnRegion
759759
mockHttpClient.stubNextResponse(mockListObjectsResponse());
760760
S3Client s3Client = clientBuilder().region(Region.of("fips-us-gov-east-1"))
761761
.serviceConfiguration(S3Configuration.builder().useArnRegionEnabled(true).build()).build();
762-
String accessPointArn = "arn:aws:s3:us-gov-east-1:123456789012:accesspoint:foobar";
762+
String accessPointArn = "arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:foobar";
763763
s3Client.getObject(GetObjectRequest.builder().bucket(accessPointArn).key("someKey").build());
764764
assertThat(mockHttpClient.getLastRequest().getUri().getHost())
765765
.isEqualTo("foobar-123456789012.s3-accesspoint-fips.us-gov-east-1.amazonaws.com");
@@ -776,15 +776,15 @@ public void accessPointArn_usGovWest1_clientRegion_FipsUsGovEast1_useArnRegionTr
776776
.isInstanceOf(IllegalArgumentException.class)
777777
.hasMessageContaining("The region field of the ARN being passed as a bucket parameter to an S3 operation does not "
778778
+ "match the region the client was configured with. Cross region access not allowed for fips "
779-
+ "region in client or arn. Provided region: 'us-west-1'; client region:'fips-us-gov-east-1'.");
779+
+ "region in client or arn. Provided region: 'us-west-1'; client region:'us-gov-east-1'.");
780780
}
781781

782782
@Test
783783
public void accessPointArn_NonFips_usGovWest1_region_FipsUsGovEast1_useArnRegionTrue_dualStackEnabled() throws Exception {
784784
mockHttpClient.stubNextResponse(mockListObjectsResponse());
785785
S3Client s3Client = clientBuilder().region(Region.of("fips-us-gov-east-1"))
786786
.serviceConfiguration(S3Configuration.builder().useArnRegionEnabled(true).dualstackEnabled(true).build()).build();
787-
String accessPointArn = "arn:aws:s3:us-gov-east-1:123456789012:accesspoint:foobar";
787+
String accessPointArn = "arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint:foobar";
788788
s3Client.getObject(GetObjectRequest.builder().bucket(accessPointArn).key("someKey").build());
789789
assertThat(mockHttpClient.getLastRequest().getUri().getHost())
790790
.isEqualTo("foobar-123456789012.s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com");

0 commit comments

Comments
 (0)