Skip to content

Commit 1d2d644

Browse files
committed
S3: Interacting with an access point in a different region to the one the S3 client is configured for will no longer result in the request being signed for the wrong region and rejected by S3.
1 parent d96b688 commit 1d2d644

File tree

7 files changed

+198
-26
lines changed

7 files changed

+198
-26
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Amazon S3",
4+
"description": "Interacting with an access point in a different region to the one the S3 client is configured for will no longer result in the request being signed for the wrong region and rejected by S3."
5+
}

services/s3/src/it/java/software/amazon/awssdk/services/s3/AccessPointsIntegrationTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,25 @@ public void transfer_Succeeds_UsingAccessPoint() {
8484

8585
assertThat(objectContent).isEqualTo("helloworld");
8686
}
87+
88+
@Test
89+
public void transfer_Succeeds_UsingAccessPoint_CrossRegion() {
90+
S3Client s3DifferentRegion =
91+
s3ClientBuilder().region(Region.US_EAST_1).serviceConfiguration(c -> c.useArnRegionEnabled(true)).build();
92+
93+
StringJoiner apArn = new StringJoiner(":");
94+
apArn.add("arn").add("aws").add("s3").add("us-west-2").add(accountId).add("accesspoint").add(AP_NAME);
95+
96+
s3DifferentRegion.putObject(PutObjectRequest.builder()
97+
.bucket(apArn.toString())
98+
.key(KEY)
99+
.build(), RequestBody.fromString("helloworld"));
100+
101+
String objectContent = s3DifferentRegion.getObjectAsBytes(GetObjectRequest.builder()
102+
.bucket(apArn.toString())
103+
.key(KEY)
104+
.build()).asUtf8String();
105+
106+
assertThat(objectContent).isEqualTo("helloworld");
107+
}
87108
}

services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.net.URI;
2020
import java.net.URL;
2121
import java.util.function.Consumer;
22+
2223
import software.amazon.awssdk.annotations.Immutable;
2324
import software.amazon.awssdk.annotations.SdkInternalApi;
2425
import software.amazon.awssdk.annotations.SdkPublicApi;
@@ -154,7 +155,8 @@ public URL getUrl(GetUrlRequest getUrlRequest) {
154155
getObjectRequest,
155156
resolvedRegion,
156157
s3Configuration,
157-
endpointOverridden);
158+
endpointOverridden)
159+
.sdkHttpRequest();
158160

