Skip to content

Commit 87298fd

Browse files
committed
repro
1 parent 53dc99d commit 87298fd

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package software.amazon.encryption.s3;
4+
5+
import org.junitpioneer.jupiter.RetryingTest;
6+
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
7+
import software.amazon.awssdk.core.async.AsyncRequestBody;
8+
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
9+
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
10+
import software.amazon.awssdk.core.interceptor.Context;
11+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
12+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
13+
import software.amazon.awssdk.core.ResponseBytes;
14+
import software.amazon.awssdk.http.SdkHttpMethod;
15+
import software.amazon.awssdk.http.SdkHttpRequest;
16+
import software.amazon.awssdk.regions.Region;
17+
import software.amazon.awssdk.services.s3.S3AsyncClient;
18+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
19+
20+
import javax.crypto.KeyGenerator;
21+
import javax.crypto.SecretKey;
22+
import java.security.NoSuchAlgorithmException;
23+
import java.util.Optional;
24+
import java.util.concurrent.atomic.AtomicReference;
25+
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertNotNull;
28+
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET;
30+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.S3_REGION;
31+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix;
32+
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject;
33+
34+
/**
35+
* Verifies that the S3EC preserves the per-request {@link AwsRequestOverrideConfiguration} set by
36+
* the caller. The client adds its own API name to every request's override configuration; these
37+
* tests ensure that doing so does not discard a caller-supplied override configuration (such as a
38+
* custom header, credentials provider, or signer override).
39+
* <p>
40+
* Each test attaches a custom header via the request-level override configuration and inspects the
41+
* request that actually reaches the wrapped client using an {@link ExecutionInterceptor}.
42+
*/
43+
public class S3EncryptionClientRequestOverrideConfigurationTest {
44+
45+
private static final String CUSTOM_HEADER_NAME = "x-amz-meta-s3ec-override-repro";
46+
private static final String CUSTOM_HEADER_VALUE = "custom-value";
47+
48+
private static SecretKey AES_KEY;
49+
50+
private static SecretKey aesKey() throws NoSuchAlgorithmException {
51+
if (AES_KEY == null) {
52+
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
53+
keyGen.init(256);
54+
AES_KEY = keyGen.generateKey();
55+
}
56+
return AES_KEY;
57+
}
58+
59+
/**
60+
* Captures the outgoing HTTP request for a given method so the test can assert on the
61+
* headers that actually reach the wire.
62+
*/
63+
private static class CapturingInterceptor implements ExecutionInterceptor {
64+
private final SdkHttpMethod methodToCapture;
65+
private final AtomicReference<SdkHttpRequest> capturedRequest = new AtomicReference<>();
66+
67+
CapturingInterceptor(SdkHttpMethod methodToCapture) {
68+
this.methodToCapture = methodToCapture;
69+
}
70+
71+
@Override
72+
public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) {
73+
SdkHttpRequest request = context.httpRequest();
74+
if (request.method() == methodToCapture) {
75+
capturedRequest.set(request);
76+
}
77+
}
78+
79+
SdkHttpRequest captured() {
80+
return capturedRequest.get();
81+
}
82+
}
83+
84+
@RetryingTest(3)
85+
public void putObjectPreservesRequestOverrideConfiguration() throws NoSuchAlgorithmException {
86+
final String objectKey = appendTestSuffix("override-config-repro-put");
87+
88+
CapturingInterceptor interceptor = new CapturingInterceptor(SdkHttpMethod.PUT);
89+
S3AsyncClient wrappedAsyncClient = S3AsyncClient.builder()
90+
.region(Region.of(S3_REGION.toString()))
91+
.overrideConfiguration(ClientOverrideConfiguration.builder()
92+
.addExecutionInterceptor(interceptor)
93+
.build())
94+
.build();
95+
96+
S3AsyncClient s3Client = S3AsyncEncryptionClient.builderV4()
97+
.wrappedClient(wrappedAsyncClient)
98+
.aesKey(aesKey())
99+
.build();
100+
101+
final String input = "PutObjectOverrideConfig";
102+
final AwsRequestOverrideConfiguration overrideConfig = AwsRequestOverrideConfiguration.builder()
103+
.putHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE)
104+
.build();
105+
106+
try {
107+
s3Client.putObject(builder -> builder
108+
.bucket(BUCKET)
109+
.key(objectKey)
110+
.overrideConfiguration(overrideConfig)
111+
.build(),
112+
AsyncRequestBody.fromString(input)).join();
113+
114+
SdkHttpRequest sentRequest = interceptor.captured();
115+
assertNotNull(sentRequest, "No PutObject request was captured by the interceptor");
116+
117+
// The S3EC API name must still be present (existing behavior must be preserved).
118+
Optional<String> userAgent = sentRequest.firstMatchingHeader("User-Agent");
119+
assertTrue(userAgent.isPresent() && userAgent.get().contains("AmazonS3Encrypt"),
120+
"Expected the S3EC API name to be present in the User-Agent header");
121+
122+
// The caller-provided override configuration (custom header) must NOT be dropped.
123+
Optional<String> customHeader = sentRequest.firstMatchingHeader(CUSTOM_HEADER_NAME);
124+
assertTrue(customHeader.isPresent(),
125+
"Caller-provided request override configuration (custom header) was dropped on PutObject");
126+
assertEquals(CUSTOM_HEADER_VALUE, customHeader.get());
127+
} finally {
128+
deleteObject(BUCKET, objectKey, s3Client);
129+
s3Client.close();
130+
wrappedAsyncClient.close();
131+
}
132+
}
133+
134+
@RetryingTest(3)
135+
public void getObjectPreservesRequestOverrideConfiguration() throws NoSuchAlgorithmException {
136+
final String objectKey = appendTestSuffix("override-config-repro-get");
137+
138+
CapturingInterceptor interceptor = new CapturingInterceptor(SdkHttpMethod.GET);
139+
S3AsyncClient wrappedAsyncClient = S3AsyncClient.builder()
140+
.region(Region.of(S3_REGION.toString()))
141+
.overrideConfiguration(ClientOverrideConfiguration.builder()
142+
.addExecutionInterceptor(interceptor)
143+
.build())
144+
.build();
145+
146+
S3AsyncClient s3Client = S3AsyncEncryptionClient.builderV4()
147+
.wrappedClient(wrappedAsyncClient)
148+
.aesKey(aesKey())
149+
.build();
150+
151+
final String input = "GetObjectOverrideConfig";
152+
final AwsRequestOverrideConfiguration overrideConfig = AwsRequestOverrideConfiguration.builder()
153+
.putHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE)
154+
.build();
155+
156+
try {
157+
// Put without an override so the capturing interceptor only sees the GET below.
158+
s3Client.putObject(builder -> builder
159+
.bucket(BUCKET)
160+
.key(objectKey)
161+
.build(),
162+
AsyncRequestBody.fromString(input)).join();
163+
164+
ResponseBytes<GetObjectResponse> objectResponse = s3Client.getObject(builder -> builder
165+
.bucket(BUCKET)
166+
.key(objectKey)
167+
.overrideConfiguration(overrideConfig)
168+
.build(),
169+
AsyncResponseTransformer.toBytes()).join();
170+
assertEquals(input, objectResponse.asUtf8String());
171+
172+
SdkHttpRequest sentRequest = interceptor.captured();
173+
assertNotNull(sentRequest, "No GetObject request was captured by the interceptor");
174+
175+
Optional<String> customHeader = sentRequest.firstMatchingHeader(CUSTOM_HEADER_NAME);
176+
assertTrue(customHeader.isPresent(),
177+
"Caller-provided request override configuration (custom header) was dropped on GetObject");
178+
assertEquals(CUSTOM_HEADER_VALUE, customHeader.get());
179+
} finally {
180+
deleteObject(BUCKET, objectKey, s3Client);
181+
s3Client.close();
182+
wrappedAsyncClient.close();
183+
}
184+
}
185+
}

0 commit comments

Comments
 (0)