Configures whether cross-region bucket access is enabled for clients using the configuration.
+ * The following behavior is used when this mode is enabled:
+ *
+ * - This method allows enabling or disabling cross-region bucket access for clients. When cross-region bucket
+ * access is enabled, requests that do not act on an existing bucket (e.g., createBucket API) will be routed to the
+ * region configured on the client
+ * - The first time a request is made that references an existing bucket (e.g., putObject API), a request will be
+ * made to the client-configured region. If the bucket does not exist in this region, the service will include the
+ * actual region in the error responses. Subsequently, the API will be called using the correct region obtained
+ * from the error response.
+ * - This location may be cached in the client for subsequent requests to the same bucket.
+ *
+ * Enabling this mode has several drawbacks, as it can increase latency if the bucket's location is physically far
+ * from the location of the request.Therefore, it is strongly advised, whenever possible, to know the location of your
+ * buckets and create a region-specific client to access them
+ *
+ * @param crossRegionAccessEnabled Whether cross region bucket access should be enabled.
+ * @return The builder object for method chaining.
+ */
+ S3CrtAsyncClientBuilder crossRegionAccessEnabled(boolean crossRegionAccessEnabled);
@Override
S3AsyncClient build();
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 860ac509932e..5a5c0d9a314d 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
@@ -18,6 +18,7 @@
import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES;
import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.HTTP_CHECKSUM;
import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.OPERATION_NAME;
+import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.SIGNING_REGION;
import static software.amazon.awssdk.services.s3.internal.crt.S3NativeClientConfiguration.DEFAULT_PART_SIZE_IN_BYTES;
import java.net.URI;
@@ -27,6 +28,7 @@
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
import software.amazon.awssdk.awscore.AwsRequest;
import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration;
import software.amazon.awssdk.core.SdkRequest;
@@ -94,6 +96,7 @@ private static S3AsyncClient initializeS3AsyncClient(DefaultS3CrtClientBuilder b
// Disable checksum, it is handled in CRT
.serviceConfiguration(S3Configuration.builder()
.checksumValidationEnabled(false)
+ .crossRegionAccessEnabled(builder.crossRegionAccessEnabled)
.build())
.region(builder.region)
.endpointOverride(builder.endpointOverride)
@@ -149,6 +152,7 @@ public static final class DefaultS3CrtClientBuilder implements S3CrtAsyncClientB
private List executionInterceptors;
private S3CrtRetryConfiguration retryConfiguration;
+ private boolean crossRegionAccessEnabled;
public AwsCredentialsProvider credentialsProvider() {
return credentialsProvider;
@@ -178,6 +182,10 @@ public Long readBufferSizeInBytes() {
return readBufferSizeInBytes;
}
+ public boolean crossRegionAccessEnabled() {
+ return crossRegionAccessEnabled;
+ }
+
@Override
public S3CrtAsyncClientBuilder credentialsProvider(AwsCredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
@@ -259,6 +267,12 @@ public S3CrtAsyncClientBuilder retryConfiguration(S3CrtRetryConfiguration retryC
return this;
}
+ @Override
+ public S3CrtAsyncClientBuilder crossRegionAccessEnabled(boolean crossRegionAccessEnabled) {
+ this.crossRegionAccessEnabled = crossRegionAccessEnabled;
+ return this;
+ }
+
@Override
public S3CrtAsyncClient build() {
return new DefaultS3CrtAsyncClient(this);
@@ -280,6 +294,7 @@ public void afterMarshalling(Context.AfterMarshalling context,
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))
.build();
// For putObject and getObject, we rely on CRT to perform checksum validation
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 9681c8c14f94..a6358b238a40 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
@@ -20,6 +20,7 @@
import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.HTTP_CHECKSUM;
import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.METAREQUEST_PAUSE_OBSERVABLE;
import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.OPERATION_NAME;
+import static software.amazon.awssdk.services.s3.internal.crt.S3InternalSdkHttpExecutionAttribute.SIGNING_REGION;
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
import java.net.URI;
@@ -31,6 +32,7 @@
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.core.interceptor.trait.HttpChecksum;
+import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig;
import software.amazon.awssdk.crt.http.HttpHeader;
import software.amazon.awssdk.crt.http.HttpRequest;
import software.amazon.awssdk.crt.s3.ChecksumConfig;
@@ -43,9 +45,11 @@
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.Logger;
import software.amazon.awssdk.utils.NumericUtils;
+import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.http.SdkHttpUtils;
/**
@@ -117,6 +121,7 @@ public CompletableFuture execute(AsyncExecuteRequest asyncRequest) {
HttpChecksum httpChecksum = asyncRequest.httpExecutionAttributes().getAttribute(HTTP_CHECKSUM);
ResumeToken resumeToken = asyncRequest.httpExecutionAttributes().getAttribute(CRT_PAUSE_RESUME_TOKEN);
+ Region signingRegion = asyncRequest.httpExecutionAttributes().getAttribute(SIGNING_REGION);
ChecksumConfig checksumConfig =
checksumConfig(httpChecksum, requestType, s3NativeClientConfiguration.checksumValidationEnabled());
@@ -130,6 +135,13 @@ public CompletableFuture execute(AsyncExecuteRequest asyncRequest) {
.withResponseHandler(responseHandler)
.withResumeToken(resumeToken);
+ // Create a new SigningConfig object only if the signing region has changed from the previously configured region.
+ if (signingRegion != null && !s3ClientOptions.getRegion().equals(signingRegion.id())) {
+ requestOptions.withSigningConfig(
+ AwsSigningConfig.getDefaultS3SigningConfig(signingRegion.id(),
+ s3ClientOptions.getCredentialsProvider()));
+ }
+
S3MetaRequest s3MetaRequest = crtS3Client.makeMetaRequest(requestOptions);
S3MetaRequestPauseObservable observable =
asyncRequest.httpExecutionAttributes().getAttribute(METAREQUEST_PAUSE_OBSERVABLE);
@@ -144,6 +156,7 @@ public CompletableFuture execute(AsyncExecuteRequest asyncRequest) {
return executeFuture;
}
+
private static URI getEndpoint(URI uri) {
return invokeSafely(() -> new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), null, null, null));
}
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 763cc874cc86..f7b817ab9ad2 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
@@ -19,6 +19,7 @@
import software.amazon.awssdk.core.interceptor.trait.HttpChecksum;
import software.amazon.awssdk.crt.s3.ResumeToken;
import software.amazon.awssdk.http.SdkHttpExecutionAttribute;
+import software.amazon.awssdk.regions.Region;
@SdkInternalApi
public final class S3InternalSdkHttpExecutionAttribute extends SdkHttpExecutionAttribute {
@@ -37,6 +38,9 @@ public final class S3InternalSdkHttpExecutionAttribute extends SdkHttpExecuti
public static final S3InternalSdkHttpExecutionAttribute CRT_PAUSE_RESUME_TOKEN =
new S3InternalSdkHttpExecutionAttribute<>(ResumeToken.class);
+ public static final S3InternalSdkHttpExecutionAttribute SIGNING_REGION =
+ new S3InternalSdkHttpExecutionAttribute<>(Region.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 5c0da7b22f9f..ee44bf18839f 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
@@ -29,8 +29,10 @@
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.services.s3.DelegatingS3AsyncClient;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams;
+import software.amazon.awssdk.services.s3.internal.crossregion.S3CrossRegionAsyncClient;
import software.amazon.awssdk.utils.AttributeMap;
class DefaultS3CrtAsyncClientTest {
@@ -92,4 +94,23 @@ void invalidConfig_shouldThrowException(long value) {
.hasMessageContaining(
"positive");
}
+
+ @Test
+ void crtClient_with_crossRegionAccessEnabled_asTrue(){
+ S3AsyncClient crossRegionCrtClient = S3AsyncClient.crtBuilder().crossRegionAccessEnabled(true).build();
+ assertThat(crossRegionCrtClient).isInstanceOf(DefaultS3CrtAsyncClient.class);
+ assertThat(((DelegatingS3AsyncClient)crossRegionCrtClient).delegate()).isInstanceOf(S3CrossRegionAsyncClient.class);
+ }
+
+ @Test
+ void crtClient_with_crossRegionAccessEnabled_asFalse(){
+ S3AsyncClient crossRegionDisabledCrtClient = S3AsyncClient.crtBuilder().crossRegionAccessEnabled(false).build();
+ assertThat(crossRegionDisabledCrtClient).isInstanceOf(DefaultS3CrtAsyncClient.class);
+ assertThat(((DelegatingS3AsyncClient)crossRegionDisabledCrtClient).delegate()).isNotInstanceOf(S3CrossRegionAsyncClient.class);
+
+ S3AsyncClient defaultCrtClient = S3AsyncClient.crtBuilder().build();
+ assertThat(defaultCrtClient).isInstanceOf(DefaultS3CrtAsyncClient.class);
+ assertThat(((DelegatingS3AsyncClient)defaultCrtClient).delegate()).isNotInstanceOf(S3CrossRegionAsyncClient.class);
+ }
+
}