diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1ddfb..702dda0cc 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
-
-
-
\ No newline at end of file
+
+
+
+
diff --git a/docs/src/main/asciidoc/s3.adoc b/docs/src/main/asciidoc/s3.adoc
index 055a33c1d..702873605 100644
--- a/docs/src/main/asciidoc/s3.adoc
+++ b/docs/src/main/asciidoc/s3.adoc
@@ -127,6 +127,88 @@ try (OutputStream outputStream = s3Resource.getOutputStream()) {
}
----
+=== S3 Client Side Encryption
+
+AWS offers encryption library which is integrated inside of S3 Client called https://docs.aws.amazon.com/amazon-s3-encryption-client/latest/developerguide/what-is-s3-encryption-client.html [S3EncryptionClient].
+With encryption client you are going to encrypt your files before sending them to S3 bucket.
+
+To autoconfigure Encryption Client simply add the following dependency.
+
+[source,xml]
+----
+
+ software.amazon.encryption.s3
+ amazon-s3-encryption-client-java
+
+----
+
+
+We are supporting 3 types of encryption.
+
+1. To configure encryption via KMS key specify 'spring.cloud.aws.s3.encryption.keyId' with KMS key arn and this key will be used to encrypt your files.
+
+Also, following dependency is required.
+[source,xml]
+----
+
+ software.amazon.awssdk
+ kms
+ true
+
+----
+
+
+2. Asymmetric encryption is possible via RSA to enable it you will have to implement 'io.awspring.cloud.autoconfigure.s3.S3RsaProvider'
+
+!Note you will have to manage storing private and public keys yourself otherwise you won't be able to decrypt the data later.
+Example of simple RSAProvider:
+
+[source,java,indent=0]
+----
+import io.awspring.cloud.autoconfigure.s3.S3RsaProvider;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+
+public class MyRsaProvider implements S3RsaProvider {
+ @Override
+ public KeyPair generateKeyPair() {
+ try {
+ // fetch key pair from secure location such as Secrets Manager
+ // access to KeyPair is required to decrypt objects when fetching, so it is advised to keep them stored securely
+ }
+ catch (Exception e) {
+ return null;
+ }
+ }
+}
+----
+
+3. Last option is if you want to use symmetric algorithm, this is possible via `io.awspring.cloud.autoconfigure.s3.S3AesProvider`
+
+!Note you will have to manage storing storing private key!
+Example of simple AESProvider:
+
+[source,java,indent=0]
+----
+import io.awspring.cloud.autoconfigure.s3.S3AesProvider;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+public class MyAesProvider implements S3AesProvider {
+ @Override
+ public SecretKey generateSecretKey() {
+ try {
+ // fetch secret key from secure location such as Secrets Manager
+ // access to secret key is required to decrypt objects when fetching, so it is advised to keep them stored securely
+ }
+ catch (Exception e) {
+ return null;
+ }
+ }
+}
+----
+
+
==== S3 Output Stream
Under the hood by default `S3Resource` uses a `io.awspring.cloud.s3.InMemoryBufferingS3OutputStream`. When data is written to the resource, is gets sent to S3 using multipart upload.
diff --git a/spring-cloud-aws-autoconfigure/pom.xml b/spring-cloud-aws-autoconfigure/pom.xml
index 7f17e16ee..cd6f485c7 100644
--- a/spring-cloud-aws-autoconfigure/pom.xml
+++ b/spring-cloud-aws-autoconfigure/pom.xml
@@ -166,5 +166,15 @@
sts
true
+
+ software.amazon.awssdk
+ kms
+ true
+
+
+ software.amazon.encryption.s3
+ amazon-s3-encryption-client-java
+ true
+
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AesProvider.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AesProvider.java
new file mode 100644
index 000000000..44d01084e
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AesProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.s3;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Interface for providing {@link SecretKey} when configuring {@link software.amazon.encryption.s3.S3EncryptionClient}.
+ * Required when encrypting files server side with AES. Secret Key should be stored in secure storage, for example AWS
+ * Secrets Manager.
+ * @author Matej Nedic
+ * @since 3.3.0
+ */
+public interface S3AesProvider {
+
+ /**
+ * Provides SecretKey that will be used to configure {@link software.amazon.encryption.s3.S3EncryptionClient}.
+ * Advised to fetch and return SecretKey in this method from Secured Storage.
+ * @return KeyPair that will be used for encryption/decryption.
+ */
+ SecretKey generateSecretKey();
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java
index dee0e3234..e2a91f384 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java
@@ -35,26 +35,28 @@
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
import software.amazon.awssdk.s3accessgrants.plugin.S3AccessGrantsPlugin;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.encryption.s3.S3EncryptionClient;
/**
* {@link EnableAutoConfiguration} for {@link S3Client} and {@link S3ProtocolResolver}.
*
* @author Maciej Walkowiak
+ * @author Matej Nedic
*/
@AutoConfiguration
@ConditionalOnClass({ S3Client.class, S3OutputStreamProvider.class })
@@ -85,6 +87,7 @@ S3ClientBuilder s3ClientBuilder(AwsClientBuilderConfigurer awsClientBuilderConfi
.enableFallback(properties.getPlugin().getEnableFallback()).build();
builder.addPlugin(s3AccessGrantsPlugin);
}
+
Optional.ofNullable(this.properties.getCrossRegionEnabled()).ifPresent(builder::crossRegionAccessEnabled);
builder.serviceConfiguration(this.properties.toS3Configuration());
@@ -119,6 +122,65 @@ else if (awsProperties.getEndpoint() != null) {
return builder.build();
}
+ @Conditional(S3EncryptionConditional.class)
+ @ConditionalOnClass(name = "software.amazon.encryption.s3.S3EncryptionClient")
+ @Configuration
+ public static class S3EncryptionConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ S3Client s3EncryptionClient(S3EncryptionClient.Builder s3EncryptionBuilder, S3ClientBuilder s3ClientBuilder) {
+ s3EncryptionBuilder.wrappedClient(s3ClientBuilder.build());
+ return s3EncryptionBuilder.build();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ S3EncryptionClient.Builder s3EncrpytionClientBuilder(S3Properties properties,
+ AwsClientBuilderConfigurer awsClientBuilderConfigurer,
+ ObjectProvider> configurer,
+ ObjectProvider connectionDetails,
+ ObjectProvider s3ClientCustomizers,
+ ObjectProvider awsSyncClientCustomizers,
+ ObjectProvider rsaProvider, ObjectProvider aesProvider) {
+ S3EncryptionClient.Builder builder = awsClientBuilderConfigurer.configureSyncClient(
+ S3EncryptionClient.builder(), properties, connectionDetails.getIfAvailable(),
+ configurer.getIfAvailable(), s3ClientCustomizers.orderedStream(),
+ awsSyncClientCustomizers.orderedStream());
+
+ Optional.ofNullable(properties.getCrossRegionEnabled()).ifPresent(builder::crossRegionAccessEnabled);
+ builder.serviceConfiguration(properties.toS3Configuration());
+
+ configureEncryptionProperties(properties, rsaProvider, aesProvider, builder);
+ return builder;
+ }
+
+ private static void configureEncryptionProperties(S3Properties properties,
+ ObjectProvider rsaProvider, ObjectProvider aesProvider,
+ S3EncryptionClient.Builder builder) {
+ PropertyMapper propertyMapper = PropertyMapper.get();
+ var encryptionProperties = properties.getEncryption();
+
+ propertyMapper.from(encryptionProperties::isEnableDelayedAuthenticationMode)
+ .to(builder::enableDelayedAuthenticationMode);
+ propertyMapper.from(encryptionProperties::isEnableLegacyUnauthenticatedModes)
+ .to(builder::enableLegacyUnauthenticatedModes);
+ propertyMapper.from(encryptionProperties::isEnableMultipartPutObject).to(builder::enableMultipartPutObject);
+
+ if (!StringUtils.hasText(properties.getEncryption().getKeyId())) {
+ if (aesProvider.getIfAvailable() != null) {
+ builder.aesKey(aesProvider.getObject().generateSecretKey());
+ }
+ else {
+ builder.rsaKeyPair(rsaProvider.getObject().generateKeyPair());
+ }
+ }
+ else {
+ propertyMapper.from(encryptionProperties::getKeyId).to(builder::kmsKeyId);
+ }
+ }
+ }
+
@Bean
@ConditionalOnMissingBean
S3Client s3Client(S3ClientBuilder s3ClientBuilder) {
@@ -143,5 +205,4 @@ S3OutputStreamProvider inMemoryBufferingS3StreamProvider(S3Client s3Client,
return new InMemoryBufferingS3OutputStreamProvider(s3Client,
contentTypeResolver.orElseGet(PropertiesS3ObjectContentTypeResolver::new));
}
-
}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3EncryptionClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3EncryptionClientCustomizer.java
new file mode 100644
index 000000000..0367badd0
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3EncryptionClientCustomizer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.s3;
+
+import io.awspring.cloud.autoconfigure.AwsClientCustomizer;
+import software.amazon.encryption.s3.S3EncryptionClient;
+
+/**
+ * Callback interface that can be used to customize a {@link S3EncryptionClient.Builder}.
+ *
+ * @author Matej Nedic
+ * @since 3.3.0
+ */
+@FunctionalInterface
+public interface S3EncryptionClientCustomizer extends AwsClientCustomizer {
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3EncryptionConditional.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3EncryptionConditional.java
new file mode 100644
index 000000000..f49d0e4ad
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3EncryptionConditional.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.s3;
+
+import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ * Conditional for creating {@link software.amazon.encryption.s3.S3EncryptionClient}. Will only create
+ * S3EncryptionClient if one of following is true.
+ * @author Matej Nedic
+ * @since 3.3.0
+ */
+public class S3EncryptionConditional extends AnyNestedCondition {
+ public S3EncryptionConditional() {
+ super(ConfigurationPhase.REGISTER_BEAN);
+ }
+
+ @ConditionalOnBean(S3RsaProvider.class)
+ static class RSAProviderCondition {
+ }
+
+ @ConditionalOnBean(S3AesProvider.class)
+ static class AESProviderCondition {
+ }
+
+ @ConditionalOnProperty(name = "spring.cloud.aws.s3.encryption.keyId")
+ static class KmsKeyProperty {
+ }
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3RsaProvider.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3RsaProvider.java
new file mode 100644
index 000000000..cc3daa08a
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3RsaProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.s3;
+
+import java.security.KeyPair;
+
+/**
+ * Interface for providing {@link KeyPair} when configuring {@link software.amazon.encryption.s3.S3EncryptionClient}.
+ * Required for encrypting/decrypting files server side with RSA. Key pair should be stored in secure storage, for
+ * example AWS Secrets Manager.
+ * @author Matej Nedic
+ * @since 3.3.0
+ */
+public interface S3RsaProvider {
+
+ /**
+ * Provides KeyPair that will be used to configure {@link software.amazon.encryption.s3.S3EncryptionClient}. Advised
+ * to fetch and return KeyPair in this method from Secured Storage.
+ * @return KeyPair that will be used for encryption/decryption.
+ */
+ KeyPair generateKeyPair();
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3EncryptionProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3EncryptionProperties.java
new file mode 100644
index 000000000..e4b910b79
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3EncryptionProperties.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.s3.properties;
+
+/**
+ * Properties to configure {@link software.amazon.encryption.s3.S3EncryptionClient}
+ * @author Matej Nedic
+ */
+public class S3EncryptionProperties {
+ private boolean enableLegacyUnauthenticatedModes = false;
+ private boolean enableDelayedAuthenticationMode = false;
+ private boolean enableMultipartPutObject = false;
+ private String keyId;
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(String keyId) {
+ this.keyId = keyId;
+ }
+
+ public boolean isEnableLegacyUnauthenticatedModes() {
+ return enableLegacyUnauthenticatedModes;
+ }
+
+ public void setEnableLegacyUnauthenticatedModes(boolean enableLegacyUnauthenticatedModes) {
+ this.enableLegacyUnauthenticatedModes = enableLegacyUnauthenticatedModes;
+ }
+
+ public boolean isEnableDelayedAuthenticationMode() {
+ return enableDelayedAuthenticationMode;
+ }
+
+ public void setEnableDelayedAuthenticationMode(boolean enableDelayedAuthenticationMode) {
+ this.enableDelayedAuthenticationMode = enableDelayedAuthenticationMode;
+ }
+
+ public boolean isEnableMultipartPutObject() {
+ return enableMultipartPutObject;
+ }
+
+ public void setEnableMultipartPutObject(boolean enableMultipartPutObject) {
+ this.enableMultipartPutObject = enableMultipartPutObject;
+ }
+
+}
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3PluginProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3PluginProperties.java
index c636d89db..15c4afdaa 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3PluginProperties.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3PluginProperties.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package io.awspring.cloud.autoconfigure.s3.properties;
public class S3PluginProperties {
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3Properties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3Properties.java
index 307caeac9..3f528ca51 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3Properties.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/properties/S3Properties.java
@@ -23,11 +23,13 @@
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.internal.crt.S3CrtAsyncClient;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
+import software.amazon.encryption.s3.S3EncryptionClient;
/**
* Properties related to AWS S3.
*
* @author Maciej Walkowiak
+ * @author Matej Nedic
*/
@ConfigurationProperties(prefix = S3Properties.PREFIX)
public class S3Properties extends AwsClientProperties {
@@ -96,6 +98,20 @@ public class S3Properties extends AwsClientProperties {
@NestedConfigurationProperty
private S3PluginProperties plugin = new S3PluginProperties();
+ /**
+ * Configuration properties for {@link S3EncryptionClient} integration
+ */
+ @NestedConfigurationProperty
+ private S3EncryptionProperties encryption = new S3EncryptionProperties();
+
+ public S3EncryptionProperties getEncryption() {
+ return encryption;
+ }
+
+ public void setEncryption(S3EncryptionProperties encryption) {
+ this.encryption = encryption;
+ }
+
@Nullable
public Boolean getAccelerateModeEnabled() {
return this.accelerateModeEnabled;
diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java
index edfb39392..c1a1ca055 100644
--- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java
+++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfigurationTests.java
@@ -26,6 +26,8 @@
import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration;
import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration;
import io.awspring.cloud.autoconfigure.s3.properties.S3Properties;
+import io.awspring.cloud.autoconfigure.s3.provider.MyAesProvider;
+import io.awspring.cloud.autoconfigure.s3.provider.MyRsaProvider;
import io.awspring.cloud.s3.InMemoryBufferingS3OutputStreamProvider;
import io.awspring.cloud.s3.ObjectMetadata;
import io.awspring.cloud.s3.S3ObjectConverter;
@@ -38,7 +40,6 @@
import java.util.Objects;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import org.mockito.internal.util.MockUtil;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -58,15 +59,24 @@
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.utils.AttributeMap;
+import software.amazon.encryption.s3.S3EncryptionClient;
/**
* Tests for {@link S3AutoConfiguration}.
*
* @author Maciej Walkowiak
+ * @author Matej Nedic
*/
class S3AutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withPropertyValues("spring.cloud.aws.region.static:eu-west-1")
+ .withClassLoader(new FilteredClassLoader(S3EncryptionClient.class))
+ .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class,
+ CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class))
+ .withClassLoader(new FilteredClassLoader(S3EncryptionClient.class));
+
+ private final ApplicationContextRunner contextRunnerEncryption = new ApplicationContextRunner()
.withPropertyValues("spring.cloud.aws.region.static:eu-west-1")
.withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class,
CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class));
@@ -75,7 +85,7 @@ class S3AutoConfigurationTests {
.withPropertyValues("spring.cloud.aws.region.static:eu-west-1")
.withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class,
CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class))
- .withClassLoader(new FilteredClassLoader(S3AccessGrantsPlugin.class));
+ .withClassLoader(new FilteredClassLoader(S3AccessGrantsPlugin.class, S3EncryptionClient.class));
@Test
void setsS3AccessGrantIdentityProvider() {
@@ -123,12 +133,40 @@ void autoconfigurationIsNotTriggeredWhenS3ModuleIsNotOnClasspath() {
});
}
- @Test
- void s3ClientCanBeOverwritten() {
- contextRunner.withUserConfiguration(CustomS3ClientConfiguration.class).run(context -> {
- assertThat(context).hasSingleBean(S3Client.class);
- assertThat(MockUtil.isMock(context.getBean(S3Client.class))).isTrue();
- });
+ @Nested
+ class S3ClientTests {
+ @Test
+ void s3ClientCanBeOverwritten() {
+ contextRunnerEncryption
+ .withPropertyValues("spring.cloud.aws.s3.encryption.keyId:234abcd-12ab-34cd-56ef-1234567890ab")
+ .withUserConfiguration(CustomS3ClientConfiguration.class).run(context -> {
+ assertThat(context).hasSingleBean(S3Client.class);
+ });
+ }
+
+ @Test
+ void createsStandardClientWhenCrossRegionAndEncryptionModuleIsNotInClasspath() {
+ contextRunnerEncryption.withClassLoader(new FilteredClassLoader(S3EncryptionClient.class)).run(context -> {
+ assertThat(context).doesNotHaveBean(S3EncryptionClient.class);
+ assertThat(context).hasSingleBean(S3Client.class);
+ });
+ }
+
+ @Test
+ void createsEncryptionClientBackedByRsa() {
+ contextRunnerEncryption.withPropertyValues().withUserConfiguration(CustomRsaProvider.class).run(context -> {
+ assertThat(context).hasSingleBean(S3EncryptionClient.class);
+ assertThat(context).hasSingleBean(S3RsaProvider.class);
+ });
+ }
+
+ @Test
+ void createsEncryptionClientBackedByAes() {
+ contextRunnerEncryption.withPropertyValues().withUserConfiguration(CustomAesProvider.class).run(context -> {
+ assertThat(context).hasSingleBean(S3EncryptionClient.class);
+ assertThat(context).hasSingleBean(S3AesProvider.class);
+ });
+ }
}
@Nested
@@ -195,10 +233,11 @@ void withJacksonOnClasspathAutoconfiguresObjectConverter() {
@Test
void withoutJacksonOnClasspathDoesNotConfigureObjectConverter() {
- contextRunner.withClassLoader(new FilteredClassLoader(ObjectMapper.class)).run(context -> {
- assertThat(context).doesNotHaveBean(S3ObjectConverter.class);
- assertThat(context).doesNotHaveBean(S3Template.class);
- });
+ contextRunner.withClassLoader(new FilteredClassLoader(ObjectMapper.class, S3EncryptionClient.class))
+ .run(context -> {
+ assertThat(context).doesNotHaveBean(S3ObjectConverter.class);
+ assertThat(context).doesNotHaveBean(S3Template.class);
+ });
}
@Test
@@ -319,6 +358,22 @@ S3Client customS3Client() {
}
+ @Configuration
+ static class CustomRsaProvider {
+ @Bean
+ S3RsaProvider rsaProvider() {
+ return new MyRsaProvider();
+ }
+ }
+
+ @Configuration
+ static class CustomAesProvider {
+ @Bean
+ S3AesProvider aesProvider() {
+ return new MyAesProvider();
+ }
+ }
+
@Configuration(proxyBeanMethods = false)
static class CustomAwsConfigurerClient {
diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizerTests.java
index 2679effd9..1b5208169 100644
--- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizerTests.java
+++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizerTests.java
@@ -25,12 +25,14 @@
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.encryption.s3.S3EncryptionClient;
/**
* Tests for {@link S3ClientCustomizer}.
@@ -43,7 +45,8 @@ class S3ClientCustomizerTests {
.withPropertyValues("spring.cloud.aws.region.static:eu-west-1",
"spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop")
.withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class,
- CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class));
+ CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class))
+ .withClassLoader(new FilteredClassLoader(S3EncryptionClient.class));
@Test
void customClientCustomizer() {
diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/provider/MyAesProvider.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/provider/MyAesProvider.java
new file mode 100644
index 000000000..a4d407bab
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/provider/MyAesProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.s3.provider;
+
+import io.awspring.cloud.autoconfigure.s3.S3AesProvider;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+public class MyAesProvider implements S3AesProvider {
+ @Override
+ public SecretKey generateSecretKey() {
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
+ keyGenerator.init(256);
+ return keyGenerator.generateKey();
+ }
+ catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/provider/MyRsaProvider.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/provider/MyRsaProvider.java
new file mode 100644
index 000000000..d9f5bdfcc
--- /dev/null
+++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/provider/MyRsaProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.awspring.cloud.autoconfigure.s3.provider;
+
+import io.awspring.cloud.autoconfigure.s3.S3RsaProvider;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+
+public class MyRsaProvider implements S3RsaProvider {
+ @Override
+ public KeyPair generateKeyPair() {
+ try {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ return keyPairGenerator.generateKeyPair();
+ }
+ catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/spring-cloud-aws-dependencies/pom.xml b/spring-cloud-aws-dependencies/pom.xml
index 9d44607a8..c032e06e0 100644
--- a/spring-cloud-aws-dependencies/pom.xml
+++ b/spring-cloud-aws-dependencies/pom.xml
@@ -26,6 +26,7 @@
2.31.0
2.29.6
2.0.5
+ 3.2.3
1.6
4.2.0-M2
2.1.0
@@ -69,6 +70,13 @@
true
+
+ software.amazon.encryption.s3
+ amazon-s3-encryption-client-java
+ ${amazon.encryption.s3.version}
+ true
+
+
software.amazon.s3.accessgrants
aws-s3-accessgrants-java-plugin
diff --git a/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/ObjectMetadataTests.java b/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/ObjectMetadataTests.java
index f6213dc3d..568f88ed6 100644
--- a/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/ObjectMetadataTests.java
+++ b/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/ObjectMetadataTests.java
@@ -81,7 +81,7 @@ void mapsEnumsToString() {
@Test
void doesNotApplyContentLengthForPartUpload() {
- long objectContentLength = 16L;
+ long objectContentLength = 16L;
long partContentLength = 8L;
ObjectMetadata metadata = ObjectMetadata.builder().contentLength(objectContentLength).build();
diff --git a/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3ResourceIntegrationTests.java b/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3ResourceIntegrationTests.java
index 7f4b68e52..2821f7a36 100644
--- a/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3ResourceIntegrationTests.java
+++ b/spring-cloud-aws-s3/src/test/java/io/awspring/cloud/s3/S3ResourceIntegrationTests.java
@@ -236,7 +236,7 @@ void contentLengthCanBeSetForLargeFiles(S3OutputStreamProvider s3OutputStreamPro
outputStream.write(Files.toByteArray(file));
}
GetObjectResponse result = client
- .getObject(request -> request.bucket("first-bucket").key("new-file" + i + ".txt").build()).response();
+ .getObject(request -> request.bucket("first-bucket").key("new-file" + i + ".txt").build()).response();
assertThat(result.contentType()).isEqualTo("text/plain");
}