From 7353923e19bffad4ebede6ad29c26fc044b27754 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Thu, 25 Jul 2024 12:15:13 -0700 Subject: [PATCH 01/17] feat: add top level client configuration option --- .../encryption/s3/S3EncryptionClient.java | 116 +++++++++++++++++- .../S3EncryptionClientCompatibilityTest.java | 9 +- ...ptionClientRangedGetCompatibilityTest.java | 4 +- .../encryption/s3/S3EncryptionClientTest.java | 116 +++++++++++++++++- .../S3EncryptionClientTestResources.java | 19 +++ 5 files changed, 251 insertions(+), 13 deletions(-) diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index a5d401e62..abb905c98 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -3,16 +3,22 @@ package software.amazon.encryption.s3; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.interceptor.ExecutionAttribute; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.endpoints.EndpointProvider; import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.kms.KmsClient; import software.amazon.awssdk.services.s3.DelegatingS3Client; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; @@ -52,6 +58,7 @@ import javax.crypto.SecretKey; import java.io.IOException; +import java.net.URI; import java.security.KeyPair; import java.security.Provider; import java.security.SecureRandom; @@ -502,7 +509,7 @@ public void close() { // This is very similar to the S3EncryptionClient builder // Make sure to keep both clients in mind when adding new builder options - public static class Builder { + public static class Builder implements AwsClientBuilder { // The non-encrypted APIs will use a default client. private S3Client _wrappedClient; private S3AsyncClient _wrappedAsyncClient; @@ -521,6 +528,15 @@ public static class Builder { private boolean _enableLegacyUnauthenticatedModes = false; private long _bufferSize = -1L; + // generic AwsClient configuration to be shared by default clients + private AwsCredentialsProvider _awsCredentialsProvider = null; + private Region _region = null; + private boolean _dualStackEnabled = false; + private boolean _fipsEnabled = false; + private ClientOverrideConfiguration _overrideConfiguration = null; + // this should only be applied to S3 clients + private URI _endpointOverride = null; + private Builder() { } @@ -756,6 +772,76 @@ public Builder secureRandom(SecureRandom secureRandom) { return this; } + /** + * The credentials provider to use for all inner clients, including KMS, if a KMS key ID is provided. + * @param awsCredentialsProvider + * @return + */ + @Override + public Builder credentialsProvider(AwsCredentialsProvider awsCredentialsProvider) { + _awsCredentialsProvider = awsCredentialsProvider; + return this; + } + + /** + * The AWS region to use for all inner clients, including KMS, if a KMS key ID is provided. + * @param region + * @return + */ + @Override + public Builder region(Region region) { + _region = region; + return this; + } + + @Override + public Builder dualstackEnabled(Boolean isDualStackEnabled) { + _dualStackEnabled = isDualStackEnabled; + return this; + } + + @Override + public Builder fipsEnabled(Boolean isFipsEnabled) { + _fipsEnabled = isFipsEnabled; + return this; + } + + @Override + public Builder overrideConfiguration(ClientOverrideConfiguration overrideConfiguration) { + _overrideConfiguration = overrideConfiguration; + return this; + } + + /** + * Retrieve the current override configuration. This allows further overrides across calls. Can be modified by first + * converting to a builder with {@link ClientOverrideConfiguration#toBuilder()}. + * + * @return The existing override configuration for the builder. + */ + @Override + public ClientOverrideConfiguration overrideConfiguration() { + return _overrideConfiguration; + } + + /** + * Configure the endpoint with which the SDK should communicate. + * NOTE: For the S3EncryptionClient, this ONLY overrides the endpoint for S3 clients. + * To set the endpointOverride for a KMS client, explicitly configure it and create a + * KmsKeyring instance for the encryption client to use. + *

+ * It is important to know that {@link EndpointProvider}s and the endpoint override on the client are not mutually + * exclusive. In all existing cases, the endpoint override is passed as a parameter to the provider and the provider *may* + * modify it. For example, the S3 provider may add the bucket name as a prefix to the endpoint override for virtual bucket + * addressing. + * + * @param endpointOverride + */ + @Override + public Builder endpointOverride(URI endpointOverride) { + _endpointOverride = endpointOverride; + return this; + } + /** * Validates and builds the S3EncryptionClient according * to the configuration options passed to the Builder object. @@ -775,11 +861,25 @@ public S3EncryptionClient build() { } if (_wrappedClient == null) { - _wrappedClient = S3Client.create(); + _wrappedClient = S3Client.builder() + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .endpointOverride(_endpointOverride) + .build(); } if (_wrappedAsyncClient == null) { - _wrappedAsyncClient = S3AsyncClient.create(); + _wrappedAsyncClient = S3AsyncClient.builder() + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .endpointOverride(_endpointOverride) + .build(); } if (_keyring == null) { @@ -796,7 +896,16 @@ public S3EncryptionClient build() { .secureRandom(_secureRandom) .build(); } else if (_kmsKeyId != null) { + KmsClient kmsClient = KmsClient.builder() + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .build(); + _keyring = KmsKeyring.builder() + .kmsClient(kmsClient) .wrappingKeyId(_kmsKeyId) .enableLegacyWrappingAlgorithms(_enableLegacyWrappingAlgorithms) .secureRandom(_secureRandom) @@ -819,5 +928,6 @@ public S3EncryptionClient build() { return new S3EncryptionClient(this); } + } } diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java index 3e8fa78fe..e9f88d685 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3; -import com.amazonaws.regions.Region; -import com.amazonaws.regions.Regions; import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; import com.amazonaws.services.s3.AmazonS3Encryption; @@ -43,6 +41,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_REGION; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; @@ -52,10 +53,6 @@ */ public class S3EncryptionClientCompatibilityTest { - private static final String BUCKET = System.getenv("AWS_S3EC_TEST_BUCKET"); - private static final String KMS_KEY_ID = System.getenv("AWS_S3EC_TEST_KMS_KEY_ID"); - private static final Region KMS_REGION = Region.getRegion(Regions.fromName(System.getenv("AWS_REGION"))); - private static SecretKey AES_KEY; private static KeyPair RSA_KEY_PAIR; diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientRangedGetCompatibilityTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientRangedGetCompatibilityTest.java index 5e23168bf..305dda8ee 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientRangedGetCompatibilityTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientRangedGetCompatibilityTest.java @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3; -import com.amazonaws.regions.Region; -import com.amazonaws.regions.Regions; import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; import com.amazonaws.services.s3.AmazonS3Encryption; @@ -38,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_REGION; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; @@ -46,7 +45,6 @@ */ public class S3EncryptionClientRangedGetCompatibilityTest { - private static final Region KMS_REGION = Region.getRegion(Regions.fromName(System.getenv("AWS_REGION"))); private static SecretKey AES_KEY; private static KeyPair RSA_KEY_PAIR; diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java index 7b9843a9c..dfb986464 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java @@ -13,14 +13,17 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.kms.KmsClient; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.NoSuchBucketException; -import software.amazon.awssdk.services.s3.model.NoSuchKeyException; import software.amazon.awssdk.services.s3.model.NoSuchUploadException; import software.amazon.awssdk.services.s3.model.ObjectIdentifier; import software.amazon.awssdk.services.s3.model.PutObjectRequest; @@ -29,6 +32,7 @@ import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager; import software.amazon.encryption.s3.materials.KmsKeyring; import software.amazon.encryption.s3.utils.BoundedInputStream; +import software.amazon.encryption.s3.utils.S3EncryptionClientTestResources; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -48,6 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -57,6 +62,7 @@ import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ALIAS; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_REGION; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; @@ -688,6 +694,46 @@ public void abortMultipartUploadFailure() { v3Client.close(); } + @Test + public void s3EncryptionClientWithCustomCredentials() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-custom-credentials"); + + // use the default creds, but through an explicit credentials provider + AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); + + S3Client wrappedClient = S3Client + .builder() + .credentialsProvider(creds) + .build(); + S3AsyncClient wrappedAsyncClient = S3AsyncClient + .builder() + .credentialsProvider(creds) + .build(); + KmsClient kmsClient = KmsClient + .builder() + .credentialsProvider(creds) + .build(); + + KmsKeyring keyring = KmsKeyring + .builder() + .kmsClient(kmsClient) + .wrappingKeyId(KMS_KEY_ID) + .build(); + S3Client s3Client = S3EncryptionClient.builder() + .wrappedClient(wrappedClient) + .wrappedAsyncClient(wrappedAsyncClient) + .keyring(keyring) + .build(); + + simpleV3RoundTrip(s3Client, objectKey); + + // Cleanup + deleteObject(BUCKET, objectKey, s3Client); + wrappedClient.close(); + wrappedAsyncClient.close(); + s3Client.close(); + } + /** * A simple, reusable round-trip (encryption + decryption) using a given * S3Client. Useful for testing client configuration. @@ -711,4 +757,72 @@ private void simpleV3RoundTrip(final S3Client v3Client, final String objectKey) assertEquals(input, output); } + @Test + public void s3EncryptionClientTopLevelCredentials() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-top-level-credentials"); + + // use the default creds, but through an explicit credentials provider + AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); + + S3Client s3Client = S3EncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); + + simpleV3RoundTrip(s3Client, objectKey); + + // Cleanup + deleteObject(BUCKET, objectKey, s3Client); + s3Client.close(); + } + + @Test + public void s3EncryptionClientTopLevelCredentialsWrongRegion() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-top-level-credentials"); + + // use the default creds, but through an explicit credentials provider + AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); + + S3Client s3Client = S3EncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of("eu-west-1")) + .kmsKeyId(KMS_KEY_ID) + .build(); + + try { + simpleV3RoundTrip(s3Client, objectKey); + fail("expected exception"); + } catch (S3EncryptionClientException exception) { + // expected + assertTrue(exception.getMessage().contains("Invalid arn")); + } finally { + // Cleanup + s3Client.close(); + } + } + + @Test + public void s3EncryptionClientTopLevelCredentialsNullCreds() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-null-credentials"); + + AwsCredentialsProvider creds = new S3EncryptionClientTestResources.NullCredentialsProvider(); + + S3Client s3Client = S3EncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of("eu-west-1")) + .kmsKeyId(KMS_KEY_ID) + .build(); + + try { + simpleV3RoundTrip(s3Client, objectKey); + fail("expected exception"); + } catch (S3EncryptionClientException exception) { + // expected + assertTrue(exception.getMessage().contains("Access key ID cannot be blank")); + } finally { + // Cleanup + s3Client.close(); + } + } } diff --git a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java index 0aa498380..40e87ec13 100644 --- a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java +++ b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java @@ -2,8 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3.utils; +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.DeleteObjectResponse; @@ -19,6 +24,20 @@ public class S3EncryptionClientTestResources { public static final String KMS_KEY_ID = System.getenv("AWS_S3EC_TEST_KMS_KEY_ID"); // This alias must point to the same key as KMS_KEY_ID public static final String KMS_KEY_ALIAS = System.getenv("AWS_S3EC_TEST_KMS_KEY_ALIAS"); + public static final Region KMS_REGION = Region.getRegion(Regions.fromName(System.getenv("AWS_REGION"))); + + public static class NullCredentialsProvider implements AwsCredentialsProvider { + + public NullCredentialsProvider() { + super(); + } + + @Override + public AwsCredentials resolveCredentials() { + return AwsBasicCredentials + .create(null, null); + } + } /** * For a given string, append a suffix to distinguish it from From 4d289ef5c36b8f188f9432826413c4dafd7acc50 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Fri, 26 Jul 2024 10:29:58 -0700 Subject: [PATCH 02/17] add top-level creds option for async --- .../s3/S3AsyncEncryptionClient.java | 111 +++++++++++++++++- .../s3/S3AsyncEncryptionClientTest.java | 84 +++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) diff --git a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java index 5fe9188f0..d26182d6e 100644 --- a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java @@ -3,11 +3,17 @@ package software.amazon.encryption.s3; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; +import software.amazon.awssdk.awscore.client.builder.AwsAsyncClientBuilder; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.client.config.ClientAsyncConfiguration; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.internal.crt.S3CrtAsyncClient; @@ -33,6 +39,7 @@ import software.amazon.encryption.s3.materials.RsaKeyring; import javax.crypto.SecretKey; +import java.net.URI; import java.security.KeyPair; import java.security.Provider; import java.security.SecureRandom; @@ -250,7 +257,7 @@ public void close() { // This is very similar to the S3EncryptionClient builder // Make sure to keep both clients in mind when adding new builder options - public static class Builder { + public static class Builder implements AwsAsyncClientBuilder { private S3AsyncClient _wrappedClient; private CryptographicMaterialsManager _cryptoMaterialsManager; private Keyring _keyring; @@ -265,6 +272,15 @@ public static class Builder { private SecureRandom _secureRandom = new SecureRandom(); private long _bufferSize = -1L; + // generic AwsClient configuration to be shared by default clients + private AwsCredentialsProvider _awsCredentialsProvider = null; + private Region _region = null; + private boolean _dualStackEnabled = false; + private boolean _fipsEnabled = false; + private ClientOverrideConfiguration _overrideConfiguration = null; + // this should only be applied to S3 clients + private URI _endpointOverride = null; + private Builder() { } @@ -489,6 +505,99 @@ public Builder secureRandom(SecureRandom secureRandom) { return this; } + /** + * The credentials provider to use for all inner clients, including KMS, if a KMS key ID is provided. + * @param awsCredentialsProvider + * @return + */ + public Builder credentialsProvider(AwsCredentialsProvider awsCredentialsProvider) { + _awsCredentialsProvider = awsCredentialsProvider; + return this; + } + + /** + * The AWS region to use for all inner clients, including KMS, if a KMS key ID is provided. + * @param region + * @return + */ + public Builder region(Region region) { + _region = region; + return this; + } + + public Builder dualstackEnabled(Boolean isDualStackEnabled) { + _dualStackEnabled = isDualStackEnabled; + return this; + } + + public Builder fipsEnabled(Boolean isFipsEnabled) { + _fipsEnabled = isFipsEnabled; + return this; + } + + public Builder overrideConfiguration(ClientOverrideConfiguration overrideConfiguration) { + _overrideConfiguration = overrideConfiguration; + return this; + } + + /** + * Retrieve the current override configuration. This allows further overrides across calls. Can be modified by first + * converting to a builder with {@link ClientOverrideConfiguration#toBuilder()}. + * + * @return The existing override configuration for the builder. + */ + public ClientOverrideConfiguration overrideConfiguration() { + return _overrideConfiguration; + } + + /** + * Specify overrides to the default SDK async configuration that should be used for clients created by this builder. + * + * @param clientAsyncConfiguration + */ + @Override + public Builder asyncConfiguration(ClientAsyncConfiguration clientAsyncConfiguration) { + return null; + } + + /** + * Sets the {@link SdkAsyncHttpClient} that the SDK service client will use to make HTTP calls. This HTTP client may be + * shared between multiple SDK service clients to share a common connection pool. To create a client you must use an + * implementation specific builder. Note that this method is only recommended when you wish to share an HTTP client across + * multiple SDK service clients. If you do not wish to share HTTP clients, it is recommended to use + * {@link #httpClientBuilder(SdkAsyncHttpClient.Builder)} so that service specific default configuration may be applied. + * + *

+ * This client must be closed by the caller when it is ready to be disposed. The SDK will not close the HTTP client + * when the service client is closed. + *

+ * + * @param httpClient + * @return This builder for method chaining. + */ + @Override + public Builder httpClient(SdkAsyncHttpClient httpClient) { + return null; + } + + /** + * Sets a custom HTTP client builder that will be used to obtain a configured instance of {@link SdkAsyncHttpClient}. Any + * service specific HTTP configuration will be merged with the builder's configuration prior to creating the client. When + * there is no desire to share HTTP clients across multiple service clients, the client builder is the preferred way to + * customize the HTTP client as it benefits from service specific defaults. + * + *

+ * Clients created by the builder are managed by the SDK and will be closed when the service client is closed. + *

+ * + * @param httpClientBuilder + * @return This builder for method chaining. + */ + @Override + public Builder httpClientBuilder(SdkAsyncHttpClient.Builder httpClientBuilder) { + return null; + } + /** * Validates and builds the S3AsyncEncryptionClient according * to the configuration options passed to the Builder object. diff --git a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java index 5c6a487f4..c2508c896 100644 --- a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java @@ -17,11 +17,14 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.services.kms.KmsClient; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CopyObjectResponse; @@ -31,6 +34,7 @@ import software.amazon.awssdk.services.s3.model.ObjectIdentifier; import software.amazon.awssdk.services.s3.model.PutObjectResponse; import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.encryption.s3.materials.KmsKeyring; import software.amazon.encryption.s3.utils.BoundedInputStream; import software.amazon.encryption.s3.utils.TinyBufferAsyncRequestBody; @@ -53,6 +57,7 @@ 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.KMS_KEY_ID; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; @@ -67,6 +72,85 @@ public static void setUp() throws NoSuchAlgorithmException { AES_KEY = keyGen.generateKey(); } + @Test + public void asyncCustomConfiguration() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-custom-credentials-async"); + + // use the default creds, but through an explicit credentials provider + AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); + + S3AsyncClient wrappedAsyncClient = S3AsyncClient + .builder() + .credentialsProvider(creds) + .build(); + KmsClient kmsClient = KmsClient + .builder() + .credentialsProvider(creds) + .build(); + + KmsKeyring keyring = KmsKeyring + .builder() + .kmsClient(kmsClient) + .wrappingKeyId(KMS_KEY_ID) + .build(); + S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .wrappedClient(wrappedAsyncClient) + .keyring(keyring) + .build(); + + final String input = "SimpleTestOfV3EncryptionClientAsync"; + + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)); + + ResponseBytes objectResponse = s3Client.getObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); + String output = objectResponse.asUtf8String(); + assertEquals(input, output); + + // Cleanup + deleteObject(BUCKET, objectKey, s3Client); + wrappedAsyncClient.close(); + s3Client.close(); + } + + @Test + public void asyncTopLevelConfiguration() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-top-level-credentials-async"); + + // use the default creds, but through an explicit credentials provider + AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); + + S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .credentialsProvider(creds) + .kmsKeyId(KMS_KEY_ID) + .build(); + + final String input = "SimpleTestOfV3EncryptionClientAsync"; + + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)); + + ResponseBytes objectResponse = s3Client.getObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); + String output = objectResponse.asUtf8String(); + assertEquals(input, output); + + // Cleanup + deleteObject(BUCKET, objectKey, s3Client); + s3Client.close(); + } + @Test public void putAsyncGetDefault() { final String objectKey = appendTestSuffix("put-async-get-default"); From 5aedc40da13f62b06dddbea328afe6a2a42e6b28 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 10:39:08 -0700 Subject: [PATCH 03/17] add test using alternate role --- pom.xml | 8 ++++ .../encryption/s3/S3EncryptionClientTest.java | 42 +++++++++++++++++++ .../S3EncryptionClientTestResources.java | 32 ++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/pom.xml b/pom.xml index ad9c29664..44d0f1baf 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,14 @@ test + + software.amazon.awssdk + sts + 2.20.38 + true + test + + diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java index dfb986464..a7511df09 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java @@ -19,6 +19,7 @@ import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.KmsException; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CreateMultipartUploadResponse; @@ -59,6 +60,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.withSettings; import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.ALTERNATE_KMS_KEY; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ALIAS; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; @@ -825,4 +827,44 @@ public void s3EncryptionClientTopLevelCredentialsNullCreds() { s3Client.close(); } } + + @Test + public void s3EncryptionClientTopLevelAlternateCredentials() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-top-level-credentials"); + + // use alternate creds + AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + + S3Client s3Client = S3EncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); + + // using the original key fails + try { + simpleV3RoundTrip(s3Client, objectKey); + fail("expected exception"); + } catch (S3EncryptionClientException exception) { + // expected + assertTrue(exception.getMessage().contains("is not authorized to perform")); + assertInstanceOf(KmsException.class, exception.getCause()); + } finally { + s3Client.close(); + } + + // using the alternate key succeeds + S3Client s3ClientAltCreds = S3EncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); + + simpleV3RoundTrip(s3ClientAltCreds, objectKey); + + // Cleanup + deleteObject(BUCKET, objectKey, s3ClientAltCreds); + s3ClientAltCreds.close(); + } + } diff --git a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java index 40e87ec13..60cd4cfe5 100644 --- a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java +++ b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java @@ -9,9 +9,12 @@ import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.DeleteObjectResponse; +import software.amazon.awssdk.services.sts.StsClient; +import software.amazon.awssdk.services.sts.model.Credentials; import java.util.concurrent.CompletableFuture; @@ -25,6 +28,35 @@ public class S3EncryptionClientTestResources { // This alias must point to the same key as KMS_KEY_ID public static final String KMS_KEY_ALIAS = System.getenv("AWS_S3EC_TEST_KMS_KEY_ALIAS"); public static final Region KMS_REGION = Region.getRegion(Regions.fromName(System.getenv("AWS_REGION"))); + // Alternate role to test credential configuration and access denied behavior + public static final String ALTERNATE_ROLE_ARN = System.getenv("AWS_S3EC_TEST_ALT_ROLE_ARN"); + // Alternate KMS key, which only the alternate role has access to + public static final String ALTERNATE_KMS_KEY = System.getenv("AWS_S3EC_TEST_ALT_KMS_KEY_ARN"); + + + /** + * Creds provider for the "alternate" role which is useful for testing cred configuration + * and access denied behavior. + */ + public static class AlternateRoleCredentialsProvider implements AwsCredentialsProvider { + + StsClient stsClient_; + + public AlternateRoleCredentialsProvider() { + super(); + stsClient_ = StsClient.create(); + } + + @Override + public AwsCredentials resolveCredentials() { + String sessionName = "s3ec-test" + DateTimeFormat.forPattern("-yyMMdd-hhmmss").print(new DateTime()); + Credentials assumeRoleCreds = stsClient_.assumeRole(builder -> builder + .roleArn(ALTERNATE_ROLE_ARN).roleSessionName(sessionName).build()).credentials(); + return AwsSessionCredentials.create(assumeRoleCreds.accessKeyId(), + assumeRoleCreds.secretAccessKey(), + assumeRoleCreds.sessionToken()); + } + } public static class NullCredentialsProvider implements AwsCredentialsProvider { From 862ecf4bc86b2eabb48851247fa37dc3a80fc467 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 13:00:36 -0700 Subject: [PATCH 04/17] add Cfn changes, another test --- cfn/S3EC-GitHub-CF-Template.yml | 72 +++++++++++++++++++ .../encryption/s3/S3EncryptionClientTest.java | 27 +++++++ 2 files changed, 99 insertions(+) diff --git a/cfn/S3EC-GitHub-CF-Template.yml b/cfn/S3EC-GitHub-CF-Template.yml index 9b498ca47..db8ae89fa 100644 --- a/cfn/S3EC-GitHub-CF-Template.yml +++ b/cfn/S3EC-GitHub-CF-Template.yml @@ -14,6 +14,20 @@ Resources: Action: 'kms:*' Resource: '*' + S3ECGitHubKMSKeyIDAlternate: + Type: 'AWS::KMS::Key' + Properties: + Description: Alternate KMS Key for GitHub Action Workflow + Enabled: true + KeyPolicy: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root' + Action: 'kms:*' + Resource: '*' + S3ECGitHubKMSKeyAlias: Type: 'AWS::KMS::Alias' Properties: @@ -73,6 +87,28 @@ Resources: } ManagedPolicyName: S3EC-GitHub-KMS-Key-Policy + S3ECGitHubKMSKeyPolicyAlternate: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + PolicyDocument: !Sub | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": [ + "arn:aws:kms:*:${AWS::AccountId}:key/${S3ECGitHubKMSKeyIDAlternate}" + ], + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:GenerateDataKeyPair" + ] + } + ] + } + ManagedPolicyName: S3EC-GitHub-KMS-Key-Policy-Alternate + S3ECGithubTestRole: Type: 'AWS::IAM::Role' Properties: @@ -108,3 +144,39 @@ Resources: ManagedPolicyArns: - !Ref S3ECGitHubKMSKeyPolicy - !Ref S3ECGitHubS3BucketPolicy + + S3ECGithubTestRoleAlternate: + Type: 'AWS::IAM::Role' + Properties: + Path: /service-role/ + RoleName: S3EC-GitHub-test-role-alternate + AssumeRolePolicyDocument: !Sub | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com" }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" + }, + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo:aws/amazon-s3-encryption-client-java:*" + } + } + }, + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::${AWS::AccountId}:role/ToolsDevelopment" }, + "Action": "sts:AssumeRole" + } + ] + } + Description: >- + Grant GitHub S3 put and get and KMS (alt key) encrypt, decrypt, and generate access + for testing + ManagedPolicyArns: + - !Ref S3ECGitHubKMSKeyPolicyAlternate + - !Ref S3ECGitHubS3BucketPolicy diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java index a7511df09..4d4303362 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java @@ -867,4 +867,31 @@ public void s3EncryptionClientTopLevelAlternateCredentials() { s3ClientAltCreds.close(); } + @Test + public void s3EncryptionClientMixedCredentials() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-mixed-credentials"); + + // use alternate creds for KMS, + // default for S3 + AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + KmsClient kmsClient = KmsClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .build(); + KmsKeyring kmsKeyring = KmsKeyring.builder() + .kmsClient(kmsClient) + .wrappingKeyId(ALTERNATE_KMS_KEY) + .build(); + + S3Client s3Client = S3EncryptionClient.builder() + .keyring(kmsKeyring) + .build(); + + simpleV3RoundTrip(s3Client, objectKey); + + // Cleanup + deleteObject(BUCKET, objectKey, s3Client); + s3Client.close(); + kmsClient.close(); + } } From 641c206620c9743bece3b61af363c7c0efe93b17 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 13:11:20 -0700 Subject: [PATCH 05/17] update CI --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c34e0db2f..738ee9333 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,8 @@ jobs: - name: Test run: | + export AWS_S3EC_TEST_ALT_KMS_KEY_ARN=arn:aws:kms:${{ vars.CI_AWS_REGION }}:${{ secrets.CI_AWS_ACCOUNT_ID }}:key/${{ vars.CI_ALT_KMS_KEY_ID }} + export AWS_S3EC_TEST_ALT_ROLE_ARN=arn:aws:iam::${{ secrets.CI_AWS_ACCOUNT_ID }}:role/service-role/${{ CI_ALT_ROLE }} export AWS_S3EC_TEST_BUCKET=${{ vars.CI_S3_BUCKET }} export AWS_S3EC_TEST_KMS_KEY_ID=arn:aws:kms:${{ vars.CI_AWS_REGION }}:${{ secrets.CI_AWS_ACCOUNT_ID }}:key/${{ vars.CI_KMS_KEY_ID }} export AWS_S3EC_TEST_KMS_KEY_ALIAS=arn:aws:kms:${{ vars.CI_AWS_REGION }}:${{ secrets.CI_AWS_ACCOUNT_ID }}:alias/${{ vars.CI_KMS_KEY_ALIAS }} From c1dbd9a324b0ff7186a42cf0875d239d515d9bdd Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 13:19:24 -0700 Subject: [PATCH 06/17] fix CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 738ee9333..e1ea6dcf0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: - name: Test run: | export AWS_S3EC_TEST_ALT_KMS_KEY_ARN=arn:aws:kms:${{ vars.CI_AWS_REGION }}:${{ secrets.CI_AWS_ACCOUNT_ID }}:key/${{ vars.CI_ALT_KMS_KEY_ID }} - export AWS_S3EC_TEST_ALT_ROLE_ARN=arn:aws:iam::${{ secrets.CI_AWS_ACCOUNT_ID }}:role/service-role/${{ CI_ALT_ROLE }} + export AWS_S3EC_TEST_ALT_ROLE_ARN=arn:aws:iam::${{ secrets.CI_AWS_ACCOUNT_ID }}:role/service-role/${{ vars.CI_ALT_ROLE }} export AWS_S3EC_TEST_BUCKET=${{ vars.CI_S3_BUCKET }} export AWS_S3EC_TEST_KMS_KEY_ID=arn:aws:kms:${{ vars.CI_AWS_REGION }}:${{ secrets.CI_AWS_ACCOUNT_ID }}:key/${{ vars.CI_KMS_KEY_ID }} export AWS_S3EC_TEST_KMS_KEY_ALIAS=arn:aws:kms:${{ vars.CI_AWS_REGION }}:${{ secrets.CI_AWS_ACCOUNT_ID }}:alias/${{ vars.CI_KMS_KEY_ALIAS }} From 462347fcced09a30a4a7f52a0c78211e51c678bd Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 14:16:45 -0700 Subject: [PATCH 07/17] fix cfn --- cfn/S3EC-GitHub-CF-Template.yml | 44 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/cfn/S3EC-GitHub-CF-Template.yml b/cfn/S3EC-GitHub-CF-Template.yml index db8ae89fa..4a9effae2 100644 --- a/cfn/S3EC-GitHub-CF-Template.yml +++ b/cfn/S3EC-GitHub-CF-Template.yml @@ -109,11 +109,11 @@ Resources: } ManagedPolicyName: S3EC-GitHub-KMS-Key-Policy-Alternate - S3ECGithubTestRole: + S3ECGithubTestRoleAlternate: Type: 'AWS::IAM::Role' Properties: Path: /service-role/ - RoleName: S3EC-GitHub-test-role + RoleName: S3EC-GitHub-test-role-alternate AssumeRolePolicyDocument: !Sub | { "Version": "2012-10-17", @@ -135,21 +135,46 @@ Resources: "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::${AWS::AccountId}:role/ToolsDevelopment" }, "Action": "sts:AssumeRole" + }, + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::${AWS::AccountId}:role/service-role/S3EC-GitHub-test-role" }, + "Action": "sts:AssumeRole" } ] } Description: >- - Grant GitHub S3 put and get and KMS encrypt, decrypt, and generate access + Grant GitHub S3 put and get and KMS (alt key) encrypt, decrypt, and generate access for testing ManagedPolicyArns: - - !Ref S3ECGitHubKMSKeyPolicy + - !Ref S3ECGitHubKMSKeyPolicyAlternate - !Ref S3ECGitHubS3BucketPolicy - S3ECGithubTestRoleAlternate: + S3ECGitHubAssumeAlternatePolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + PolicyDocument: !Sub | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::${AWS::AccountId}:role/service-role/${S3ECGithubTestRoleAlternate}" + ], + "Action": [ + "sts:AssumeRole" + ] + } + ] + } + ManagedPolicyName: S3EC-GitHub-Assume-Alternate-Policy + + S3ECGithubTestRole: Type: 'AWS::IAM::Role' Properties: Path: /service-role/ - RoleName: S3EC-GitHub-test-role-alternate + RoleName: S3EC-GitHub-test-role AssumeRolePolicyDocument: !Sub | { "Version": "2012-10-17", @@ -175,8 +200,11 @@ Resources: ] } Description: >- - Grant GitHub S3 put and get and KMS (alt key) encrypt, decrypt, and generate access + Grant GitHub S3 put and get and KMS encrypt, decrypt, and generate access for testing ManagedPolicyArns: - - !Ref S3ECGitHubKMSKeyPolicyAlternate + - !Ref S3ECGitHubKMSKeyPolicy - !Ref S3ECGitHubS3BucketPolicy + - !Ref S3ECGitHubAssumeAlternatePolicy + + From 5d12e2a55dfb3a7348493ee35bdfd49bfec62c8c Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 14:27:12 -0700 Subject: [PATCH 08/17] apply config in async builder --- .../s3/S3AsyncEncryptionClient.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java index d26182d6e..7a9701754 100644 --- a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java @@ -14,6 +14,7 @@ import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.kms.KmsClient; import software.amazon.awssdk.services.s3.DelegatingS3AsyncClient; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.internal.crt.S3CrtAsyncClient; @@ -617,7 +618,14 @@ public S3AsyncEncryptionClient build() { } if (_wrappedClient == null) { - _wrappedClient = S3AsyncClient.create(); + _wrappedClient = S3AsyncClient.builder() + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .endpointOverride(_endpointOverride) + .build(); } if (_keyring == null) { @@ -634,11 +642,20 @@ public S3AsyncEncryptionClient build() { .secureRandom(_secureRandom) .build(); } else if (_kmsKeyId != null) { + KmsClient kmsClient = KmsClient.builder() + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .build(); + _keyring = KmsKeyring.builder() - .wrappingKeyId(_kmsKeyId) - .enableLegacyWrappingAlgorithms(_enableLegacyWrappingAlgorithms) - .secureRandom(_secureRandom) - .build(); + .kmsClient(kmsClient) + .wrappingKeyId(_kmsKeyId) + .enableLegacyWrappingAlgorithms(_enableLegacyWrappingAlgorithms) + .secureRandom(_secureRandom) + .build(); } } From 343a9e6ad4facdc387132e1013b07fa37a17d395 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 14:49:50 -0700 Subject: [PATCH 09/17] add region, more tests to async tests --- .../s3/S3AsyncEncryptionClientTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java index c2508c896..4a8c38ec7 100644 --- a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java @@ -24,7 +24,9 @@ import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.KmsException; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CopyObjectResponse; @@ -36,6 +38,7 @@ import software.amazon.awssdk.services.s3.model.S3Exception; import software.amazon.encryption.s3.materials.KmsKeyring; import software.amazon.encryption.s3.utils.BoundedInputStream; +import software.amazon.encryption.s3.utils.S3EncryptionClientTestResources; import software.amazon.encryption.s3.utils.TinyBufferAsyncRequestBody; import javax.crypto.KeyGenerator; @@ -54,10 +57,14 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.ALTERNATE_KMS_KEY; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_REGION; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; @@ -82,10 +89,12 @@ public void asyncCustomConfiguration() { S3AsyncClient wrappedAsyncClient = S3AsyncClient .builder() .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) .build(); KmsClient kmsClient = KmsClient .builder() .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) .build(); KmsKeyring keyring = KmsKeyring @@ -128,6 +137,7 @@ public void asyncTopLevelConfiguration() { S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) .kmsKeyId(KMS_KEY_ID) .build(); @@ -151,6 +161,102 @@ public void asyncTopLevelConfiguration() { s3Client.close(); } + @Test + public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { + final String objectKey = appendTestSuffix("wrapped-s3-async-client-with-top-level-credentials"); + final String input = "S3EncryptionClientTopLevelAlternateCredsTest"; + + // use alternate creds + AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + + S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); + + // using the original key fails + try { + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)); + fail("expected exception"); + } catch (S3EncryptionClientException exception) { + // expected + assertTrue(exception.getMessage().contains("is not authorized to perform")); + assertInstanceOf(KmsException.class, exception.getCause()); + } finally { + s3Client.close(); + } + + // using the alternate key succeeds + S3Client s3ClientAltCreds = S3EncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); + + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)); + + ResponseBytes objectResponse = s3Client.getObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); + String output = objectResponse.asUtf8String(); + assertEquals(input, output); + + // Cleanup + deleteObject(BUCKET, objectKey, s3ClientAltCreds); + s3ClientAltCreds.close(); + } + + @Test + public void s3EncryptionClientMixedCredentials() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-mixed-credentials"); + final String input = "S3EncryptionClientTopLevelAlternateCredsTest"; + + // use alternate creds for KMS, + // default for S3 + AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + KmsClient kmsClient = KmsClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .build(); + KmsKeyring kmsKeyring = KmsKeyring.builder() + .kmsClient(kmsClient) + .wrappingKeyId(ALTERNATE_KMS_KEY) + .build(); + + S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); + + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)); + + ResponseBytes objectResponse = s3Client.getObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); + String output = objectResponse.asUtf8String(); + assertEquals(input, output); + + // Cleanup + deleteObject(BUCKET, objectKey, s3Client); + s3Client.close(); + kmsClient.close(); + } @Test public void putAsyncGetDefault() { final String objectKey = appendTestSuffix("put-async-get-default"); From 46f72e9d30546d2e5012a60db88f356e8a894740 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 16:06:22 -0700 Subject: [PATCH 10/17] test fixes --- .../s3/S3AsyncEncryptionClientTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java index 4a8c38ec7..b429425e2 100644 --- a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java @@ -113,7 +113,7 @@ public void asyncCustomConfiguration() { .bucket(BUCKET) .key(objectKey) .build(), - AsyncRequestBody.fromString(input)); + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3Client.getObject(builder -> builder .bucket(BUCKET) @@ -147,7 +147,7 @@ public void asyncTopLevelConfiguration() { .bucket(BUCKET) .key(objectKey) .build(), - AsyncRequestBody.fromString(input)); + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3Client.getObject(builder -> builder .bucket(BUCKET) @@ -181,7 +181,7 @@ public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { .bucket(BUCKET) .key(objectKey) .build(), - AsyncRequestBody.fromString(input)); + AsyncRequestBody.fromString(input)).join(); fail("expected exception"); } catch (S3EncryptionClientException exception) { // expected @@ -202,7 +202,7 @@ public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { .bucket(BUCKET) .key(objectKey) .build(), - AsyncRequestBody.fromString(input)); + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3Client.getObject(builder -> builder .bucket(BUCKET) @@ -217,7 +217,7 @@ public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { } @Test - public void s3EncryptionClientMixedCredentials() { + public void s3AsyncEncryptionClientMixedCredentials() { final String objectKey = appendTestSuffix("wrapped-s3-client-with-mixed-credentials"); final String input = "S3EncryptionClientTopLevelAlternateCredsTest"; @@ -236,14 +236,14 @@ public void s3EncryptionClientMixedCredentials() { S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() .credentialsProvider(creds) .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(ALTERNATE_KMS_KEY) + .keyring(kmsKeyring) .build(); s3Client.putObject(builder -> builder .bucket(BUCKET) .key(objectKey) .build(), - AsyncRequestBody.fromString(input)); + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3Client.getObject(builder -> builder .bucket(BUCKET) @@ -257,6 +257,7 @@ public void s3EncryptionClientMixedCredentials() { s3Client.close(); kmsClient.close(); } + @Test public void putAsyncGetDefault() { final String objectKey = appendTestSuffix("put-async-get-default"); From 2ddf32984e909d8b87755e64bb2ba176334741fe Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Mon, 29 Jul 2024 16:34:30 -0700 Subject: [PATCH 11/17] fix test --- .../encryption/s3/S3AsyncEncryptionClientTest.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java index b429425e2..eacf40d9d 100644 --- a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java @@ -57,7 +57,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -183,28 +182,27 @@ public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { .build(), AsyncRequestBody.fromString(input)).join(); fail("expected exception"); - } catch (S3EncryptionClientException exception) { + } catch (KmsException exception) { // expected assertTrue(exception.getMessage().contains("is not authorized to perform")); - assertInstanceOf(KmsException.class, exception.getCause()); } finally { s3Client.close(); } // using the alternate key succeeds - S3Client s3ClientAltCreds = S3EncryptionClient.builder() + S3AsyncClient s3ClientAltCreds = S3AsyncEncryptionClient.builder() .credentialsProvider(creds) .region(Region.of(KMS_REGION.toString())) .kmsKeyId(ALTERNATE_KMS_KEY) .build(); - s3Client.putObject(builder -> builder + s3ClientAltCreds.putObject(builder -> builder .bucket(BUCKET) .key(objectKey) .build(), AsyncRequestBody.fromString(input)).join(); - ResponseBytes objectResponse = s3Client.getObject(builder -> builder + ResponseBytes objectResponse = s3ClientAltCreds.getObject(builder -> builder .bucket(BUCKET) .key(objectKey) .build(), AsyncResponseTransformer.toBytes()).join(); From 39f1526cfc4b33032e4e759530e5d8fa3541bed7 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Tue, 30 Jul 2024 15:07:20 -0700 Subject: [PATCH 12/17] add examples --- .../s3/examples/AsyncClientExample.java | 10 +- .../examples/ClientConfigurationExample.java | 139 ++++++++++++++++++ .../s3/examples/PartialKeyPairExample.java | 2 +- .../s3/S3AsyncEncryptionClientTest.java | 59 +++++++- .../encryption/s3/S3EncryptionClientTest.java | 48 +++--- .../ClientConfigurationExampleTest.java | 10 ++ .../S3EncryptionClientTestResources.java | 2 + 7 files changed, 239 insertions(+), 31 deletions(-) create mode 100644 src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java create mode 100644 src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java diff --git a/src/examples/java/software/amazon/encryption/s3/examples/AsyncClientExample.java b/src/examples/java/software/amazon/encryption/s3/examples/AsyncClientExample.java index ac30ec93f..6cc1e9e89 100644 --- a/src/examples/java/software/amazon/encryption/s3/examples/AsyncClientExample.java +++ b/src/examples/java/software/amazon/encryption/s3/examples/AsyncClientExample.java @@ -1,9 +1,5 @@ package software.amazon.encryption.s3.examples; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; - import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; @@ -14,6 +10,10 @@ import java.util.concurrent.CompletableFuture; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; + public class AsyncClientExample { public static final String OBJECT_KEY = appendTestSuffix("async-client-example"); @@ -33,7 +33,7 @@ public static void AsyncClient(String bucket) { final String input = "PutAsyncGetAsync"; // Instantiate the S3 Async Encryption Client to encrypt and decrypt - // by specifying an AES Key with the aesKey builder parameter. + // by specifying a KMS key with the kmsKeyId parameter. // // This means that the S3 Async Encryption Client can perform both encrypt and decrypt operations // as part of the S3 putObject and getObject operations. diff --git a/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java b/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java new file mode 100644 index 000000000..879bc4561 --- /dev/null +++ b/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java @@ -0,0 +1,139 @@ +package software.amazon.encryption.s3.examples; + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.encryption.s3.S3EncryptionClient; +import software.amazon.encryption.s3.materials.KmsKeyring; +import software.amazon.encryption.s3.utils.S3EncryptionClientTestResources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.ALTERNATE_KMS_KEY; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_REGION; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.S3_REGION; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; + +public class ClientConfigurationExample { + + public static void main(String[] args) { + CustomClientConfiguration(); + TopLevelClientConfiguration(); + } + + /** + * This example demonstrates how to use specific client configuration for + * the S3 and KMS clients used within the S3 Encryption Client. + */ + public static void CustomClientConfiguration() { + final String objectKey = appendTestSuffix("custom-client-configuration-example"); + final String input = "CustomClientConfigurationExample"; + // Load your AWS credentials from an external source. + final AwsCredentialsProvider defaultCreds = DefaultCredentialsProvider.create(); + // This example uses two different sets of credentials. + final AwsCredentialsProvider altCreds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + + // Instantiate the wrapped S3 client with the default credentials + // and the region to use with S3. + final S3Client wrappedClient = S3Client.builder() + .credentialsProvider(defaultCreds) + .region(Region.of(S3_REGION.toString())) + .build(); + + // Instantiate the wrapped Async S3 client with the default credentials + // and the region to use with S3. + final S3AsyncClient wrappedAsyncClient = S3AsyncClient.builder() + .credentialsProvider(defaultCreds) + .region(Region.of(S3_REGION.toString())) + .build(); + + // Instantiate the KMS client with alternate credentials. + // This client will be used for all KMS requests. + final KmsClient kmsClient = KmsClient.builder() + .credentialsProvider(altCreds) + .region(Region.of(KMS_REGION.toString())) + .build(); + + // Instantiate a KMS Keyring to use with the S3 Encryption Client. + final KmsKeyring kmsKeyring = KmsKeyring.builder() + .wrappingKeyId(ALTERNATE_KMS_KEY) + .kmsClient(kmsClient) + .build(); + + // Instantiate the S3 Encryption Client using the configured clients and keyring. + final S3Client s3Client = S3EncryptionClient.builder() + .wrappedClient(wrappedClient) + .wrappedAsyncClient(wrappedAsyncClient) + .keyring(kmsKeyring) + .build(); + + // Use the client to call putObject. + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + RequestBody.fromString(input)); + + // Use the client to call getObject. + ResponseBytes objectResponse = s3Client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + String output = objectResponse.asUtf8String(); + // Check that the output matches the input. + assertEquals(input, output); + + // Delete the object. + deleteObject(BUCKET, objectKey, s3Client); + // Close the S3 Client. + s3Client.close(); + } + + /** + * This example demonstrates how to use a single client configuration for + * the S3 and KMS clients used within the S3 Encryption Client. + */ + public static void TopLevelClientConfiguration() { + final String objectKey = appendTestSuffix("top-level-client-configuration-example"); + final String input = "TopLevelClientConfigurationExample"; + // Load your AWS credentials from an external source. + final AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + + // Instantiate the S3 Encryption Client via its builder. + // By passing the creds into the credentialsProvider parameter, + // the S3EC will use these creds for both S3 and KMS requests. + final S3Client s3Client = S3EncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); + + // Use the client to call putObject. + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + RequestBody.fromString(input)); + + // Use the client to call getObject. + ResponseBytes objectResponse = s3Client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + String output = objectResponse.asUtf8String(); + // Check that the output matches the input. + assertEquals(input, output); + + // Delete the object. + deleteObject(BUCKET, objectKey, s3Client); + // Close the S3 Client. + s3Client.close(); + } +} diff --git a/src/examples/java/software/amazon/encryption/s3/examples/PartialKeyPairExample.java b/src/examples/java/software/amazon/encryption/s3/examples/PartialKeyPairExample.java index 74d3c3f91..7efb629ba 100644 --- a/src/examples/java/software/amazon/encryption/s3/examples/PartialKeyPairExample.java +++ b/src/examples/java/software/amazon/encryption/s3/examples/PartialKeyPairExample.java @@ -159,7 +159,7 @@ static void useOnlyPrivateKey(final String bucket) { s3ClientPrivateKeyOnly.close(); } - public static void cleanup(final String bucket) { + private static void cleanup(final String bucket) { // The S3 Encryption client is not required when deleting encrypted // objects, use the S3 Client. final S3Client s3Client = S3Client.builder().build(); diff --git a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java index eacf40d9d..d97f3b032 100644 --- a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java @@ -27,6 +27,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.kms.KmsClient; import software.amazon.awssdk.services.kms.model.KmsException; +import software.amazon.awssdk.services.kms.model.NotFoundException; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CopyObjectResponse; @@ -162,7 +163,7 @@ public void asyncTopLevelConfiguration() { @Test public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { - final String objectKey = appendTestSuffix("wrapped-s3-async-client-with-top-level-credentials"); + final String objectKey = appendTestSuffix("wrapped-s3-async-client-with-top-level-alternate-credentials"); final String input = "S3EncryptionClientTopLevelAlternateCredsTest"; // use alternate creds @@ -256,6 +257,62 @@ public void s3AsyncEncryptionClientMixedCredentials() { kmsClient.close(); } + @Test + public void asyncTopLevelConfigurationWrongRegion() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-wrong-region-credentials-async"); + + AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); + + S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of("eu-west-1")) + .kmsKeyId(KMS_KEY_ID) + .build(); + + final String input = "SimpleTestOfV3EncryptionClientAsync"; + + try { + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); + fail("expected exception"); + } catch (NotFoundException e) { + assertTrue(e.getMessage().contains("Invalid arn")); + } finally { + s3Client.close(); + } + } + + @Test + public void asyncTopLevelConfigurationNullCreds() { + final String objectKey = appendTestSuffix("wrapped-s3-client-with-null-credentials-async"); + + AwsCredentialsProvider creds = new S3EncryptionClientTestResources.NullCredentialsProvider(); + + S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); + + final String input = "SimpleTestOfV3EncryptionClientAsync"; + + try { + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); + fail("expected exception"); + } catch (NullPointerException npe) { + assertTrue(npe.getMessage().contains("Access key ID cannot be blank")); + } finally { + s3Client.close(); + } + } + @Test public void putAsyncGetDefault() { final String objectKey = appendTestSuffix("put-async-get-default"); diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java index 4d4303362..38f18e1a8 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java @@ -736,29 +736,6 @@ public void s3EncryptionClientWithCustomCredentials() { s3Client.close(); } - /** - * A simple, reusable round-trip (encryption + decryption) using a given - * S3Client. Useful for testing client configuration. - * - * @param v3Client the client under test - */ - private void simpleV3RoundTrip(final S3Client v3Client, final String objectKey) { - final String input = "SimpleTestOfV3EncryptionClient"; - - v3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - RequestBody.fromString(input)); - - ResponseBytes objectResponse = v3Client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); - String output = objectResponse.asUtf8String(); - assertEquals(input, output); - } - @Test public void s3EncryptionClientTopLevelCredentials() { final String objectKey = appendTestSuffix("wrapped-s3-client-with-top-level-credentials"); @@ -812,7 +789,7 @@ public void s3EncryptionClientTopLevelCredentialsNullCreds() { S3Client s3Client = S3EncryptionClient.builder() .credentialsProvider(creds) - .region(Region.of("eu-west-1")) + .region(Region.of(KMS_REGION.toString())) .kmsKeyId(KMS_KEY_ID) .build(); @@ -894,4 +871,27 @@ public void s3EncryptionClientMixedCredentials() { s3Client.close(); kmsClient.close(); } + + /** + * A simple, reusable round-trip (encryption + decryption) using a given + * S3Client. Useful for testing client configuration. + * + * @param v3Client the client under test + */ + private void simpleV3RoundTrip(final S3Client v3Client, final String objectKey) { + final String input = "SimpleTestOfV3EncryptionClient"; + + v3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + RequestBody.fromString(input)); + + ResponseBytes objectResponse = v3Client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + String output = objectResponse.asUtf8String(); + assertEquals(input, output); + } } diff --git a/src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java b/src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java new file mode 100644 index 000000000..80cce790e --- /dev/null +++ b/src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java @@ -0,0 +1,10 @@ +package software.amazon.encryption.s3.examples; + +import org.junit.jupiter.api.Test; + +public class ClientConfigurationExampleTest { + @Test + public void testClientConfigurationExamples() { + ClientConfigurationExample.main(new String[0]); + } +} diff --git a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java index 60cd4cfe5..1f7ba15d4 100644 --- a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java +++ b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java @@ -27,6 +27,8 @@ public class S3EncryptionClientTestResources { public static final String KMS_KEY_ID = System.getenv("AWS_S3EC_TEST_KMS_KEY_ID"); // This alias must point to the same key as KMS_KEY_ID public static final String KMS_KEY_ALIAS = System.getenv("AWS_S3EC_TEST_KMS_KEY_ALIAS"); + // For now, these are the same. + public static final Region S3_REGION = Region.getRegion(Regions.fromName(System.getenv("AWS_REGION"))); public static final Region KMS_REGION = Region.getRegion(Regions.fromName(System.getenv("AWS_REGION"))); // Alternate role to test credential configuration and access denied behavior public static final String ALTERNATE_ROLE_ARN = System.getenv("AWS_S3EC_TEST_ALT_ROLE_ARN"); From 73e8f224726f754f33563a18770a0f0d7f1e9a15 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Tue, 30 Jul 2024 15:27:00 -0700 Subject: [PATCH 13/17] other async options --- .../s3/S3AsyncEncryptionClient.java | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java index 7a9701754..eff21eaf9 100644 --- a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java @@ -12,6 +12,7 @@ import software.amazon.awssdk.core.client.config.ClientAsyncConfiguration; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.endpoints.EndpointProvider; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.kms.KmsClient; @@ -281,6 +282,10 @@ public static class Builder implements AwsAsyncClientBuilder + * It is important to know that {@link EndpointProvider}s and the endpoint override on the client are not mutually + * exclusive. In all existing cases, the endpoint override is passed as a parameter to the provider and the provider *may* + * modify it. For example, the S3 provider may add the bucket name as a prefix to the endpoint override for virtual bucket + * addressing. + * + * @param endpointOverride + */ + public Builder endpointOverride(URI endpointOverride) { + _endpointOverride = endpointOverride; + return this; + } + + /** * Specify overrides to the default SDK async configuration that should be used for clients created by this builder. * @@ -558,7 +582,8 @@ public ClientOverrideConfiguration overrideConfiguration() { */ @Override public Builder asyncConfiguration(ClientAsyncConfiguration clientAsyncConfiguration) { - return null; + _clientAsyncConfiguration = clientAsyncConfiguration; + return this; } /** @@ -578,7 +603,8 @@ public Builder asyncConfiguration(ClientAsyncConfiguration clientAsyncConfigurat */ @Override public Builder httpClient(SdkAsyncHttpClient httpClient) { - return null; + _sdkAsyncHttpClient = httpClient; + return this; } /** @@ -596,7 +622,8 @@ public Builder httpClient(SdkAsyncHttpClient httpClient) { */ @Override public Builder httpClientBuilder(SdkAsyncHttpClient.Builder httpClientBuilder) { - return null; + _sdkAsyncHttpClientBuilder = httpClientBuilder; + return this; } /** @@ -625,6 +652,9 @@ public S3AsyncEncryptionClient build() { .fipsEnabled(_fipsEnabled) .overrideConfiguration(_overrideConfiguration) .endpointOverride(_endpointOverride) + .asyncConfiguration(_clientAsyncConfiguration) + .httpClient(_sdkAsyncHttpClient) + .httpClientBuilder(_sdkAsyncHttpClientBuilder) .build(); } From 7e4a80fcf0839c525cdfe5e522dbdf1b1a4335e1 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Tue, 30 Jul 2024 15:27:10 -0700 Subject: [PATCH 14/17] readme updates --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index c52ba3fd4..4833395b4 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ The other values are added as variables (by clicking the "New repository variabl * `CI_S3_BUCKET` - the S3 bucket to use, e.g. s3ec-github-test-bucket. * `CI_KMS_KEY_ID` - the short KMS key ID to use, e.g. c3eafb5f-e87d-4584-9400-cf419ce5d782. * `CI_KMS_KEY_ALIAS` - the KMS key alias to use, e.g. S3EC-Github-KMS-Key. Note that the alias must reference the key ID above. +* `CI_ALT_ROLE` - an alternate role to use that is different from the role defined above. It must have permission to use the KMS key below and the S3 bucket above. +* `CI_ALT_KMS_KEY_ID`- the KMS key of an alternate KMS key to use. The alternate role must have access to use the key and the role for `CI_AWS_ROLE` must not have access to the key. ## Migration @@ -44,6 +46,12 @@ However, this version does not support V2's Unencrypted Object Passthrough. This library can only read encrypted objects from S3, unencrypted objects MUST be read with the base S3 Client. +## Client Configuration + +The S3 Encryption Client uses "wrapped" clients to make its requests to S3 and/or KMS. +You can configure each client independently, or apply a "top-level" configuration which is applied to all wrapped clients. +Refer to the Client Configuration Example in the [Examples directory](https://github.com/aws/amazon-s3-encryption-client-java/tree/main/src/examples/java/software/amazon/encryption/s3/examples) for examples of each configuration method. + ### Examples #### V2 KMS Materials Provider to V3 ```java From 3922935e3f9cf94ad86bcab24201e561c91b434f Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Tue, 30 Jul 2024 15:34:04 -0700 Subject: [PATCH 15/17] java formatting --- .../examples/ClientConfigurationExample.java | 68 +++---- .../s3/S3AsyncEncryptionClient.java | 44 ++--- .../encryption/s3/S3EncryptionClient.java | 50 +++--- .../s3/S3AsyncEncryptionClientTest.java | 170 +++++++++--------- .../encryption/s3/S3EncryptionClientTest.java | 136 +++++++------- .../ClientConfigurationExampleTest.java | 2 +- .../S3EncryptionClientTestResources.java | 8 +- 7 files changed, 239 insertions(+), 239 deletions(-) diff --git a/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java b/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java index 879bc4561..57a302c1f 100644 --- a/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java +++ b/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java @@ -43,49 +43,49 @@ public static void CustomClientConfiguration() { // Instantiate the wrapped S3 client with the default credentials // and the region to use with S3. final S3Client wrappedClient = S3Client.builder() - .credentialsProvider(defaultCreds) - .region(Region.of(S3_REGION.toString())) - .build(); + .credentialsProvider(defaultCreds) + .region(Region.of(S3_REGION.toString())) + .build(); // Instantiate the wrapped Async S3 client with the default credentials // and the region to use with S3. final S3AsyncClient wrappedAsyncClient = S3AsyncClient.builder() - .credentialsProvider(defaultCreds) - .region(Region.of(S3_REGION.toString())) - .build(); + .credentialsProvider(defaultCreds) + .region(Region.of(S3_REGION.toString())) + .build(); // Instantiate the KMS client with alternate credentials. // This client will be used for all KMS requests. final KmsClient kmsClient = KmsClient.builder() - .credentialsProvider(altCreds) - .region(Region.of(KMS_REGION.toString())) - .build(); + .credentialsProvider(altCreds) + .region(Region.of(KMS_REGION.toString())) + .build(); // Instantiate a KMS Keyring to use with the S3 Encryption Client. final KmsKeyring kmsKeyring = KmsKeyring.builder() - .wrappingKeyId(ALTERNATE_KMS_KEY) - .kmsClient(kmsClient) - .build(); + .wrappingKeyId(ALTERNATE_KMS_KEY) + .kmsClient(kmsClient) + .build(); // Instantiate the S3 Encryption Client using the configured clients and keyring. final S3Client s3Client = S3EncryptionClient.builder() - .wrappedClient(wrappedClient) - .wrappedAsyncClient(wrappedAsyncClient) - .keyring(kmsKeyring) - .build(); + .wrappedClient(wrappedClient) + .wrappedAsyncClient(wrappedAsyncClient) + .keyring(kmsKeyring) + .build(); // Use the client to call putObject. s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - RequestBody.fromString(input)); + .bucket(BUCKET) + .key(objectKey) + .build(), + RequestBody.fromString(input)); // Use the client to call getObject. ResponseBytes objectResponse = s3Client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + .bucket(BUCKET) + .key(objectKey) + .build()); String output = objectResponse.asUtf8String(); // Check that the output matches the input. assertEquals(input, output); @@ -110,23 +110,23 @@ public static void TopLevelClientConfiguration() { // By passing the creds into the credentialsProvider parameter, // the S3EC will use these creds for both S3 and KMS requests. final S3Client s3Client = S3EncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(ALTERNATE_KMS_KEY) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); // Use the client to call putObject. s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - RequestBody.fromString(input)); + .bucket(BUCKET) + .key(objectKey) + .build(), + RequestBody.fromString(input)); // Use the client to call getObject. ResponseBytes objectResponse = s3Client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + .bucket(BUCKET) + .key(objectKey) + .build()); String output = objectResponse.asUtf8String(); // Check that the output matches the input. assertEquals(input, output); diff --git a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java index eff21eaf9..9332a6b1e 100644 --- a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java @@ -189,7 +189,7 @@ private CompletableFuture multipartPutObject(PutObjectRequest */ @Override public CompletableFuture getObject(GetObjectRequest getObjectRequest, - AsyncResponseTransformer asyncResponseTransformer) { + AsyncResponseTransformer asyncResponseTransformer) { GetEncryptedObjectPipeline pipeline = GetEncryptedObjectPipeline.builder() .s3AsyncClient(_wrappedClient) .cryptoMaterialsManager(_cryptoMaterialsManager) @@ -646,16 +646,16 @@ public S3AsyncEncryptionClient build() { if (_wrappedClient == null) { _wrappedClient = S3AsyncClient.builder() - .credentialsProvider(_awsCredentialsProvider) - .region(_region) - .dualstackEnabled(_dualStackEnabled) - .fipsEnabled(_fipsEnabled) - .overrideConfiguration(_overrideConfiguration) - .endpointOverride(_endpointOverride) - .asyncConfiguration(_clientAsyncConfiguration) - .httpClient(_sdkAsyncHttpClient) - .httpClientBuilder(_sdkAsyncHttpClientBuilder) - .build(); + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .endpointOverride(_endpointOverride) + .asyncConfiguration(_clientAsyncConfiguration) + .httpClient(_sdkAsyncHttpClient) + .httpClientBuilder(_sdkAsyncHttpClientBuilder) + .build(); } if (_keyring == null) { @@ -673,19 +673,19 @@ public S3AsyncEncryptionClient build() { .build(); } else if (_kmsKeyId != null) { KmsClient kmsClient = KmsClient.builder() - .credentialsProvider(_awsCredentialsProvider) - .region(_region) - .dualstackEnabled(_dualStackEnabled) - .fipsEnabled(_fipsEnabled) - .overrideConfiguration(_overrideConfiguration) - .build(); + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .build(); _keyring = KmsKeyring.builder() - .kmsClient(kmsClient) - .wrappingKeyId(_kmsKeyId) - .enableLegacyWrappingAlgorithms(_enableLegacyWrappingAlgorithms) - .secureRandom(_secureRandom) - .build(); + .kmsClient(kmsClient) + .wrappingKeyId(_kmsKeyId) + .enableLegacyWrappingAlgorithms(_enableLegacyWrappingAlgorithms) + .secureRandom(_secureRandom) + .build(); } } diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index abb905c98..ff9e29810 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -203,11 +203,11 @@ public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBod try { CompletableFuture futurePut = pipeline.putObject(putObjectRequest, - AsyncRequestBody.fromInputStream( - requestBody.contentStreamProvider().newStream(), - requestBody.optionalContentLength().orElse(-1L), - singleThreadExecutor - ) + AsyncRequestBody.fromInputStream( + requestBody.contentStreamProvider().newStream(), + requestBody.optionalContentLength().orElse(-1L), + singleThreadExecutor + ) ); PutObjectResponse response = futurePut.join(); @@ -862,24 +862,24 @@ public S3EncryptionClient build() { if (_wrappedClient == null) { _wrappedClient = S3Client.builder() - .credentialsProvider(_awsCredentialsProvider) - .region(_region) - .dualstackEnabled(_dualStackEnabled) - .fipsEnabled(_fipsEnabled) - .overrideConfiguration(_overrideConfiguration) - .endpointOverride(_endpointOverride) - .build(); + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .endpointOverride(_endpointOverride) + .build(); } if (_wrappedAsyncClient == null) { _wrappedAsyncClient = S3AsyncClient.builder() - .credentialsProvider(_awsCredentialsProvider) - .region(_region) - .dualstackEnabled(_dualStackEnabled) - .fipsEnabled(_fipsEnabled) - .overrideConfiguration(_overrideConfiguration) - .endpointOverride(_endpointOverride) - .build(); + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .endpointOverride(_endpointOverride) + .build(); } if (_keyring == null) { @@ -897,12 +897,12 @@ public S3EncryptionClient build() { .build(); } else if (_kmsKeyId != null) { KmsClient kmsClient = KmsClient.builder() - .credentialsProvider(_awsCredentialsProvider) - .region(_region) - .dualstackEnabled(_dualStackEnabled) - .fipsEnabled(_fipsEnabled) - .overrideConfiguration(_overrideConfiguration) - .build(); + .credentialsProvider(_awsCredentialsProvider) + .region(_region) + .dualstackEnabled(_dualStackEnabled) + .fipsEnabled(_fipsEnabled) + .overrideConfiguration(_overrideConfiguration) + .build(); _keyring = KmsKeyring.builder() .kmsClient(kmsClient) diff --git a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java index d97f3b032..7d7542612 100644 --- a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java @@ -87,38 +87,38 @@ public void asyncCustomConfiguration() { AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); S3AsyncClient wrappedAsyncClient = S3AsyncClient - .builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .build(); + .builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .build(); KmsClient kmsClient = KmsClient - .builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .build(); + .builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .build(); KmsKeyring keyring = KmsKeyring - .builder() - .kmsClient(kmsClient) - .wrappingKeyId(KMS_KEY_ID) - .build(); + .builder() + .kmsClient(kmsClient) + .wrappingKeyId(KMS_KEY_ID) + .build(); S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() - .wrappedClient(wrappedAsyncClient) - .keyring(keyring) - .build(); + .wrappedClient(wrappedAsyncClient) + .keyring(keyring) + .build(); final String input = "SimpleTestOfV3EncryptionClientAsync"; s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - AsyncRequestBody.fromString(input)).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3Client.getObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), AsyncResponseTransformer.toBytes()).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); String output = objectResponse.asUtf8String(); assertEquals(input, output); @@ -136,23 +136,23 @@ public void asyncTopLevelConfiguration() { AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); final String input = "SimpleTestOfV3EncryptionClientAsync"; s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - AsyncRequestBody.fromString(input)).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3Client.getObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), AsyncResponseTransformer.toBytes()).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); String output = objectResponse.asUtf8String(); assertEquals(input, output); @@ -170,18 +170,18 @@ public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); // using the original key fails try { s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - AsyncRequestBody.fromString(input)).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); fail("expected exception"); } catch (KmsException exception) { // expected @@ -192,21 +192,21 @@ public void s3AsyncEncryptionClientTopLevelAlternateCredentials() { // using the alternate key succeeds S3AsyncClient s3ClientAltCreds = S3AsyncEncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(ALTERNATE_KMS_KEY) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); s3ClientAltCreds.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - AsyncRequestBody.fromString(input)).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3ClientAltCreds.getObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), AsyncResponseTransformer.toBytes()).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); String output = objectResponse.asUtf8String(); assertEquals(input, output); @@ -224,30 +224,30 @@ public void s3AsyncEncryptionClientMixedCredentials() { // default for S3 AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); KmsClient kmsClient = KmsClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .build(); KmsKeyring kmsKeyring = KmsKeyring.builder() - .kmsClient(kmsClient) - .wrappingKeyId(ALTERNATE_KMS_KEY) - .build(); + .kmsClient(kmsClient) + .wrappingKeyId(ALTERNATE_KMS_KEY) + .build(); S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .keyring(kmsKeyring) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .keyring(kmsKeyring) + .build(); s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - AsyncRequestBody.fromString(input)).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); ResponseBytes objectResponse = s3Client.getObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), AsyncResponseTransformer.toBytes()).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); String output = objectResponse.asUtf8String(); assertEquals(input, output); @@ -264,19 +264,19 @@ public void asyncTopLevelConfigurationWrongRegion() { AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of("eu-west-1")) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of("eu-west-1")) + .kmsKeyId(KMS_KEY_ID) + .build(); final String input = "SimpleTestOfV3EncryptionClientAsync"; try { s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - AsyncRequestBody.fromString(input)).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); fail("expected exception"); } catch (NotFoundException e) { assertTrue(e.getMessage().contains("Invalid arn")); @@ -292,19 +292,19 @@ public void asyncTopLevelConfigurationNullCreds() { AwsCredentialsProvider creds = new S3EncryptionClientTestResources.NullCredentialsProvider(); S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); final String input = "SimpleTestOfV3EncryptionClientAsync"; try { s3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - AsyncRequestBody.fromString(input)).join(); + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); fail("expected exception"); } catch (NullPointerException npe) { assertTrue(npe.getMessage().contains("Access key ID cannot be blank")); diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java index 38f18e1a8..976c002fa 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java @@ -399,8 +399,8 @@ public void s3EncryptionClientWithKeyringFromKmsKeyIdSucceeds() { KmsKeyring keyring = KmsKeyring.builder().wrappingKeyId(KMS_KEY_ID).build(); S3Client v3Client = S3EncryptionClient.builder() - .keyring(keyring) - .build(); + .keyring(keyring) + .build(); simpleV3RoundTrip(v3Client, objectKey); @@ -416,12 +416,12 @@ public void s3EncryptionClientWithCmmFromKmsKeyIdSucceeds() { KmsKeyring keyring = KmsKeyring.builder().wrappingKeyId(KMS_KEY_ID).build(); CryptographicMaterialsManager cmm = DefaultCryptoMaterialsManager.builder() - .keyring(keyring) - .build(); + .keyring(keyring) + .build(); S3Client v3Client = S3EncryptionClient.builder() - .cryptoMaterialsManager(cmm) - .build(); + .cryptoMaterialsManager(cmm) + .build(); simpleV3RoundTrip(v3Client, objectKey); @@ -459,21 +459,21 @@ public void s3EncryptionClientWithWrappedS3ClientSucceeds() { @Test public void s3EncryptionClientWithWrappedS3EncryptionClientFails() { S3AsyncClient wrappedAsyncClient = S3AsyncEncryptionClient.builder() - .kmsKeyId(KMS_KEY_ID) - .build(); + .kmsKeyId(KMS_KEY_ID) + .build(); assertThrows(S3EncryptionClientException.class, () -> S3EncryptionClient.builder() - .wrappedAsyncClient(wrappedAsyncClient) - .kmsKeyId(KMS_KEY_ID) - .build()); + .wrappedAsyncClient(wrappedAsyncClient) + .kmsKeyId(KMS_KEY_ID) + .build()); } @Test public void s3EncryptionClientWithNullSecureRandomFails() { assertThrows(S3EncryptionClientException.class, () -> S3EncryptionClient.builder() - .aesKey(AES_KEY) - .secureRandom(null) - .build()); + .aesKey(AES_KEY) + .secureRandom(null) + .build()); } @Test @@ -483,8 +483,8 @@ public void s3EncryptionClientFromKMSKeyDoesNotUseUnprovidedSecureRandom() { final String objectKey = appendTestSuffix("no-secure-random-object-kms"); S3Client v3Client = S3EncryptionClient.builder() - .kmsKeyId(KMS_KEY_ID) - .build(); + .kmsKeyId(KMS_KEY_ID) + .build(); simpleV3RoundTrip(v3Client, objectKey); @@ -704,28 +704,28 @@ public void s3EncryptionClientWithCustomCredentials() { AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); S3Client wrappedClient = S3Client - .builder() - .credentialsProvider(creds) - .build(); + .builder() + .credentialsProvider(creds) + .build(); S3AsyncClient wrappedAsyncClient = S3AsyncClient - .builder() - .credentialsProvider(creds) - .build(); + .builder() + .credentialsProvider(creds) + .build(); KmsClient kmsClient = KmsClient - .builder() - .credentialsProvider(creds) - .build(); + .builder() + .credentialsProvider(creds) + .build(); KmsKeyring keyring = KmsKeyring - .builder() - .kmsClient(kmsClient) - .wrappingKeyId(KMS_KEY_ID) - .build(); + .builder() + .kmsClient(kmsClient) + .wrappingKeyId(KMS_KEY_ID) + .build(); S3Client s3Client = S3EncryptionClient.builder() - .wrappedClient(wrappedClient) - .wrappedAsyncClient(wrappedAsyncClient) - .keyring(keyring) - .build(); + .wrappedClient(wrappedClient) + .wrappedAsyncClient(wrappedAsyncClient) + .keyring(keyring) + .build(); simpleV3RoundTrip(s3Client, objectKey); @@ -744,10 +744,10 @@ public void s3EncryptionClientTopLevelCredentials() { AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); S3Client s3Client = S3EncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); simpleV3RoundTrip(s3Client, objectKey); @@ -764,10 +764,10 @@ public void s3EncryptionClientTopLevelCredentialsWrongRegion() { AwsCredentialsProvider creds = DefaultCredentialsProvider.create(); S3Client s3Client = S3EncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of("eu-west-1")) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of("eu-west-1")) + .kmsKeyId(KMS_KEY_ID) + .build(); try { simpleV3RoundTrip(s3Client, objectKey); @@ -788,10 +788,10 @@ public void s3EncryptionClientTopLevelCredentialsNullCreds() { AwsCredentialsProvider creds = new S3EncryptionClientTestResources.NullCredentialsProvider(); S3Client s3Client = S3EncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); try { simpleV3RoundTrip(s3Client, objectKey); @@ -813,10 +813,10 @@ public void s3EncryptionClientTopLevelAlternateCredentials() { AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); S3Client s3Client = S3EncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(KMS_KEY_ID) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(KMS_KEY_ID) + .build(); // using the original key fails try { @@ -832,10 +832,10 @@ public void s3EncryptionClientTopLevelAlternateCredentials() { // using the alternate key succeeds S3Client s3ClientAltCreds = S3EncryptionClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .kmsKeyId(ALTERNATE_KMS_KEY) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); simpleV3RoundTrip(s3ClientAltCreds, objectKey); @@ -852,17 +852,17 @@ public void s3EncryptionClientMixedCredentials() { // default for S3 AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); KmsClient kmsClient = KmsClient.builder() - .credentialsProvider(creds) - .region(Region.of(KMS_REGION.toString())) - .build(); + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .build(); KmsKeyring kmsKeyring = KmsKeyring.builder() - .kmsClient(kmsClient) - .wrappingKeyId(ALTERNATE_KMS_KEY) - .build(); + .kmsClient(kmsClient) + .wrappingKeyId(ALTERNATE_KMS_KEY) + .build(); S3Client s3Client = S3EncryptionClient.builder() - .keyring(kmsKeyring) - .build(); + .keyring(kmsKeyring) + .build(); simpleV3RoundTrip(s3Client, objectKey); @@ -882,15 +882,15 @@ private void simpleV3RoundTrip(final S3Client v3Client, final String objectKey) final String input = "SimpleTestOfV3EncryptionClient"; v3Client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), - RequestBody.fromString(input)); + .bucket(BUCKET) + .key(objectKey) + .build(), + RequestBody.fromString(input)); ResponseBytes objectResponse = v3Client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + .bucket(BUCKET) + .key(objectKey) + .build()); String output = objectResponse.asUtf8String(); assertEquals(input, output); } diff --git a/src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java b/src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java index 80cce790e..91077c643 100644 --- a/src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java +++ b/src/test/java/software/amazon/encryption/s3/examples/ClientConfigurationExampleTest.java @@ -5,6 +5,6 @@ public class ClientConfigurationExampleTest { @Test public void testClientConfigurationExamples() { - ClientConfigurationExample.main(new String[0]); + ClientConfigurationExample.main(new String[0]); } } diff --git a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java index 1f7ba15d4..96099b4a9 100644 --- a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java +++ b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java @@ -53,10 +53,10 @@ public AlternateRoleCredentialsProvider() { public AwsCredentials resolveCredentials() { String sessionName = "s3ec-test" + DateTimeFormat.forPattern("-yyMMdd-hhmmss").print(new DateTime()); Credentials assumeRoleCreds = stsClient_.assumeRole(builder -> builder - .roleArn(ALTERNATE_ROLE_ARN).roleSessionName(sessionName).build()).credentials(); + .roleArn(ALTERNATE_ROLE_ARN).roleSessionName(sessionName).build()).credentials(); return AwsSessionCredentials.create(assumeRoleCreds.accessKeyId(), - assumeRoleCreds.secretAccessKey(), - assumeRoleCreds.sessionToken()); + assumeRoleCreds.secretAccessKey(), + assumeRoleCreds.sessionToken()); } } @@ -69,7 +69,7 @@ public NullCredentialsProvider() { @Override public AwsCredentials resolveCredentials() { return AwsBasicCredentials - .create(null, null); + .create(null, null); } } From 5cc8845b144e0a92df2a2c63ba5d1c406e217592 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Tue, 30 Jul 2024 15:36:52 -0700 Subject: [PATCH 16/17] fix async npe --- .../software/amazon/encryption/s3/S3AsyncEncryptionClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java index 9332a6b1e..016810879 100644 --- a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java @@ -652,7 +652,7 @@ public S3AsyncEncryptionClient build() { .fipsEnabled(_fipsEnabled) .overrideConfiguration(_overrideConfiguration) .endpointOverride(_endpointOverride) - .asyncConfiguration(_clientAsyncConfiguration) + .asyncConfiguration(_clientAsyncConfiguration != null ? _clientAsyncConfiguration : ClientAsyncConfiguration.builder().build()) .httpClient(_sdkAsyncHttpClient) .httpClientBuilder(_sdkAsyncHttpClientBuilder) .build(); From 3ca3bf3e536862b48bd6436b100921db1ffc7ca0 Mon Sep 17 00:00:00 2001 From: J Plasmeier Date: Wed, 31 Jul 2024 13:53:34 -0700 Subject: [PATCH 17/17] address PR feedback --- .../examples/ClientConfigurationExample.java | 129 ++++++++++++++++-- .../s3/S3AsyncEncryptionClient.java | 35 +++++ .../encryption/s3/S3EncryptionClient.java | 34 +++++ 3 files changed, 190 insertions(+), 8 deletions(-) diff --git a/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java b/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java index 57a302c1f..225eab078 100644 --- a/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java +++ b/src/examples/java/software/amazon/encryption/s3/examples/ClientConfigurationExample.java @@ -3,29 +3,29 @@ import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.kms.KmsClient; import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.encryption.s3.S3AsyncEncryptionClient; import software.amazon.encryption.s3.S3EncryptionClient; import software.amazon.encryption.s3.materials.KmsKeyring; import software.amazon.encryption.s3.utils.S3EncryptionClientTestResources; import static org.junit.jupiter.api.Assertions.assertEquals; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.ALTERNATE_KMS_KEY; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_REGION; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.S3_REGION; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.*; public class ClientConfigurationExample { public static void main(String[] args) { CustomClientConfiguration(); TopLevelClientConfiguration(); + CustomClientConfigurationAsync(); + TopLevelClientConfigurationAsync(); } /** @@ -47,8 +47,14 @@ public static void CustomClientConfiguration() { .region(Region.of(S3_REGION.toString())) .build(); - // Instantiate the wrapped Async S3 client with the default credentials - // and the region to use with S3. + /* + * Instantiate the wrapped Async S3 client with the default credentials + * and the region to use with S3. + * The default S3 Encryption Client uses the async client for + * operations requiring encryption or decryption. + * All other operations such as bucket-related operations use the default client, + * which is configured below. + */ final S3AsyncClient wrappedAsyncClient = S3AsyncClient.builder() .credentialsProvider(defaultCreds) .region(Region.of(S3_REGION.toString())) @@ -109,6 +115,9 @@ public static void TopLevelClientConfiguration() { // Instantiate the S3 Encryption Client via its builder. // By passing the creds into the credentialsProvider parameter, // the S3EC will use these creds for both S3 and KMS requests. + // NOTE: If you use both the "top-level" configuration AND + // custom configuration such as the above example, the custom client + // configuration will take precedence. final S3Client s3Client = S3EncryptionClient.builder() .credentialsProvider(creds) .region(Region.of(KMS_REGION.toString())) @@ -136,4 +145,108 @@ public static void TopLevelClientConfiguration() { // Close the S3 Client. s3Client.close(); } + + /** + * This example demonstrates how to use specific client configuration for + * the S3 and KMS clients used within the S3 Async Encryption Client. + */ + public static void CustomClientConfigurationAsync() { + final String objectKey = appendTestSuffix("custom-client-configuration-example-async"); + final String input = "CustomClientConfigurationExample"; + // Load your AWS credentials from an external source. + final AwsCredentialsProvider defaultCreds = DefaultCredentialsProvider.create(); + // This example uses two different sets of credentials. + final AwsCredentialsProvider altCreds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + + // Instantiate the wrapped (async) S3 client with the default credentials + // and the region to use with S3. + final S3AsyncClient wrappedAsyncClient = S3AsyncClient.builder() + .credentialsProvider(defaultCreds) + .region(Region.of(S3_REGION.toString())) + .build(); + + // Instantiate the KMS client with alternate credentials. + // This client will be used for all KMS requests. + final KmsClient kmsClient = KmsClient.builder() + .credentialsProvider(altCreds) + .region(Region.of(KMS_REGION.toString())) + .build(); + + // Instantiate a KMS Keyring to use with the S3 Encryption Client. + final KmsKeyring kmsKeyring = KmsKeyring.builder() + .wrappingKeyId(ALTERNATE_KMS_KEY) + .kmsClient(kmsClient) + .build(); + + // Instantiate the S3 Async Encryption Client using the configured clients and keyring. + final S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .wrappedClient(wrappedAsyncClient) + .keyring(kmsKeyring) + .build(); + + // Use the async client to call putObject and block on its response + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); + + // Use the async client to call getObject and block on its response + ResponseBytes objectResponse = s3Client.getObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); + String output = objectResponse.asUtf8String(); + // Check that the output matches the input. + assertEquals(input, output); + + // Delete the object. + deleteObject(BUCKET, objectKey, s3Client); + // Close the S3 Client. + s3Client.close(); + } + + /** + * This example demonstrates how to use a single client configuration for + * the S3 and KMS clients used within the S3 Encryption Client. + */ + public static void TopLevelClientConfigurationAsync() { + final String objectKey = appendTestSuffix("top-level-client-configuration-async-example"); + final String input = "TopLevelClientConfigurationExample"; + // Load your AWS credentials from an external source. + final AwsCredentialsProvider creds = new S3EncryptionClientTestResources.AlternateRoleCredentialsProvider(); + + // Instantiate the S3 Async Encryption Client via its builder. + // By passing the creds into the credentialsProvider parameter, + // the S3EC will use these creds for both S3 and KMS requests. + // NOTE: If you use both the "top-level" configuration AND + // custom configuration such as the above example, the custom client + // configuration will take precedence. + final S3AsyncClient s3Client = S3AsyncEncryptionClient.builder() + .credentialsProvider(creds) + .region(Region.of(KMS_REGION.toString())) + .kmsKeyId(ALTERNATE_KMS_KEY) + .build(); + + // Use the async client to call putObject and block on its response. + s3Client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), + AsyncRequestBody.fromString(input)).join(); + + // Use the async client to call getObject and block on its response. + ResponseBytes objectResponse = s3Client.getObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), AsyncResponseTransformer.toBytes()).join(); + String output = objectResponse.asUtf8String(); + // Check that the output matches the input. + assertEquals(input, output); + + // Delete the object. + deleteObject(BUCKET, objectKey, s3Client); + // Close the S3 Client. + s3Client.close(); + } } diff --git a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java index 016810879..a06185239 100644 --- a/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3AsyncEncryptionClient.java @@ -513,6 +513,7 @@ public Builder secureRandom(SecureRandom secureRandom) { /** * The credentials provider to use for all inner clients, including KMS, if a KMS key ID is provided. + * Note that if a wrapped client is configured, the wrapped client will take precedence over this option. * @param awsCredentialsProvider * @return */ @@ -523,6 +524,7 @@ public Builder credentialsProvider(AwsCredentialsProvider awsCredentialsProvider /** * The AWS region to use for all inner clients, including KMS, if a KMS key ID is provided. + * Note that if a wrapped client is configured, the wrapped client will take precedence over this option. * @param region * @return */ @@ -531,16 +533,49 @@ public Builder region(Region region) { return this; } + /** + * Configure whether the SDK should use the AWS dualstack endpoint. + * + *

If this is not specified, the SDK will attempt to determine whether the dualstack endpoint should be used + * automatically using the following logic: + *

    + *
  1. Check the 'aws.useDualstackEndpoint' system property for 'true' or 'false'.
  2. + *
  3. Check the 'AWS_USE_DUALSTACK_ENDPOINT' environment variable for 'true' or 'false'.
  4. + *
  5. Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_dualstack_endpoint' + * property set to 'true' or 'false'.
  6. + *
+ * + *

If the setting is not found in any of the locations above, 'false' will be used. + */ public Builder dualstackEnabled(Boolean isDualStackEnabled) { _dualStackEnabled = isDualStackEnabled; return this; } + /** + * Configure whether the wrapped SDK clients should use the AWS FIPS endpoints. + * Note that this option only enables FIPS for the service endpoints which the SDK clients use, + * it does not enable FIPS for the S3EC itself. Use a FIPS-enabled CryptoProvider for full FIPS support. + * + *

If this is not specified, the SDK will attempt to determine whether the FIPS endpoint should be used + * automatically using the following logic: + *

    + *
  1. Check the 'aws.useFipsEndpoint' system property for 'true' or 'false'.
  2. + *
  3. Check the 'AWS_USE_FIPS_ENDPOINT' environment variable for 'true' or 'false'.
  4. + *
  5. Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_fips_endpoint' + * property set to 'true' or 'false'.
  6. + *
+ * + *

If the setting is not found in any of the locations above, 'false' will be used. + */ public Builder fipsEnabled(Boolean isFipsEnabled) { _fipsEnabled = isFipsEnabled; return this; } + /** + * Specify overrides to the default SDK configuration that should be used for wrapped clients. + */ public Builder overrideConfiguration(ClientOverrideConfiguration overrideConfiguration) { _overrideConfiguration = overrideConfiguration; return this; diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index ff9e29810..cc7c97871 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -774,6 +774,7 @@ public Builder secureRandom(SecureRandom secureRandom) { /** * The credentials provider to use for all inner clients, including KMS, if a KMS key ID is provided. + * Note that if a wrapped client is configured, the wrapped client will take precedence over this option. * @param awsCredentialsProvider * @return */ @@ -794,18 +795,51 @@ public Builder region(Region region) { return this; } + /** + * Configure whether the SDK should use the AWS dualstack endpoint. + * + *

If this is not specified, the SDK will attempt to determine whether the dualstack endpoint should be used + * automatically using the following logic: + *

    + *
  1. Check the 'aws.useDualstackEndpoint' system property for 'true' or 'false'.
  2. + *
  3. Check the 'AWS_USE_DUALSTACK_ENDPOINT' environment variable for 'true' or 'false'.
  4. + *
  5. Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_dualstack_endpoint' + * property set to 'true' or 'false'.
  6. + *
+ * + *

If the setting is not found in any of the locations above, 'false' will be used. + */ @Override public Builder dualstackEnabled(Boolean isDualStackEnabled) { _dualStackEnabled = isDualStackEnabled; return this; } + /** + * Configure whether the wrapped SDK clients should use the AWS FIPS endpoints. + * Note that this option only enables FIPS for the service endpoints which the SDK clients use, + * it does not enable FIPS for the S3EC itself. Use a FIPS-enabled CryptoProvider for full FIPS support. + * + *

If this is not specified, the SDK will attempt to determine whether the FIPS endpoint should be used + * automatically using the following logic: + *

    + *
  1. Check the 'aws.useFipsEndpoint' system property for 'true' or 'false'.
  2. + *
  3. Check the 'AWS_USE_FIPS_ENDPOINT' environment variable for 'true' or 'false'.
  4. + *
  5. Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_fips_endpoint' + * property set to 'true' or 'false'.
  6. + *
+ * + *

If the setting is not found in any of the locations above, 'false' will be used. + */ @Override public Builder fipsEnabled(Boolean isFipsEnabled) { _fipsEnabled = isFipsEnabled; return this; } + /** + * Specify overrides to the default SDK configuration that should be used for clients created by this builder. + */ @Override public Builder overrideConfiguration(ClientOverrideConfiguration overrideConfiguration) { _overrideConfiguration = overrideConfiguration;