diff --git a/pom.xml b/pom.xml index f33beb9ef..f504c79ca 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,9 @@ 8 8 UTF-8 + 2.21.46 + 0.29.24 + 1.12.441 @@ -48,7 +51,7 @@ com.amazonaws aws-java-sdk-bom - 1.12.441 + ${com.amazonaws.version} pom import @@ -56,7 +59,7 @@ software.amazon.awssdk bom - 2.20.38 + ${aws.java.sdk.version} true pom import @@ -68,13 +71,11 @@ software.amazon.awssdk s3 - 2.20.38 software.amazon.awssdk kms - 2.20.38 true @@ -82,7 +83,7 @@ software.amazon.awssdk.crt aws-crt - 0.29.24 + ${aws.java.crt.version} true @@ -164,7 +165,6 @@ software.amazon.awssdk sts - 2.20.38 true test diff --git a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java index 2ed1ba3f5..f44c57545 100644 --- a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java @@ -30,6 +30,7 @@ import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectResponse; import software.amazon.awssdk.services.s3.model.S3Request; +import software.amazon.awssdk.services.s3.multipart.MultipartConfiguration; import software.amazon.encryption.s3.internal.GetEncryptedObjectPipeline; import software.amazon.encryption.s3.internal.NoRetriesAsyncRequestBody; import software.amazon.encryption.s3.internal.PutEncryptedObjectPipeline; @@ -71,6 +72,7 @@ public class S3AsyncEncryptionClient extends DelegatingS3AsyncClient { private final boolean _enableDelayedAuthenticationMode; private final boolean _enableMultipartPutObject; private final long _bufferSize; + private final boolean _clientMultipartEnabled; private S3AsyncEncryptionClient(Builder builder) { super(builder._wrappedClient); @@ -81,6 +83,7 @@ private S3AsyncEncryptionClient(Builder builder) { _enableDelayedAuthenticationMode = builder._enableDelayedAuthenticationMode; _enableMultipartPutObject = builder._enableMultipartPutObject; _bufferSize = builder._bufferSize; + _clientMultipartEnabled = builder._multipartEnabled != null && builder._multipartEnabled; } /** @@ -147,16 +150,19 @@ public CompletableFuture putObject(PutObjectRequest putObject } private CompletableFuture multipartPutObject(PutObjectRequest putObjectRequest, AsyncRequestBody requestBody) { - S3AsyncClient crtClient; - if (_wrappedClient instanceof S3CrtAsyncClient) { + S3AsyncClient mpuClient; + if (_wrappedClient instanceof S3CrtAsyncClient && !_clientMultipartEnabled) { // if the wrappedClient is a CRT, use it - crtClient = _wrappedClient; - } else { - // else create a default one - crtClient = S3AsyncClient.crtCreate(); + mpuClient = _wrappedClient; + } else if (_clientMultipartEnabled) { + mpuClient = _wrappedClient; + } + else { + // else create a default CRT client + mpuClient = S3AsyncClient.crtCreate(); } PutEncryptedObjectPipeline pipeline = PutEncryptedObjectPipeline.builder() - .s3AsyncClient(crtClient) + .s3AsyncClient(mpuClient) .cryptoMaterialsManager(_cryptoMaterialsManager) .secureRandom(_secureRandom) .build(); @@ -291,8 +297,12 @@ public static class Builder implements S3AsyncClientBuilder { private S3Configuration _serviceConfiguration = null; private Boolean _accelerate = null; private Boolean _disableMultiRegionAccessPoints = null; + private Boolean _disableS3ExpressSessionAuth = null; private Boolean _forcePathStyle = null; private Boolean _useArnRegion = null; + private Boolean _crossRegionAccessEnabled = null; + private Boolean _multipartEnabled = null; + private MultipartConfiguration _multipartConfiguration = null; private Builder() { } @@ -696,6 +706,12 @@ public Builder disableMultiRegionAccessPoints(Boolean disableMultiRegionAccessPo return this; } + @Override + public S3AsyncClientBuilder disableS3ExpressSessionAuth(Boolean disableS3ExpressSessionAuth) { + _disableS3ExpressSessionAuth = disableS3ExpressSessionAuth; + return this; + } + /** * Forces this client to use path-style addressing for buckets. * @@ -719,6 +735,24 @@ public Builder useArnRegion(Boolean useArnRegion) { return this; } + @Override + public Builder multipartEnabled(Boolean enabled) { + _multipartEnabled = enabled; + return this; + } + + @Override + public S3AsyncClientBuilder multipartConfiguration(MultipartConfiguration multipartConfiguration) { + _multipartConfiguration = multipartConfiguration; + return this; + } + + @Override + public Builder crossRegionAccessEnabled(Boolean crossRegionAccessEnabled) { + _crossRegionAccessEnabled = crossRegionAccessEnabled; + return this; + } + /** * Validates and builds the S3AsyncEncryptionClient according * to the configuration options passed to the Builder object. @@ -737,6 +771,12 @@ public S3AsyncEncryptionClient build() { _bufferSize = DEFAULT_BUFFER_SIZE_BYTES; } + // The S3 Async Client has its own multipart setting, + // we enforce that the S3EC multipart PutObject setting is enabled as well. + if (_multipartEnabled != null && _multipartEnabled && !_enableMultipartPutObject) { + throw new S3EncryptionClientException("EnableMultipartPutObject MUST be enabled when the MultipartEnabled option is set to true."); + } + if (_wrappedClient == null) { _wrappedClient = S3AsyncClient.builder() .credentialsProvider(_awsCredentialsProvider) @@ -751,8 +791,12 @@ public S3AsyncEncryptionClient build() { .serviceConfiguration(_serviceConfiguration) .accelerate(_accelerate) .disableMultiRegionAccessPoints(_disableMultiRegionAccessPoints) + .disableS3ExpressSessionAuth(_disableS3ExpressSessionAuth) .forcePathStyle(_forcePathStyle) .useArnRegion(_useArnRegion) + .crossRegionAccessEnabled(_crossRegionAccessEnabled) + .multipartEnabled(_multipartEnabled) + .multipartConfiguration(_multipartConfiguration) .build(); } diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index 1c3f6c27a..7f3f1f6be 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -544,8 +544,10 @@ public static class Builder implements S3BaseClientBuilder encryptionContext = new HashMap<>(); + encryptionContext.put("user-metadata-key", "user-metadata-value-v3-to-v3"); + + ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); + + CompletableFuture futurePut = v3Client.putObject(builder -> builder + .bucket(BUCKET) + .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) + .key(objectKey), AsyncRequestBody.fromInputStream(inputStream, fileSizeLimit, singleThreadExecutor)); + futurePut.join(); + singleThreadExecutor.shutdown(); + + // Asserts + CompletableFuture> getFuture = v3Client.getObject(builder -> builder + .bucket(BUCKET) + .overrideConfiguration(S3EncryptionClient.withAdditionalConfiguration(encryptionContext)) + .key(objectKey), AsyncResponseTransformer.toBlockingInputStream()); + ResponseInputStream output = getFuture.join(); + + assertTrue(IOUtils.contentEquals(objectStreamForResult, output)); + + deleteObject(BUCKET, objectKey, v3Client); + v3Client.close(); + } }