From 47a17aca70d45f035a60e7ee804540ef6567715f Mon Sep 17 00:00:00 2001 From: yibole Date: Fri, 13 Mar 2026 08:45:32 -0700 Subject: [PATCH 1/5] add request level creds override for crt --- ...equestLevelCredentialsIntegrationTest.java | 138 ++++++++++++++++++ .../internal/crt/DefaultS3CrtAsyncClient.java | 54 ++++--- .../s3/internal/crt/S3CrtAsyncHttpClient.java | 20 ++- .../S3InternalSdkHttpExecutionAttribute.java | 3 + .../crt/DefaultS3CrtAsyncClientTest.java | 75 ++++++++++ .../crt/S3CrtAsyncHttpClientTest.java | 60 ++++++++ 6 files changed, 326 insertions(+), 24 deletions(-) create mode 100644 services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java new file mode 100644 index 000000000000..9b5b7e35f713 --- /dev/null +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java @@ -0,0 +1,138 @@ +/* + * 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.services.s3.crt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static software.amazon.awssdk.testutils.service.S3BucketUtils.temporaryBucketName; + +import java.io.File; +import java.util.concurrent.CompletionException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3IntegrationTestBase; +import software.amazon.awssdk.services.s3.internal.crt.S3CrtAsyncClient; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.awssdk.testutils.RandomTempFile; +import software.amazon.awssdk.testutils.service.AwsTestBase; + +/** + * Integration tests verifying that request-level credential overrides work correctly + * with the S3 CRT client. + */ +public class S3CrtRequestLevelCredentialsIntegrationTest extends S3IntegrationTestBase { + private static final String BUCKET = temporaryBucketName(S3CrtRequestLevelCredentialsIntegrationTest.class); + private static final String KEY = "request-level-creds-test-key"; + private static File testFile; + + private static final StaticCredentialsProvider INVALID_CREDENTIALS = + StaticCredentialsProvider.create(AwsBasicCredentials.create("invalidAccessKey", "invalidSecretKey")); + + @BeforeAll + public static void setup() throws Exception { + S3IntegrationTestBase.setUp(); + S3IntegrationTestBase.createBucket(BUCKET); + testFile = new RandomTempFile(1024); + S3IntegrationTestBase.s3.putObject(PutObjectRequest.builder() + .bucket(BUCKET) + .key(KEY) + .build(), testFile.toPath()); + } + + @AfterAll + public static void cleanup() { + S3IntegrationTestBase.deleteBucketAndAllContents(BUCKET); + } + + @Test + void getObject_withValidRequestLevelCredentials_overridingInvalidClientCredentials_shouldSucceed() { + // Client is built with INVALID credentials, but the request overrides with VALID ones. + // If request-level override works, the request should succeed. + try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() + .region(DEFAULT_REGION) + .credentialsProvider(INVALID_CREDENTIALS) + .build()) { + + byte[] result = crtClient.getObject( + b -> b.bucket(BUCKET).key(KEY) + .overrideConfiguration(o -> o.credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN)), + AsyncResponseTransformer.toBytes()).join().asByteArray(); + + assertThat(result).hasSize(1024); + } + } + + @Test + void putObject_withValidRequestLevelCredentials_overridingInvalidClientCredentials_shouldSucceed() { + String overrideKey = KEY + "-put-override"; + try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() + + .region(DEFAULT_REGION) + .credentialsProvider(INVALID_CREDENTIALS) + .build()) { + + crtClient.putObject( + b -> b.bucket(BUCKET).key(overrideKey) + .overrideConfiguration(o -> o.credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN)), + AsyncRequestBody.fromString("hello")).join(); + + byte[] content = S3IntegrationTestBase.s3.getObjectAsBytes( + b -> b.bucket(BUCKET).key(overrideKey)).asByteArray(); + assertThat(new String(content)).isEqualTo("hello"); + } + } + + @Test + void getObject_withInvalidRequestLevelCredentials_overridingValidClientCredentials_shouldFail() { + // Client is built with VALID credentials, but the request overrides with INVALID ones. + // The request should fail with a signing/auth error, proving the override is actually used. + try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() + .region(DEFAULT_REGION) + .credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN) + .build()) { + + assertThatThrownBy(() -> crtClient.getObject( + b -> b.bucket(BUCKET).key(KEY) + .overrideConfiguration(o -> o.credentialsProvider(INVALID_CREDENTIALS)), + AsyncResponseTransformer.toBytes()).join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(S3Exception.class); + } + } + + @Test + void getObject_withValidClientCredentials_noOverride_shouldSucceed() { + // Baseline: client-level credentials work when no override is provided. + try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() + .region(DEFAULT_REGION) + .credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN) + .build()) { + + byte[] result = crtClient.getObject( + b -> b.bucket(BUCKET).key(KEY), + AsyncResponseTransformer.toBytes()).join().asByteArray(); + + assertThat(result).hasSize(1024); + } + } +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java index 2436a27487fa..e49abb7126e5 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java @@ -423,24 +423,37 @@ public void afterMarshalling(Context.AfterMarshalling context, existingHttpAttributes.toBuilder() : SdkHttpExecutionAttributes.builder(); - SdkHttpExecutionAttributes attributes = - builder.put(OPERATION_NAME, - executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME)) - .put(HTTP_CHECKSUM, executionAttributes.getAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM)) - .put(SIGNING_REGION, executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION)) - .put(S3InternalSdkHttpExecutionAttribute.OBJECT_FILE_PATH, - executionAttributes.getAttribute(OBJECT_FILE_PATH)) - .put(USE_S3_EXPRESS_AUTH, S3ExpressUtils.useS3ExpressAuthScheme(executionAttributes)) - .put(SIGNING_NAME, executionAttributes.getAttribute(SERVICE_SIGNING_NAME)) - .put(REQUEST_CHECKSUM_CALCULATION, - executionAttributes.getAttribute(SdkInternalExecutionAttribute.REQUEST_CHECKSUM_CALCULATION)) - .put(RESPONSE_CHECKSUM_VALIDATION, - executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION)) - .put(S3InternalSdkHttpExecutionAttribute.RESPONSE_FILE_PATH, - executionAttributes.getAttribute(RESPONSE_FILE_PATH)) - .put(S3InternalSdkHttpExecutionAttribute.RESPONSE_FILE_OPTION, - executionAttributes.getAttribute(RESPONSE_FILE_OPTION)) - .build(); + builder.put(OPERATION_NAME, + executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME)) + .put(HTTP_CHECKSUM, executionAttributes.getAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM)) + .put(SIGNING_REGION, executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION)) + .put(S3InternalSdkHttpExecutionAttribute.OBJECT_FILE_PATH, + executionAttributes.getAttribute(OBJECT_FILE_PATH)) + .put(USE_S3_EXPRESS_AUTH, S3ExpressUtils.useS3ExpressAuthScheme(executionAttributes)) + .put(SIGNING_NAME, executionAttributes.getAttribute(SERVICE_SIGNING_NAME)) + .put(REQUEST_CHECKSUM_CALCULATION, + executionAttributes.getAttribute(SdkInternalExecutionAttribute.REQUEST_CHECKSUM_CALCULATION)) + .put(RESPONSE_CHECKSUM_VALIDATION, + executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION)) + .put(S3InternalSdkHttpExecutionAttribute.RESPONSE_FILE_PATH, + executionAttributes.getAttribute(RESPONSE_FILE_PATH)) + .put(S3InternalSdkHttpExecutionAttribute.RESPONSE_FILE_OPTION, + executionAttributes.getAttribute(RESPONSE_FILE_OPTION)); + + SdkRequest request = context.request(); + if (request instanceof AwsRequest) { + ((AwsRequest) request).overrideConfiguration().ifPresent(config -> { + AwsRequestOverrideConfiguration awsConfig = (AwsRequestOverrideConfiguration) config; + awsConfig.credentialsIdentityProvider().ifPresent(credentialsProvider -> { + CrtCredentialsProviderAdapter adapter = + new CrtCredentialsProviderAdapter(credentialsProvider); + builder.put(S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER, + adapter); + }); + }); + } + + SdkHttpExecutionAttributes attributes = builder.build(); // We rely on CRT to perform checksum validation, disable SDK flexible checksum implementation executionAttributes.putAttribute(SdkInternalExecutionAttribute.HTTP_CHECKSUM, null); @@ -468,11 +481,6 @@ private static void validateOverrideConfiguration(SdkRequest request) { throw new UnsupportedOperationException("Request-level signer override is not supported"); } - // TODO: support request-level credential override - if (overrideConfiguration.credentialsIdentityProvider().isPresent()) { - throw new UnsupportedOperationException("Request-level credentials override is not supported"); - } - if (!CollectionUtils.isNullOrEmpty(overrideConfiguration.metricPublishers())) { throw new UnsupportedOperationException("Request-level Metric Publishers override is not supported"); } diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClient.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClient.java index 11f318cbff18..b48d9528800c 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClient.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClient.java @@ -43,6 +43,7 @@ import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.ResponseChecksumValidation; import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.crt.auth.credentials.CredentialsProvider; import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; import software.amazon.awssdk.crt.http.HttpHeader; import software.amazon.awssdk.crt.http.HttpProxyEnvironmentVariableSetting; @@ -186,6 +187,9 @@ public CompletableFuture execute(AsyncExecuteRequest asyncRequest) { requestOptions = requestOptions.withResponseFileOption(responseFileOption); } + CrtCredentialsProviderAdapter requestCredentialsAdapter = + httpExecutionAttributes.getAttribute(S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER); + try { S3MetaRequestWrapper requestWrapper = new S3MetaRequestWrapper(crtS3Client.makeMetaRequest(requestOptions)); s3MetaRequestFuture.complete(requestWrapper); @@ -196,16 +200,30 @@ public CompletableFuture execute(AsyncExecuteRequest asyncRequest) { if (observable != null) { observable.subscribe(requestWrapper); } + } catch (Throwable t) { + if (requestCredentialsAdapter != null) { + requestCredentialsAdapter.close(); + } + throw t; } finally { signingConfig.close(); } + if (requestCredentialsAdapter != null) { + executeFuture.whenComplete((result, error) -> requestCredentialsAdapter.close()); + } + return executeFuture; } private AwsSigningConfig awsSigningConfig(Region signingRegion, SdkHttpExecutionAttributes httpExecutionAttributes) { + CrtCredentialsProviderAdapter requestAdapter = + httpExecutionAttributes.getAttribute(S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER); + CredentialsProvider effectiveCredentials = + requestAdapter != null ? requestAdapter.crtCredentials() : s3ClientOptions.getCredentialsProvider(); + AwsSigningConfig defaultS3SigningConfig = - AwsSigningConfig.getDefaultS3SigningConfig(s3ClientOptions.getRegion(), s3ClientOptions.getCredentialsProvider()); + AwsSigningConfig.getDefaultS3SigningConfig(s3ClientOptions.getRegion(), effectiveCredentials); // Override the region only if the signing region has changed from the previously configured region. if (signingRegion != null && !s3ClientOptions.getRegion().equals(signingRegion.id())) { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3InternalSdkHttpExecutionAttribute.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3InternalSdkHttpExecutionAttribute.java index 7f882ca15398..c9138539c81c 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3InternalSdkHttpExecutionAttribute.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3InternalSdkHttpExecutionAttribute.java @@ -64,6 +64,9 @@ public final class S3InternalSdkHttpExecutionAttribute extends SdkHttpExecuti public static final S3InternalSdkHttpExecutionAttribute RESPONSE_FILE_OPTION = new S3InternalSdkHttpExecutionAttribute<>(S3MetaRequestOptions.ResponseFileOption.class); + public static final S3InternalSdkHttpExecutionAttribute CRT_CREDENTIALS_PROVIDER_ADAPTER = + new S3InternalSdkHttpExecutionAttribute<>(CrtCredentialsProviderAdapter.class); + private S3InternalSdkHttpExecutionAttribute(Class valueClass) { super(valueClass); } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java index 0276d116b16b..f102f8448e2a 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java @@ -22,7 +22,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsS3V4Signer; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; @@ -31,6 +33,7 @@ import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.http.SdkHttpExecutionAttributes; import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient; @@ -161,4 +164,76 @@ void build_withAdvancedOptions() { assertThat(client).isInstanceOf(DefaultS3CrtAsyncClient.class); } } + + @Test + void afterMarshalling_withRequestLevelCredentials_shouldAttachCrtCredentialsAdapterToHttpAttributes() { + AtomicReference capturedAttributes = new AtomicReference<>(); + + ExecutionInterceptor attributeCaptor = new ExecutionInterceptor() { + @Override + public void afterMarshalling(Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { + capturedAttributes.set( + executionAttributes.getAttribute(SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES)); + throw new RuntimeException("STOP"); + } + }; + + DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder builder = + (DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder) S3CrtAsyncClient.builder(); + builder.addExecutionInterceptor(attributeCaptor); + + try (S3AsyncClient s3AsyncClient = builder.build()) { + IdentityProvider requestCredentials = + StaticCredentialsProvider.create(AwsBasicCredentials.create("requestAkid", "requestSkid")); + + assertThatThrownBy(() -> s3AsyncClient.getObject( + b -> b.bucket("bucket").key("key") + .overrideConfiguration(o -> o.credentialsProvider(requestCredentials)), + AsyncResponseTransformer.toBytes()).join()).hasMessageContaining("STOP"); + + SdkHttpExecutionAttributes httpAttributes = capturedAttributes.get(); + assertThat(httpAttributes).isNotNull(); + CrtCredentialsProviderAdapter adapter = + httpAttributes.getAttribute(S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER); + assertThat(adapter).isNotNull(); + + software.amazon.awssdk.crt.auth.credentials.Credentials crtCreds = + adapter.crtCredentials().getCredentials().join(); + assertThat(crtCreds.getAccessKeyId()) + .isEqualTo("requestAkid".getBytes(java.nio.charset.StandardCharsets.UTF_8)); + assertThat(crtCreds.getSecretAccessKey()) + .isEqualTo("requestSkid".getBytes(java.nio.charset.StandardCharsets.UTF_8)); + } + } + + @Test + void afterMarshalling_withoutRequestLevelCredentials_shouldNotAttachCrtCredentialsAdapter() { + AtomicReference capturedAttributes = new AtomicReference<>(); + + ExecutionInterceptor attributeCaptor = new ExecutionInterceptor() { + @Override + public void afterMarshalling(Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { + capturedAttributes.set( + executionAttributes.getAttribute(SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES)); + throw new RuntimeException("STOP"); + } + }; + + DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder builder = + (DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder) S3CrtAsyncClient.builder(); + builder.addExecutionInterceptor(attributeCaptor); + + try (S3AsyncClient s3AsyncClient = builder.build()) { + assertThatThrownBy(() -> s3AsyncClient.getObject( + b -> b.bucket("bucket").key("key"), + AsyncResponseTransformer.toBytes()).join()).hasMessageContaining("STOP"); + + SdkHttpExecutionAttributes httpAttributes = capturedAttributes.get(); + assertThat(httpAttributes).isNotNull(); + CrtCredentialsProviderAdapter adapter = + httpAttributes.getAttribute(S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER); + assertThat(adapter).isNull(); + } + } + } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java index 9b29569aaff1..d8c9bfec9561 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java @@ -27,6 +27,7 @@ import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.RESPONSE_CHECKSUM_VALIDATION; import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.RESPONSE_FILE_OPTION; import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.RESPONSE_FILE_PATH; +import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER; import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.SIGNING_NAME; import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.SIGNING_REGION; import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.USE_S3_EXPRESS_AUTH; @@ -50,6 +51,7 @@ import software.amazon.awssdk.core.checksums.RequestChecksumCalculation; import software.amazon.awssdk.core.checksums.ResponseChecksumValidation; import software.amazon.awssdk.core.interceptor.trait.HttpChecksum; +import software.amazon.awssdk.crt.auth.credentials.CredentialsProvider; import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig; import software.amazon.awssdk.crt.http.HttpProxyEnvironmentVariableSetting; import software.amazon.awssdk.crt.http.HttpRequest; @@ -623,6 +625,64 @@ public void responseFilePathAndOption_shouldPassToCrt() { assertThat(actual.getResponseFileOption()).isEqualTo(S3MetaRequestOptions.ResponseFileOption.CREATE_OR_APPEND); } + @Test + public void execute_withRequestLevelCredentials_shouldUseRequestCredentialsInSigningConfig() { + CredentialsProvider crtCredentials = Mockito.mock(CredentialsProvider.class); + CrtCredentialsProviderAdapter adapter = Mockito.mock(CrtCredentialsProviderAdapter.class); + when(adapter.crtCredentials()).thenReturn(crtCredentials); + + AsyncExecuteRequest asyncExecuteRequest = + getExecuteRequestBuilder() + .putHttpExecutionAttribute(OPERATION_NAME, "GetObject") + .putHttpExecutionAttribute(SIGNING_REGION, Region.US_WEST_2) + .putHttpExecutionAttribute(SIGNING_NAME, "s3") + .putHttpExecutionAttribute(CRT_CREDENTIALS_PROVIDER_ADAPTER, adapter) + .build(); + + S3MetaRequestOptions actual = makeRequest(asyncExecuteRequest); + AwsSigningConfig signingConfig = actual.getSigningConfig(); + assertThat(signingConfig.getCredentialsProvider()).isSameAs(crtCredentials); + } + + @Test + public void execute_withoutRequestLevelCredentials_shouldUseClientLevelCredentials() { + AsyncExecuteRequest asyncExecuteRequest = + getExecuteRequestBuilder() + .putHttpExecutionAttribute(OPERATION_NAME, "GetObject") + .putHttpExecutionAttribute(SIGNING_REGION, Region.US_WEST_2) + .putHttpExecutionAttribute(SIGNING_NAME, "s3") + .build(); + + S3MetaRequestOptions actual = makeRequest(asyncExecuteRequest); + AwsSigningConfig signingConfig = actual.getSigningConfig(); + assertThat(signingConfig.getCredentialsProvider()).isNotNull(); + } + + @Test + public void execute_withRequestLevelCredentials_shouldCloseAdapterOnCompletion() { + CrtCredentialsProviderAdapter adapter = Mockito.mock(CrtCredentialsProviderAdapter.class); + when(adapter.crtCredentials()).thenReturn(Mockito.mock(CredentialsProvider.class)); + S3MetaRequest metaRequest = Mockito.mock(S3MetaRequest.class); + when(s3Client.makeMetaRequest(any(S3MetaRequestOptions.class))).thenReturn(metaRequest); + + AsyncExecuteRequest asyncExecuteRequest = + getExecuteRequestBuilder() + .putHttpExecutionAttribute(OPERATION_NAME, "GetObject") + .putHttpExecutionAttribute(SIGNING_REGION, Region.US_WEST_2) + .putHttpExecutionAttribute(SIGNING_NAME, "s3") + .putHttpExecutionAttribute(CRT_CREDENTIALS_PROVIDER_ADAPTER, adapter) + .build(); + + CompletableFuture future = asyncHttpClient.execute(asyncExecuteRequest); + + Mockito.verify(adapter, Mockito.never()).close(); + + future.complete(null); + + Mockito.verify(adapter).close(); + } + + private AsyncExecuteRequest.Builder getExecuteRequestBuilder() { return getExecuteRequestBuilder(443); } From 6e56c050ac5d1f66e9ba6385ad5907621b9e4bb8 Mon Sep 17 00:00:00 2001 From: yibole Date: Fri, 13 Mar 2026 08:51:52 -0700 Subject: [PATCH 2/5] changelog added --- .changes/next-release/feature-AmazonS3-bf3403d.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/next-release/feature-AmazonS3-bf3403d.json diff --git a/.changes/next-release/feature-AmazonS3-bf3403d.json b/.changes/next-release/feature-AmazonS3-bf3403d.json new file mode 100644 index 000000000000..aa8a92a6b0a0 --- /dev/null +++ b/.changes/next-release/feature-AmazonS3-bf3403d.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Amazon S3", + "contributor": "", + "description": "aaa" +} From 655998a7db5c699512e0b00524c3bac862dc7ec8 Mon Sep 17 00:00:00 2001 From: yibole Date: Fri, 13 Mar 2026 09:29:24 -0700 Subject: [PATCH 3/5] change decription --- .changes/next-release/feature-AmazonS3-bf3403d.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/next-release/feature-AmazonS3-bf3403d.json b/.changes/next-release/feature-AmazonS3-bf3403d.json index aa8a92a6b0a0..7bbdefb1d20a 100644 --- a/.changes/next-release/feature-AmazonS3-bf3403d.json +++ b/.changes/next-release/feature-AmazonS3-bf3403d.json @@ -2,5 +2,5 @@ "type": "feature", "category": "Amazon S3", "contributor": "", - "description": "aaa" + "description": "Added support of Request-level credentials override in DefaultS3CrtAsyncClient. See [#5354](https://github.com/aws/aws-sdk-java-v2/issues/5354)." } From 1f989a7ba46d6c475db8c8b5406903fa9337a0c5 Mon Sep 17 00:00:00 2001 From: yibole Date: Wed, 18 Mar 2026 15:59:57 -0700 Subject: [PATCH 4/5] change integration test to wiremock test --- ...equestLevelCredentialsIntegrationTest.java | 138 ----------------- ...rtRequestLevelCredentialsWireMockTest.java | 144 ++++++++++++++++++ .../crt/DefaultS3CrtAsyncClientTest.java | 74 --------- .../crt/S3CrtAsyncHttpClientTest.java | 33 ---- 4 files changed, 144 insertions(+), 245 deletions(-) delete mode 100644 services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java deleted file mode 100644 index 9b5b7e35f713..000000000000 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsIntegrationTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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.services.s3.crt; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static software.amazon.awssdk.testutils.service.S3BucketUtils.temporaryBucketName; - -import java.io.File; -import java.util.concurrent.CompletionException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.core.async.AsyncRequestBody; -import software.amazon.awssdk.core.async.AsyncResponseTransformer; -import software.amazon.awssdk.services.s3.S3AsyncClient; -import software.amazon.awssdk.services.s3.S3IntegrationTestBase; -import software.amazon.awssdk.services.s3.internal.crt.S3CrtAsyncClient; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.awssdk.services.s3.model.S3Exception; -import software.amazon.awssdk.testutils.RandomTempFile; -import software.amazon.awssdk.testutils.service.AwsTestBase; - -/** - * Integration tests verifying that request-level credential overrides work correctly - * with the S3 CRT client. - */ -public class S3CrtRequestLevelCredentialsIntegrationTest extends S3IntegrationTestBase { - private static final String BUCKET = temporaryBucketName(S3CrtRequestLevelCredentialsIntegrationTest.class); - private static final String KEY = "request-level-creds-test-key"; - private static File testFile; - - private static final StaticCredentialsProvider INVALID_CREDENTIALS = - StaticCredentialsProvider.create(AwsBasicCredentials.create("invalidAccessKey", "invalidSecretKey")); - - @BeforeAll - public static void setup() throws Exception { - S3IntegrationTestBase.setUp(); - S3IntegrationTestBase.createBucket(BUCKET); - testFile = new RandomTempFile(1024); - S3IntegrationTestBase.s3.putObject(PutObjectRequest.builder() - .bucket(BUCKET) - .key(KEY) - .build(), testFile.toPath()); - } - - @AfterAll - public static void cleanup() { - S3IntegrationTestBase.deleteBucketAndAllContents(BUCKET); - } - - @Test - void getObject_withValidRequestLevelCredentials_overridingInvalidClientCredentials_shouldSucceed() { - // Client is built with INVALID credentials, but the request overrides with VALID ones. - // If request-level override works, the request should succeed. - try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() - .region(DEFAULT_REGION) - .credentialsProvider(INVALID_CREDENTIALS) - .build()) { - - byte[] result = crtClient.getObject( - b -> b.bucket(BUCKET).key(KEY) - .overrideConfiguration(o -> o.credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN)), - AsyncResponseTransformer.toBytes()).join().asByteArray(); - - assertThat(result).hasSize(1024); - } - } - - @Test - void putObject_withValidRequestLevelCredentials_overridingInvalidClientCredentials_shouldSucceed() { - String overrideKey = KEY + "-put-override"; - try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() - - .region(DEFAULT_REGION) - .credentialsProvider(INVALID_CREDENTIALS) - .build()) { - - crtClient.putObject( - b -> b.bucket(BUCKET).key(overrideKey) - .overrideConfiguration(o -> o.credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN)), - AsyncRequestBody.fromString("hello")).join(); - - byte[] content = S3IntegrationTestBase.s3.getObjectAsBytes( - b -> b.bucket(BUCKET).key(overrideKey)).asByteArray(); - assertThat(new String(content)).isEqualTo("hello"); - } - } - - @Test - void getObject_withInvalidRequestLevelCredentials_overridingValidClientCredentials_shouldFail() { - // Client is built with VALID credentials, but the request overrides with INVALID ones. - // The request should fail with a signing/auth error, proving the override is actually used. - try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() - .region(DEFAULT_REGION) - .credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN) - .build()) { - - assertThatThrownBy(() -> crtClient.getObject( - b -> b.bucket(BUCKET).key(KEY) - .overrideConfiguration(o -> o.credentialsProvider(INVALID_CREDENTIALS)), - AsyncResponseTransformer.toBytes()).join()) - .isInstanceOf(CompletionException.class) - .hasCauseInstanceOf(S3Exception.class); - } - } - - @Test - void getObject_withValidClientCredentials_noOverride_shouldSucceed() { - // Baseline: client-level credentials work when no override is provided. - try (S3AsyncClient crtClient = S3CrtAsyncClient.builder() - .region(DEFAULT_REGION) - .credentialsProvider(AwsTestBase.CREDENTIALS_PROVIDER_CHAIN) - .build()) { - - byte[] result = crtClient.getObject( - b -> b.bucket(BUCKET).key(KEY), - AsyncResponseTransformer.toBytes()).join().asByteArray(); - - assertThat(result).hasSize(1024); - } - } -} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java new file mode 100644 index 000000000000..094c6c2aa72e --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java @@ -0,0 +1,144 @@ +/* + * 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.services.s3.crt; + +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.head; +import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.put; +import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.crt.Log; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; + +/** + * WireMock tests verifying that request-level credential overrides are used for signing + * with the S3 CRT client. Verifies the Authorization header contains the expected access key. + */ +@WireMockTest +@Timeout(10) +public class S3CrtRequestLevelCredentialsWireMockTest { + + private static final String BUCKET = "my-bucket"; + private static final String KEY = "my-key"; + private static final String PATH = String.format("/%s/%s", BUCKET, KEY); + private static final byte[] CONTENT = "hello".getBytes(StandardCharsets.UTF_8); + + private static final StaticCredentialsProvider CLIENT_CREDENTIALS = + StaticCredentialsProvider.create(AwsBasicCredentials.create("clientAccessKey", "clientSecretKey")); + + private static final StaticCredentialsProvider REQUEST_CREDENTIALS = + StaticCredentialsProvider.create(AwsBasicCredentials.create("requestAccessKey", "requestSecretKey")); + + private S3AsyncClient s3; + + @BeforeAll + public static void setUpBeforeAll() { + System.setProperty("aws.crt.debugnative", "true"); + Log.initLoggingToStdout(Log.LogLevel.Warn); + } + + @BeforeEach + public void setup(WireMockRuntimeInfo wiremock) { + stubFor(head(urlPathEqualTo(PATH)) + .willReturn(WireMock.aResponse().withStatus(200) + .withHeader("ETag", "etag") + .withHeader("Content-Length", + Integer.toString(CONTENT.length)))); + stubFor(get(urlPathEqualTo(PATH)) + .willReturn(WireMock.aResponse().withStatus(200) + .withHeader("Content-Type", "text/plain") + .withBody(CONTENT))); + stubFor(put(urlPathEqualTo(PATH)) + .willReturn(WireMock.aResponse().withStatus(200) + .withHeader("ETag", "etag"))); + + s3 = S3AsyncClient.crtBuilder() + .endpointOverride(URI.create("http://localhost:" + wiremock.getHttpPort())) + .credentialsProvider(CLIENT_CREDENTIALS) + .forcePathStyle(true) + .region(Region.US_EAST_1) + .build(); + } + + @AfterEach + public void tearDown() { + s3.close(); + } + + @Test + void getObject_withRequestLevelCredentials_shouldSignWithOverrideCredentials() { + s3.getObject( + b -> b.bucket(BUCKET).key(KEY) + .overrideConfiguration(o -> o.credentialsProvider(REQUEST_CREDENTIALS)), + AsyncResponseTransformer.toBytes()).join(); + + verify(getRequestedFor(urlPathEqualTo(PATH)) + .withHeader("Authorization", containing("Credential=requestAccessKey/"))); + } + + @Test + void getObject_withoutRequestLevelCredentials_shouldSignWithClientCredentials() { + s3.getObject( + b -> b.bucket(BUCKET).key(KEY), + AsyncResponseTransformer.toBytes()).join(); + + verify(getRequestedFor(urlPathEqualTo(PATH)) + .withHeader("Authorization", containing("Credential=clientAccessKey/"))); + } + + @Test + void putObject_withRequestLevelCredentials_shouldSignWithOverrideCredentials() { + s3.putObject( + b -> b.bucket(BUCKET).key(KEY) + .overrideConfiguration(o -> o.credentialsProvider(REQUEST_CREDENTIALS)), + AsyncRequestBody.fromString("hello")).join(); + + verify(putRequestedFor(urlPathEqualTo(PATH)) + .withHeader("Authorization", containing("Credential=requestAccessKey/"))); + } + + @Test + void putObject_withoutRequestLevelCredentials_shouldSignWithClientCredentials() { + s3.putObject( + b -> b.bucket(BUCKET).key(KEY), + AsyncRequestBody.fromString("hello")).join(); + + verify(putRequestedFor(urlPathEqualTo(PATH)) + .withHeader("Authorization", containing("Credential=clientAccessKey/"))); + } +} diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java index f102f8448e2a..3db4cf04a247 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java @@ -22,9 +22,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.signer.AwsS3V4Signer; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; @@ -33,7 +31,6 @@ import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; -import software.amazon.awssdk.http.SdkHttpExecutionAttributes; import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient; @@ -165,75 +162,4 @@ void build_withAdvancedOptions() { } } - @Test - void afterMarshalling_withRequestLevelCredentials_shouldAttachCrtCredentialsAdapterToHttpAttributes() { - AtomicReference capturedAttributes = new AtomicReference<>(); - - ExecutionInterceptor attributeCaptor = new ExecutionInterceptor() { - @Override - public void afterMarshalling(Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { - capturedAttributes.set( - executionAttributes.getAttribute(SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES)); - throw new RuntimeException("STOP"); - } - }; - - DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder builder = - (DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder) S3CrtAsyncClient.builder(); - builder.addExecutionInterceptor(attributeCaptor); - - try (S3AsyncClient s3AsyncClient = builder.build()) { - IdentityProvider requestCredentials = - StaticCredentialsProvider.create(AwsBasicCredentials.create("requestAkid", "requestSkid")); - - assertThatThrownBy(() -> s3AsyncClient.getObject( - b -> b.bucket("bucket").key("key") - .overrideConfiguration(o -> o.credentialsProvider(requestCredentials)), - AsyncResponseTransformer.toBytes()).join()).hasMessageContaining("STOP"); - - SdkHttpExecutionAttributes httpAttributes = capturedAttributes.get(); - assertThat(httpAttributes).isNotNull(); - CrtCredentialsProviderAdapter adapter = - httpAttributes.getAttribute(S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER); - assertThat(adapter).isNotNull(); - - software.amazon.awssdk.crt.auth.credentials.Credentials crtCreds = - adapter.crtCredentials().getCredentials().join(); - assertThat(crtCreds.getAccessKeyId()) - .isEqualTo("requestAkid".getBytes(java.nio.charset.StandardCharsets.UTF_8)); - assertThat(crtCreds.getSecretAccessKey()) - .isEqualTo("requestSkid".getBytes(java.nio.charset.StandardCharsets.UTF_8)); - } - } - - @Test - void afterMarshalling_withoutRequestLevelCredentials_shouldNotAttachCrtCredentialsAdapter() { - AtomicReference capturedAttributes = new AtomicReference<>(); - - ExecutionInterceptor attributeCaptor = new ExecutionInterceptor() { - @Override - public void afterMarshalling(Context.AfterMarshalling context, ExecutionAttributes executionAttributes) { - capturedAttributes.set( - executionAttributes.getAttribute(SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES)); - throw new RuntimeException("STOP"); - } - }; - - DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder builder = - (DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder) S3CrtAsyncClient.builder(); - builder.addExecutionInterceptor(attributeCaptor); - - try (S3AsyncClient s3AsyncClient = builder.build()) { - assertThatThrownBy(() -> s3AsyncClient.getObject( - b -> b.bucket("bucket").key("key"), - AsyncResponseTransformer.toBytes()).join()).hasMessageContaining("STOP"); - - SdkHttpExecutionAttributes httpAttributes = capturedAttributes.get(); - assertThat(httpAttributes).isNotNull(); - CrtCredentialsProviderAdapter adapter = - httpAttributes.getAttribute(S3InternalSdkHttpExecutionAttribute.CRT_CREDENTIALS_PROVIDER_ADAPTER); - assertThat(adapter).isNull(); - } - } - } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java index d8c9bfec9561..9432e1f13939 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java @@ -625,39 +625,6 @@ public void responseFilePathAndOption_shouldPassToCrt() { assertThat(actual.getResponseFileOption()).isEqualTo(S3MetaRequestOptions.ResponseFileOption.CREATE_OR_APPEND); } - @Test - public void execute_withRequestLevelCredentials_shouldUseRequestCredentialsInSigningConfig() { - CredentialsProvider crtCredentials = Mockito.mock(CredentialsProvider.class); - CrtCredentialsProviderAdapter adapter = Mockito.mock(CrtCredentialsProviderAdapter.class); - when(adapter.crtCredentials()).thenReturn(crtCredentials); - - AsyncExecuteRequest asyncExecuteRequest = - getExecuteRequestBuilder() - .putHttpExecutionAttribute(OPERATION_NAME, "GetObject") - .putHttpExecutionAttribute(SIGNING_REGION, Region.US_WEST_2) - .putHttpExecutionAttribute(SIGNING_NAME, "s3") - .putHttpExecutionAttribute(CRT_CREDENTIALS_PROVIDER_ADAPTER, adapter) - .build(); - - S3MetaRequestOptions actual = makeRequest(asyncExecuteRequest); - AwsSigningConfig signingConfig = actual.getSigningConfig(); - assertThat(signingConfig.getCredentialsProvider()).isSameAs(crtCredentials); - } - - @Test - public void execute_withoutRequestLevelCredentials_shouldUseClientLevelCredentials() { - AsyncExecuteRequest asyncExecuteRequest = - getExecuteRequestBuilder() - .putHttpExecutionAttribute(OPERATION_NAME, "GetObject") - .putHttpExecutionAttribute(SIGNING_REGION, Region.US_WEST_2) - .putHttpExecutionAttribute(SIGNING_NAME, "s3") - .build(); - - S3MetaRequestOptions actual = makeRequest(asyncExecuteRequest); - AwsSigningConfig signingConfig = actual.getSigningConfig(); - assertThat(signingConfig.getCredentialsProvider()).isNotNull(); - } - @Test public void execute_withRequestLevelCredentials_shouldCloseAdapterOnCompletion() { CrtCredentialsProviderAdapter adapter = Mockito.mock(CrtCredentialsProviderAdapter.class); From bc87be6c8c7c3cba7269d150a15ad361615abd7e Mon Sep 17 00:00:00 2001 From: yibole Date: Wed, 18 Mar 2026 16:17:22 -0700 Subject: [PATCH 5/5] added test for not happy path --- ...rtRequestLevelCredentialsWireMockTest.java | 2 -- .../crt/DefaultS3CrtAsyncClientTest.java | 1 - .../crt/S3CrtAsyncHttpClientTest.java | 22 ++++++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java index 094c6c2aa72e..ab3e5cf564d6 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/crt/S3CrtRequestLevelCredentialsWireMockTest.java @@ -15,12 +15,10 @@ package software.amazon.awssdk.services.s3.crt; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; import static com.github.tomakehurst.wiremock.client.WireMock.containing; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.head; -import static com.github.tomakehurst.wiremock.client.WireMock.headRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.put; import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java index 3db4cf04a247..0276d116b16b 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java @@ -161,5 +161,4 @@ void build_withAdvancedOptions() { assertThat(client).isInstanceOf(DefaultS3CrtAsyncClient.class); } } - } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java index 9432e1f13939..40702c70ef65 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtAsyncHttpClientTest.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.services.s3.internal.crt; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -62,7 +63,6 @@ import software.amazon.awssdk.crt.s3.S3ClientOptions; import software.amazon.awssdk.crt.s3.S3MetaRequest; import software.amazon.awssdk.crt.s3.S3MetaRequestOptions; -import software.amazon.awssdk.crt.s3.S3MetaRequestResponseHandler; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; import software.amazon.awssdk.http.async.AsyncExecuteRequest; @@ -649,6 +649,26 @@ public void execute_withRequestLevelCredentials_shouldCloseAdapterOnCompletion() Mockito.verify(adapter).close(); } + @Test + void execute_whenMakeMetaRequestThrows_shouldCloseAdapter() { + CrtCredentialsProviderAdapter adapter = Mockito.mock(CrtCredentialsProviderAdapter.class); + when(adapter.crtCredentials()).thenReturn(Mockito.mock(CredentialsProvider.class)); + when(s3Client.makeMetaRequest(any(S3MetaRequestOptions.class))) + .thenThrow(new RuntimeException("boom")); + + AsyncExecuteRequest asyncExecuteRequest = + getExecuteRequestBuilder() + .putHttpExecutionAttribute(OPERATION_NAME, "GetObject") + .putHttpExecutionAttribute(SIGNING_REGION, Region.US_WEST_2) + .putHttpExecutionAttribute(SIGNING_NAME, "s3") + .putHttpExecutionAttribute(CRT_CREDENTIALS_PROVIDER_ADAPTER, adapter) + .build(); + + assertThatThrownBy(() -> asyncHttpClient.execute(asyncExecuteRequest)) + .isInstanceOf(RuntimeException.class); + + Mockito.verify(adapter).close(); + } private AsyncExecuteRequest.Builder getExecuteRequestBuilder() { return getExecuteRequestBuilder(443);