159161
try {
160162
return httpRequest.getUri().toURL();
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2010-2019 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.services.s3.internal;
17+
18+
import java.util.Optional;
19+
20+
import software.amazon.awssdk.annotations.SdkInternalApi;
21+
import software.amazon.awssdk.http.SdkHttpRequest;
22+
import software.amazon.awssdk.regions.Region;
23+
import software.amazon.awssdk.utils.Validate;
24+
import software.amazon.awssdk.utils.builder.CopyableBuilder;
25+
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
26+
27+
@SdkInternalApi
28+
public class ConfiguredS3SdkHttpRequest
29+
implements ToCopyableBuilder<ConfiguredS3SdkHttpRequest.Builder, ConfiguredS3SdkHttpRequest> {
30+
private final SdkHttpRequest sdkHttpRequest;
31+
private final Region signingRegionModification;
32+
33+
private ConfiguredS3SdkHttpRequest(Builder builder) {
34+
this.sdkHttpRequest = Validate.notNull(builder.sdkHttpRequest, "sdkHttpRequest");
35+
this.signingRegionModification = builder.signingRegionModification;
36+
}
37+
38+
public static Builder builder() {
39+
return new Builder();
40+
}
41+
42+
public SdkHttpRequest sdkHttpRequest() {
43+
return sdkHttpRequest;
44+
}
45+
46+
public Optional<Region> signingRegionModification() {
47+
return Optional.ofNullable(signingRegionModification);
48+
}
49+
50+
@Override
51+
public Builder toBuilder() {
52+
return builder().sdkHttpRequest(sdkHttpRequest).signingRegionModification(signingRegionModification);
53+
}
54+
55+
@Override
56+
public boolean equals(Object o) {
57+
if (this == o) {
58+
return true;
59+
}
60+
if (o == null || getClass() != o.getClass()) {
61+
return false;
62+
}
63+
64+
ConfiguredS3SdkHttpRequest that = (ConfiguredS3SdkHttpRequest) o;
65+
66+
if (sdkHttpRequest != null ? ! sdkHttpRequest.equals(that.sdkHttpRequest) : that.sdkHttpRequest != null) {
67+
return false;
68+
}
69+
return signingRegionModification != null ?
70+
signingRegionModification.equals(that.signingRegionModification)
71+
: that.signingRegionModification == null;
72+
}
73+
74+
@Override
75+
public int hashCode() {
76+
int result = sdkHttpRequest != null ? sdkHttpRequest.hashCode() : 0;
77+
result = 31 * result + (signingRegionModification != null ? signingRegionModification.hashCode() : 0);
78+
return result;
79+
}
80+
81+
public static class Builder implements CopyableBuilder<Builder, ConfiguredS3SdkHttpRequest> {
82+
private SdkHttpRequest sdkHttpRequest;
83+
private Region signingRegionModification;
84+
85+
private Builder() {}
86+
87+
public Builder sdkHttpRequest(SdkHttpRequest sdkHttpRequest) {
88+
this.sdkHttpRequest = sdkHttpRequest;
89+
return this;
90+
}
91+
92+
public Builder signingRegionModification(Region signingRegionModification) {
93+
this.signingRegionModification = signingRegionModification;
94+
return this;
95+
}
96+
97+
@Override
98+
public ConfiguredS3SdkHttpRequest build() {
99+
return new ConfiguredS3SdkHttpRequest(this);
100+
}
101+
}
102+
103+
}

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/S3EndpointUtils.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.net.URISyntaxException;
2222
import java.util.Arrays;
2323
import java.util.List;
24+
2425
import software.amazon.awssdk.annotations.SdkInternalApi;
2526
import software.amazon.awssdk.arns.Arn;
2627
import software.amazon.awssdk.core.SdkRequest;
@@ -56,11 +57,11 @@ private S3EndpointUtils() {
5657
* Returns a new instance of the given {@link SdkHttpRequest} by applying any endpoint changes based on
5758
* the given {@link S3Configuration} options.
5859
*/
59-
public static SdkHttpRequest applyEndpointConfiguration(SdkHttpRequest request,
60-
SdkRequest originalRequest,
61-
Region region,
62-
S3Configuration serviceConfiguration,
63-
boolean endpointOverridden) {
60+
public static ConfiguredS3SdkHttpRequest applyEndpointConfiguration(SdkHttpRequest request,
61+
SdkRequest originalRequest,
62+
Region region,
63+
S3Configuration serviceConfiguration,
64+
boolean endpointOverridden) {
6465
String bucketName = originalRequest.getValueForField("Bucket", String.class).orElse(null);
6566
String key = originalRequest.getValueForField("Key", String.class).orElse(null);
6667

@@ -82,15 +83,19 @@ public static SdkHttpRequest applyEndpointConfiguration(SdkHttpRequest request,
8283
}
8384
}
8485

85-
return mutableRequest.build();
86+
return ConfiguredS3SdkHttpRequest.builder()
87+
.sdkHttpRequest(mutableRequest.build())
88+
.build();
8689
}
8790

88-
private static SdkHttpRequest applyEndpointConfigurationForAccessPointArn(SdkHttpRequest request,
89-
Region region,
90-
boolean endpointOverridden,
91-
S3Configuration serviceConfiguration,
92-
String bucketName,
93-
String key) {
91+
private static ConfiguredS3SdkHttpRequest applyEndpointConfigurationForAccessPointArn(
92+
SdkHttpRequest request,
93+
Region region,
94+
boolean endpointOverridden,
95+
S3Configuration serviceConfiguration,
96+
String bucketName,
97+
String key) {
98+
9499
Arn resourceArn = Arn.fromString(bucketName);
95100
S3Resource s3Resource = S3ArnConverter.create().convertArn(resourceArn);
96101

@@ -169,12 +174,17 @@ private static SdkHttpRequest applyEndpointConfigurationForAccessPointArn(SdkHtt
169174
.dualstackEnabled(dualstackEnabled)
170175
.toUri();
171176

172-
return request.toBuilder()
173-
.protocol(accessPointUri.getScheme())
174-
.host(accessPointUri.getHost())
175-
.port(accessPointUri.getPort())
176-
.encodedPath(key)
177-
.build();
177+
SdkHttpRequest httpRequest = request.toBuilder()
178+
.protocol(accessPointUri.getScheme())
179+
.host(accessPointUri.getHost())
180+
.port(accessPointUri.getPort())
181+
.encodedPath(key)
182+
.build();
183+
184+
return ConfiguredS3SdkHttpRequest.builder()
185+
.sdkHttpRequest(httpRequest)
186+
.signingRegionModification(Region.of(arnRegion))
187+
.build();
178188
}
179189

180190
/**

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/EndpointAddressInterceptor.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
2525
import software.amazon.awssdk.http.SdkHttpRequest;
2626
import software.amazon.awssdk.services.s3.S3Configuration;
27+
import software.amazon.awssdk.services.s3.internal.ConfiguredS3SdkHttpRequest;
2728
import software.amazon.awssdk.services.s3.internal.S3EndpointUtils;
2829

2930
@SdkInternalApi
@@ -32,11 +33,17 @@ public final class EndpointAddressInterceptor implements ExecutionInterceptor {
3233
@Override
3334
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context,
3435
ExecutionAttributes executionAttributes) {
35-
return S3EndpointUtils.applyEndpointConfiguration(
36-
context.httpRequest(),
37-
context.request(),
38-
executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION),
39-
(S3Configuration) executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_CONFIG),
40-
Boolean.TRUE.equals(executionAttributes.getAttribute(SdkExecutionAttribute.ENDPOINT_OVERRIDDEN)));
36+
ConfiguredS3SdkHttpRequest configuredRequest =
37+
S3EndpointUtils.applyEndpointConfiguration(
38+
context.httpRequest(),
39+
context.request(),
40+
executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION),
41+
(S3Configuration) executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_CONFIG),
42+
Boolean.TRUE.equals(executionAttributes.getAttribute(SdkExecutionAttribute.ENDPOINT_OVERRIDDEN)));
43+
44+
configuredRequest.signingRegionModification().ifPresent(
45+
region -> executionAttributes.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region));
46+
47+
return configuredRequest.sdkHttpRequest();
4148
}
4249
}

0 commit comments

Comments
 (0)