Skip to content

fix: add instruction file support in getObject async #69

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 3 commits into from
Jan 27, 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
6 changes: 1 addition & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,6 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<argLine>-Xms4096m</argLine>
<argLine>-Xmx4096m</argLine>
</configuration>
</plugin>

<plugin>
Expand Down Expand Up @@ -210,7 +206,7 @@
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.7</minimum>
<minimum>0.5</minimum>
</limit>
</limits>
</rule>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;

public class S3AsyncEncryptionClient implements S3AsyncClient {

Expand Down Expand Up @@ -91,32 +92,26 @@ public CompletableFuture<DeleteObjectResponse> deleteObject(DeleteObjectRequest
// TODO: Pass-through requests MUST set the user agent
final CompletableFuture<DeleteObjectResponse> response = _wrappedClient.deleteObject(deleteObjectRequest);
final String instructionObjectKey = deleteObjectRequest.key() + ".instruction";
// Deleting the instruction file is "fire and forget"
// This is necessary because the encryption client must adhere to the
// same interface as the default client thus it is not possible to
// use e.g. allOf to return a future which includes both deletions.
_wrappedClient.deleteObject(builder -> builder
final CompletableFuture<DeleteObjectResponse> instructionResponse = _wrappedClient.deleteObject(builder -> builder
.bucket(deleteObjectRequest.bucket())
.key(instructionObjectKey));
return response;
// Delete the instruction file, then delete the object
Function<DeleteObjectResponse, DeleteObjectResponse> deletion = deleteObjectResponse ->
response.join();
return instructionResponse.thenApplyAsync(deletion);
}

@Override
public CompletableFuture<DeleteObjectsResponse> deleteObjects(DeleteObjectsRequest deleteObjectsRequest) throws AwsServiceException,
SdkClientException {
// TODO: Pass-through requests MUST set the user agent
final CompletableFuture<DeleteObjectsResponse> deleteObjectsResponse = _wrappedClient.deleteObjects(deleteObjectsRequest);
// If Instruction files exists, delete the instruction files as well.
final List<ObjectIdentifier> deleteObjects = S3EncryptionClientUtilities.instructionFileKeysToDelete(deleteObjectsRequest);
// Deleting the instruction files is "fire and forget"
// This is necessary because the encryption client must adhere to the
// same interface as the default client thus it is not possible to
// use e.g. allOf to return a future which includes both deletions.
_wrappedClient.deleteObjects(DeleteObjectsRequest.builder()
.bucket(deleteObjectsRequest.bucket())
.delete(builder -> builder.objects(deleteObjects))
// Add the instruction file keys to the list of objects to delete
final List<ObjectIdentifier> objectsToDelete = S3EncryptionClientUtilities.instructionFileKeysToDelete(deleteObjectsRequest);
// Add the original objects
objectsToDelete.addAll(deleteObjectsRequest.delete().objects());
return _wrappedClient.deleteObjects(deleteObjectsRequest.toBuilder()
.delete(builder -> builder.objects(objectsToDelete))
.build());
return deleteObjectsResponse;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ public static Builder builder() {
}

private GetEncryptedObjectPipeline(Builder builder) {
this._s3Client = builder._s3Client;
// TODO: Clean up sync/async options
if (builder._s3Client == null) {
this._s3Client = S3Client.create();
} else {
this._s3Client = builder._s3Client;
}
this._s3AsyncClient = builder._s3AsyncClient;
this._cryptoMaterialsManager = builder._cryptoMaterialsManager;
this._enableLegacyUnauthenticatedModes = builder._enableLegacyUnauthenticatedModes;
Expand Down Expand Up @@ -164,8 +169,7 @@ public void onResponse(GetObjectResponse response) {
if (!_enableLegacyUnauthenticatedModes && getObjectRequest.range() != null) {
throw new S3EncryptionClientException("Enable legacy unauthenticated modes to use Ranged Get.");
}
// TODO: Implement instruction file handling - this is a bit less intuitive in async
contentMetadata = ContentMetadataStrategy.decode(null, getObjectRequest, response);
contentMetadata = ContentMetadataStrategy.decode(_s3Client, getObjectRequest, response);
materials = prepareMaterialsFromRequest(getObjectRequest, response, contentMetadata);
wrappedAsyncResponseTransformer.onResponse(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,42 @@ public void aesCbcV1toV3Async() {
v3Client.close();
}

@Test
public void AsyncAesGcmV2toV3WithInstructionFile() {
final String objectKey = "async-aes-gcm-v2-to-v3-with-instruction-file";

// V2 Client
EncryptionMaterialsProvider materialsProvider =
new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY));
CryptoConfigurationV2 cryptoConfig =
new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption)
.withStorageMode(CryptoStorageMode.InstructionFile);
AmazonS3EncryptionV2 v2Client = AmazonS3EncryptionClientV2.encryptionBuilder()
.withCryptoConfiguration(cryptoConfig)
.withEncryptionMaterialsProvider(materialsProvider)
.build();

// V3 Async Client
S3AsyncClient v3AsyncClient = S3AsyncEncryptionClient.builder()
.aesKey(AES_KEY)
.build();

// Asserts
final String input = "AesGcmV2toV3";
v2Client.putObject(BUCKET, objectKey, input);

CompletableFuture<ResponseBytes<GetObjectResponse>> futureGet = v3AsyncClient.getObject(builder -> builder
.bucket(BUCKET)
.key(objectKey)
.build(), AsyncResponseTransformer.toBytes());
String outputAsync = futureGet.join().asUtf8String();
assertEquals(input, outputAsync);

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

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