Skip to content

feat: extend delegating client to enable non-encrypted API operations… #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectResponse;
Expand Down Expand Up @@ -36,7 +37,7 @@
import java.util.function.Consumer;
import java.util.function.Function;

public class S3AsyncEncryptionClient implements S3AsyncClient {
public class S3AsyncEncryptionClient extends DelegatingS3AsyncClient {

private final S3AsyncClient _wrappedClient;
private final S3AsyncClient _wrappedCrtClient;
Expand All @@ -48,6 +49,7 @@ public class S3AsyncEncryptionClient implements S3AsyncClient {
private final boolean _enableMultipartPutObject;

private S3AsyncEncryptionClient(Builder builder) {
super(builder._wrappedClient);
_wrappedClient = builder._wrappedClient;
_wrappedCrtClient = builder._wrappedCrtClient;
_cryptoMaterialsManager = builder._cryptoMaterialsManager;
Expand Down Expand Up @@ -123,11 +125,6 @@ public CompletableFuture<DeleteObjectsResponse> deleteObjects(DeleteObjectsReque
.build());
}

@Override
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nb: the delegating client's implementation is final but also identical to this one.

public String serviceName() {
return _wrappedClient.serviceName();
}

@Override
public void close() {
_wrappedClient.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.http.AbortableInputStream;
import software.amazon.awssdk.services.s3.DelegatingS3Client;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.AbortMultipartUploadRequest;
Expand Down Expand Up @@ -59,13 +60,14 @@
* This client is a drop-in replacement for the S3 client. It will automatically encrypt objects
* on putObject and decrypt objects on getObject using the provided encryption key(s).
*/
public class S3EncryptionClient implements S3Client {
public class S3EncryptionClient extends DelegatingS3Client {

// Used for request-scoped encryption contexts for supporting keys
public static final ExecutionAttribute<Map<String, String>> ENCRYPTION_CONTEXT = new ExecutionAttribute<>("EncryptionContext");
// TODO: Replace with UploadPartRequest.isLastPart() when launched.
// Used for multipart uploads
public static final ExecutionAttribute<Boolean> IS_LAST_PART = new ExecutionAttribute<>("isLastPart");

private final S3AsyncClient _wrappedClient;
private final S3AsyncClient _wrappedCrtClient;
private final CryptographicMaterialsManager _cryptoMaterialsManager;
Expand All @@ -77,6 +79,9 @@ public class S3EncryptionClient implements S3Client {
private final MultipartUploadObjectPipeline _multipartPipeline;

private S3EncryptionClient(Builder builder) {
// The non-encrypted APIs will use a default client.
// In the future, we may want to make this configurable.
super(S3Client.create());
_wrappedClient = builder._wrappedClient;
_wrappedCrtClient = builder._wrappedCrtClient;
_cryptoMaterialsManager = builder._cryptoMaterialsManager;
Expand Down Expand Up @@ -206,11 +211,6 @@ public AbortMultipartUploadResponse abortMultipartUpload(AbortMultipartUploadReq
return _multipartPipeline.abortMultipartUpload(request);
}

@Override
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nb: the delegating client's implementation is final but also identical to this one.

public String serviceName() {
return _wrappedClient.serviceName();
}

@Override
public void close() {
_wrappedClient.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.CopyObjectResponse;
import software.amazon.awssdk.services.s3.model.DeleteObjectResponse;
import software.amazon.awssdk.services.s3.model.DeleteObjectsResponse;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
Expand All @@ -38,7 +39,6 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET;
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix;
import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject;
Expand Down Expand Up @@ -120,6 +120,36 @@ public void putDefaultGetAsync() {
v3AsyncClient.close();
}

@Test
public void putAsyncGetAsync() {
final String objectKey = appendTestSuffix("put-async-get-async");

S3AsyncClient v3AsyncClient = S3AsyncEncryptionClient.builder()
.aesKey(AES_KEY)
.build();

final String input = "PutAsyncGetAsync";

CompletableFuture<PutObjectResponse> futurePut = v3AsyncClient.putObject(builder -> builder
.bucket(BUCKET)
.key(objectKey)
.build(), AsyncRequestBody.fromString(input));
// Block on completion of the futurePut
futurePut.join();

CompletableFuture<ResponseBytes<GetObjectResponse>> futureGet = v3AsyncClient.getObject(builder -> builder
.bucket(BUCKET)
.key(objectKey)
.build(), AsyncResponseTransformer.toBytes());
// Just wait for the future to complete
ResponseBytes<GetObjectResponse> getResponse = futureGet.join();
assertEquals(input, getResponse.asUtf8String());

// Cleanup
deleteObject(BUCKET, objectKey, v3AsyncClient);
v3AsyncClient.close();
}

@Test
public void aesCbcV1toV3Async() {
final String objectKey = appendTestSuffix("aes-cbc-v1-to-v3-async");
Expand Down Expand Up @@ -333,4 +363,46 @@ public void deleteObjectWithWrongObjectKeySuccessAsync() {
// Cleanup
v3Client.close();
}

@Test
public void copyObjectTransparentlyAsync() {
final String objectKey = appendTestSuffix("copy-object-from-here-async");
final String newObjectKey = appendTestSuffix("copy-object-to-here-async");

S3AsyncClient v3AsyncClient = S3AsyncEncryptionClient.builder()
.aesKey(AES_KEY)
.build();

final String input = "CopyObjectAsync";

CompletableFuture<PutObjectResponse> futurePut = v3AsyncClient.putObject(builder -> builder
.bucket(BUCKET)
.key(objectKey)
.build(), AsyncRequestBody.fromString(input));
// Block on completion of the futurePut
futurePut.join();

CompletableFuture<CopyObjectResponse> futureCopy = v3AsyncClient.copyObject(builder -> builder
.sourceBucket(BUCKET)
.destinationBucket(BUCKET)
.sourceKey(objectKey)
.destinationKey(newObjectKey)
.build());
// Block on copy future
futureCopy.join();

// Decrypt new object
CompletableFuture<ResponseBytes<GetObjectResponse>> futureGet = v3AsyncClient.getObject(builder -> builder
.bucket(BUCKET)
.key(newObjectKey)
.build(), AsyncResponseTransformer.toBytes());
ResponseBytes<GetObjectResponse> getResponse = futureGet.join();
assertEquals(input, getResponse.asUtf8String());

// Cleanup
deleteObject(BUCKET, objectKey, v3AsyncClient);
deleteObject(BUCKET, newObjectKey, v3AsyncClient);
v3AsyncClient.close();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
Expand All @@ -36,7 +35,6 @@
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,43 @@ public static void setUp() throws NoSuchAlgorithmException {
RSA_KEY_PAIR = keyPairGen.generateKeyPair();
}

@Test
public void copyObjectTransparently() {
final String objectKey = appendTestSuffix("copy-object-from-here");
final String newObjectKey = appendTestSuffix("copy-object-to-here");

S3Client s3EncryptionClient = S3EncryptionClient.builder()
.kmsKeyId(KMS_KEY_ID)
.build();

final String input = "SimpleTestOfV3EncryptionClientCopyObject";

s3EncryptionClient.putObject(builder -> builder
.bucket(BUCKET)
.key(objectKey)
.build(),
RequestBody.fromString(input));

s3EncryptionClient.copyObject(builder -> builder
.sourceBucket(BUCKET)
.destinationBucket(BUCKET)
.sourceKey(objectKey)
.destinationKey(newObjectKey)
.build());

ResponseBytes<GetObjectResponse> objectResponse = s3EncryptionClient.getObjectAsBytes(builder -> builder
.bucket(BUCKET)
.key(newObjectKey)
.build());
String output = objectResponse.asUtf8String();
assertEquals(input, output);

// Cleanup
deleteObject(BUCKET, objectKey, s3EncryptionClient);
deleteObject(BUCKET, newObjectKey, s3EncryptionClient);
s3EncryptionClient.close();
}

@Test
public void deleteObjectWithInstructionFileSuccess() {
final String objectKey = appendTestSuffix("delete-object-with-instruction-file");
Expand Down