If this is not specified, the SDK will attempt to determine whether the dualstack endpoint should be used
+ * automatically using the following logic:
+ *
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:
+ *
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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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
+ */
+ 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.
+ *
+ * @param clientAsyncConfiguration
+ */
+ @Override
+ public Builder asyncConfiguration(ClientAsyncConfiguration clientAsyncConfiguration) {
+ _clientAsyncConfiguration = clientAsyncConfiguration;
+ return this;
+ }
+
+ /**
+ * 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.
+ *
+ *
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();
@@ -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,110 @@ 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.
+ * Note that if a wrapped client is configured, the wrapped client will take precedence over this option.
+ * @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;
+ }
+
+ /**
+ * 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:
+ *
+ * - Check the 'aws.useDualstackEndpoint' system property for 'true' or 'false'.
+ * - Check the 'AWS_USE_DUALSTACK_ENDPOINT' environment variable for 'true' or 'false'.
+ * - Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_dualstack_endpoint'
+ * property set to 'true' or 'false'.
+ *
+ *
+ * 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:
+ *
+ * - Check the 'aws.useFipsEndpoint' system property for 'true' or 'false'.
+ * - Check the 'AWS_USE_FIPS_ENDPOINT' environment variable for 'true' or 'false'.
+ * - Check the {user.home}/.aws/credentials and {user.home}/.aws/config files for the 'use_fips_endpoint'
+ * property set to 'true' or 'false'.
+ *
+ *
+ * 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;
+ 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 +895,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 +930,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 +962,6 @@ public S3EncryptionClient build() {
return new S3EncryptionClient(this);
}
+
}
}
diff --git a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java
index 5c6a487f4..7d7542612 100644
--- a/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java
+++ b/src/test/java/software/amazon/encryption/s3/S3AsyncEncryptionClientTest.java
@@ -17,11 +17,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.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.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;
@@ -31,7 +37,9 @@
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.S3EncryptionClientTestResources;
import software.amazon.encryption.s3.utils.TinyBufferAsyncRequestBody;
import javax.crypto.KeyGenerator;
@@ -52,7 +60,11 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static 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;
@@ -67,6 +79,240 @@ 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)
+ .region(Region.of(KMS_REGION.toString()))
+ .build();
+ KmsClient kmsClient = KmsClient
+ .builder()
+ .credentialsProvider(creds)
+ .region(Region.of(KMS_REGION.toString()))
+ .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)).join();
+
+ 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)
+ .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();
+
+ 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 s3AsyncEncryptionClientTopLevelAlternateCredentials() {
+ final String objectKey = appendTestSuffix("wrapped-s3-async-client-with-top-level-alternate-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)).join();
+ fail("expected exception");
+ } catch (KmsException exception) {
+ // expected
+ assertTrue(exception.getMessage().contains("is not authorized to perform"));
+ } finally {
+ s3Client.close();
+ }
+
+ // using the alternate key succeeds
+ S3AsyncClient s3ClientAltCreds = S3AsyncEncryptionClient.builder()
+ .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();
+
+ ResponseBytes objectResponse = s3ClientAltCreds.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 s3AsyncEncryptionClientMixedCredentials() {
+ 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()))
+ .keyring(kmsKeyring)
+ .build();
+
+ s3Client.putObject(builder -> builder
+ .bucket(BUCKET)
+ .key(objectKey)
+ .build(),
+ AsyncRequestBody.fromString(input)).join();
+
+ 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 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/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..976c002fa 100644
--- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java
+++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientTest.java
@@ -13,14 +13,18 @@
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.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;
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 +33,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,15 +53,18 @@
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;
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;
+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;
@@ -391,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);
@@ -408,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);
@@ -451,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
@@ -475,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);
@@ -688,6 +696,182 @@ 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();
+ }
+
+ @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(KMS_REGION.toString()))
+ .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();
+ }
+ }
+
+ @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();
+ }
+
+ @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();
+ }
+
/**
* A simple, reusable round-trip (encryption + decryption) using a given
* S3Client. Useful for testing client configuration.
@@ -710,5 +894,4 @@ private void simpleV3RoundTrip(final S3Client v3Client, final String objectKey)
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..91077c643
--- /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 0aa498380..96099b4a9 100644
--- a/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java
+++ b/src/test/java/software/amazon/encryption/s3/utils/S3EncryptionClientTestResources.java
@@ -2,11 +2,19 @@
// 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.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;
@@ -19,6 +27,51 @@ 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");
+ // 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 {
+
+ public NullCredentialsProvider() {
+ super();
+ }
+
+ @Override
+ public AwsCredentials resolveCredentials() {
+ return AwsBasicCredentials
+ .create(null, null);
+ }
+ }
/**
* For a given string, append a suffix to distinguish it from