Skip to content

Commit de8a411

Browse files
committed
Fixed an issue where presigned URLs for access point objects could bypass encoding, causing an IllegalArgumentException
1 parent 6e9a0bd commit de8a411

File tree

4 files changed

+65
-8
lines changed

4 files changed

+65
-8
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon S3",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Fixed an issue where presigned URLs for access point objects could bypass encoding, causing an IllegalArgumentException."
6+
}

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919

20-
import java.io.Closeable;
2120
import java.io.IOException;
2221
import java.io.InputStream;
2322
import java.net.HttpURLConnection;
@@ -30,9 +29,7 @@
3029
import org.junit.Before;
3130
import org.junit.BeforeClass;
3231
import org.junit.Test;
33-
3432
import software.amazon.awssdk.awscore.presigner.PresignedRequest;
35-
import software.amazon.awssdk.core.ResponseInputStream;
3633
import software.amazon.awssdk.core.SdkBytes;
3734
import software.amazon.awssdk.core.sync.RequestBody;
3835
import software.amazon.awssdk.http.AbortableInputStream;
@@ -45,8 +42,6 @@
4542
import software.amazon.awssdk.services.s3.model.CompleteMultipartUploadRequest;
4643
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadRequest;
4744
import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse;
48-
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
49-
import software.amazon.awssdk.services.s3.model.ListMultipartUploadsResponse;
5045
import software.amazon.awssdk.services.s3.model.MultipartUpload;
5146
import software.amazon.awssdk.services.s3.model.RequestPayer;
5247
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
@@ -116,7 +111,7 @@ public void bucketsWithScaryCharactersWorks() throws IOException {
116111

117112
@Test
118113
public void keysWithScaryCharactersWorks() throws IOException {
119-
String scaryObjectKey = testGetObjectKey + " !'/()~`";
114+
String scaryObjectKey = "a0A!-_.*'()&@:,$=+?; \n\\^`<>{}[]#%\"~|山/" + testGetObjectKey;
120115
S3TestUtils.putObject(S3PresignerIntegrationTest.class, client, testBucket, scaryObjectKey, testObjectContent);
121116

122117
assertThatPresigningWorks(testBucket, scaryObjectKey);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import software.amazon.awssdk.services.s3.internal.resource.S3Resource;
4040
import software.amazon.awssdk.services.s3.internal.resource.S3ResourceType;
4141
import software.amazon.awssdk.utils.Validate;
42+
import software.amazon.awssdk.utils.http.SdkHttpUtils;
4243

4344
/**
4445
* Returns a new configured HTTP request with a resolved access point endpoint and signing overrides.
@@ -107,7 +108,7 @@ private String buildPath(URI accessPointUri, S3EndpointResolverContext context)
107108
if (pathBuilder.length() > 0) {
108109
pathBuilder.append('/');
109110
}
110-
pathBuilder.append(key);
111+
pathBuilder.append(SdkHttpUtils.urlEncodeIgnoreSlashes(key));
111112
}
112113
return pathBuilder.length() > 0 ? pathBuilder.toString() : null;
113114
}
@@ -117,7 +118,6 @@ private String validateConfiguration(S3EndpointResolverContext context, S3Resour
117118
String arnRegion = s3Resource.region().orElseThrow(() -> new IllegalArgumentException(
118119
"An S3 access point ARN must have a region"));
119120

120-
121121
S3Configuration serviceConfiguration = context.serviceConfiguration();
122122
if (isAccelerateEnabled(serviceConfiguration)) {
123123
throw new IllegalArgumentException("An access point ARN cannot be passed as a bucket parameter to an S3 "

services/s3control/src/it/java/software.amazon.awssdk.services.s3control/S3AccessPointsIntegrationTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,28 @@
1717
import static org.assertj.core.api.Assertions.assertThat;
1818
import static org.junit.Assert.assertNotNull;
1919
import static software.amazon.awssdk.testutils.service.S3BucketUtils.temporaryBucketName;
20+
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
2021

22+
import java.io.IOException;
23+
import java.time.Duration;
2124
import java.util.StringJoiner;
2225
import org.junit.AfterClass;
2326
import org.junit.BeforeClass;
2427
import org.junit.Test;
2528
import software.amazon.awssdk.core.sync.RequestBody;
29+
import software.amazon.awssdk.http.HttpExecuteRequest;
30+
import software.amazon.awssdk.http.HttpExecuteResponse;
31+
import software.amazon.awssdk.http.SdkHttpClient;
32+
import software.amazon.awssdk.http.SdkHttpRequest;
33+
import software.amazon.awssdk.http.apache.ApacheHttpClient;
2634
import software.amazon.awssdk.regions.Region;
2735
import software.amazon.awssdk.services.s3.S3Client;
2836
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
2937
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
38+
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
3039
import software.amazon.awssdk.services.sts.StsClient;
40+
import software.amazon.awssdk.utils.IoUtils;
41+
import software.amazon.awssdk.utils.StringInputStream;
3142

3243
public class S3AccessPointsIntegrationTest extends S3ControlIntegrationTestBase {
3344

@@ -108,6 +119,51 @@ public void transfer_Succeeds_UsingAccessPoint_CrossRegion() {
108119
assertThat(objectContent).isEqualTo("helloworld");
109120
}
110121

122+
@Test
123+
public void uploadAndDownloadWithPresignedUrlWorks() throws IOException {
124+
String accessPointArn = new StringJoiner(":").add("arn").add("aws").add("s3").add("us-west-2").add(accountId)
125+
.add("accesspoint").add(AP_NAME).toString();
126+
String key = "foo/a0A!-_.*'()&@:,$=+?; \n\\^`<>{}[]#%\"~|山";
127+
128+
testAccessPointPresigning(accessPointArn, key);
129+
}
130+
131+
private void testAccessPointPresigning(String accessPointArn, String key) throws IOException {
132+
String data = "Hello";
133+
134+
S3Presigner presigner = S3Presigner.builder().region(Region.US_WEST_2).build();
135+
136+
SdkHttpRequest presignedPut = presigner.presignPutObject(r -> r.signatureDuration(Duration.ofDays(7))
137+
.putObjectRequest(por -> por.bucket(accessPointArn)
138+
.key(key)))
139+
.httpRequest();
140+
141+
142+
SdkHttpRequest presignedGet = presigner.presignGetObject(r -> r.signatureDuration(Duration.ofDays(7))
143+
.getObjectRequest(gor -> gor.bucket(accessPointArn)
144+
.key(key)))
145+
.httpRequest();
146+
147+
try (SdkHttpClient client = ApacheHttpClient.create()) {
148+
client.prepareRequest(HttpExecuteRequest.builder()
149+
.request(presignedPut)
150+
.contentStreamProvider(() -> new StringInputStream(data))
151+
.build())
152+
.call();
153+
154+
HttpExecuteResponse getResult = client.prepareRequest(HttpExecuteRequest.builder()
155+
.request(presignedGet)
156+
.build())
157+
.call();
158+
159+
String result = getResult.responseBody()
160+
.map(stream -> invokeSafely(() -> IoUtils.toUtf8String(stream)))
161+
.orElseThrow(AssertionError::new);
162+
163+
assertThat(result).isEqualTo(data);
164+
}
165+
}
166+
111167
@Test
112168
public void accessPointOperation_nonArns() {
113169
assertNotNull(s3control.listAccessPoints(b -> b.bucket(BUCKET).accountId(accountId).maxResults(1)));

0 commit comments

Comments
 (0)