Skip to content

Commit

Permalink
Merge branch 'main' of github.com:awspring/spring-cloud-aws into awsp…
Browse files Browse the repository at this point in the history
  • Loading branch information
Forfend committed Nov 14, 2024
2 parents 8de3eec + 6151537 commit 84ffac1
Show file tree
Hide file tree
Showing 39 changed files with 583 additions and 67 deletions.
8 changes: 4 additions & 4 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ Spring Cloud AWS simplifies using AWS managed services in a Spring and Spring Bo

For a deep dive into the project, refer to the Spring Cloud AWS documentation:

| Version | Reference Docs | API Docs |
|------------------------|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
| Spring Cloud AWS 3.2.0 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.2.0/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.2.0/apidocs/index.html) |
| Spring Cloud AWS 3.1.1 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.1.1/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.1.1/apidocs/index.html) |
| Spring Cloud AWS 3.0.4 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.0.4/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.0.4/apidocs/index.html) |
| Spring Cloud AWS 2.4.4 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.4.4/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.4.4/apidocs/index.html) |
| Spring Cloud AWS 2.3.5 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.3.5/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.3.5/apidocs/index.html) |
| Version | Reference Docs | API Docs |
|---------------------------|--------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
| Spring Cloud AWS 3.3.0-M1 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.3.0-M1/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.3.0-M1/apidocs/index.html) |
| Spring Cloud AWS 3.2.1 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.2.1/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.2.0/apidocs/index.html) |
| Spring Cloud AWS 3.1.1 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.1.1/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.1.1/apidocs/index.html) |
| Spring Cloud AWS 3.0.4 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.0.4/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/3.0.4/apidocs/index.html) |
| Spring Cloud AWS 2.4.4 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.4.4/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.4.4/apidocs/index.html) |
| Spring Cloud AWS 2.3.5 | [Reference Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.3.5/reference/html/index.html) | [API Docs](https://docs.awspring.io/spring-cloud-aws/docs/2.3.5/apidocs/index.html) |

## Sponsors

Expand All @@ -30,7 +31,7 @@ This project has dependency and transitive dependencies on Spring Projects. The
| 2.4.x (maintenance mode) | [2021.0.x](https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2021.0-Release-Notes) (3.1/Jubilee) | 2.6.x, 2.7.x | 5.3.x | 1.x |
| 3.0.x | [2022.0.x](https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2022.0-Release-Notes) (4.0/Kilburn) | 3.0.x, 3.1.x | 6.0.x | 2.x |
| 3.1.x | [2023.0.x](https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2023.0-Release-Notes) (4.0/Kilburn) | 3.2.x | 6.1.x | 2.x |
| 3.2.0 | [2023.0.x](https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2023.0-Release-Notes) (4.0/Kilburn) | 3.2.x, 3.3.x | 6.1.x | 2.x |
| 3.2.1 | [2023.0.x](https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2023.0-Release-Notes) (4.0/Kilburn) | 3.2.x, 3.3.x | 6.1.x | 2.x |

**Note**: 3.0.0-M2 is the last version compatible with Spring Boot 2.7.x and Spring Cloud 3.1. Starting from 3.0.0-M3, project has switched to Spring Boot 3.0.

Expand Down
86 changes: 84 additions & 2 deletions docs/src/main/asciidoc/s3.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ Transfer Manager works the best with CRT S3 Client. To auto-configure CRT based
[source,xml]
----
<dependency>
<groupId>software.amazon.awssdk.crt</groupId>
<artifactId>aws-crt</artifactId>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-crt-client</artifactId>
</dependency>
----

Expand Down Expand Up @@ -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]
----
<dependency>
<groupId>software.amazon.encryption.s3</groupId>
<artifactId>amazon-s3-encryption-client-java</artifactId>
</dependency>
----


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]
----
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>kms</artifactId>
<optional>true</optional>
</dependency>
----


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.
Expand Down
4 changes: 2 additions & 2 deletions docs/src/main/asciidoc/testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Maven coordinates, using <<index.adoc#bill-of-materials, Spring Cloud AWS BOM>>:
</dependency>
----

== Service Connection
=== Service Connection

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.testing.testcontainers.service-connections[@ServiceConnection] for https://java.testcontainers.org/modules/localstack/[LocalStack Container] simplifies configuring Spring Cloud AWS based project to point to LocalStack instead of real AWS.

Expand Down Expand Up @@ -41,7 +41,7 @@ class LocalstackAwsClientFactoryTest {
@Container
private LocalStackContainer localStackContainer = new LocalStackContainer(
DockerImageName.parse("localstack/localstack:3.2.0"));
DockerImageName.parse("localstack/localstack:3.8.1"));
@Test
void aTest() {
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>4.1.3</version>
<version>4.2.0-M2</version>
<relativePath/><!-- lookup parent from repository -->
</parent>

Expand Down
10 changes: 10 additions & 0 deletions spring-cloud-aws-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,15 @@
<artifactId>sts</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>kms</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.encryption.s3</groupId>
<artifactId>amazon-s3-encryption-client-java</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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<AwsClientCustomizer<S3EncryptionClient.Builder>> configurer,
ObjectProvider<AwsConnectionDetails> connectionDetails,
ObjectProvider<S3EncryptionClientCustomizer> s3ClientCustomizers,
ObjectProvider<AwsSyncClientCustomizer> awsSyncClientCustomizers,
ObjectProvider<S3RsaProvider> rsaProvider, ObjectProvider<S3AesProvider> 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<S3RsaProvider> rsaProvider, ObjectProvider<S3AesProvider> 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) {
Expand All @@ -143,5 +205,4 @@ S3OutputStreamProvider inMemoryBufferingS3StreamProvider(S3Client s3Client,
return new InMemoryBufferingS3OutputStreamProvider(s3Client,
contentTypeResolver.orElseGet(PropertiesS3ObjectContentTypeResolver::new));
}

}
Original file line number Diff line number Diff line change
@@ -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<S3EncryptionClient.Builder> {
}
Loading

0 comments on commit 84ffac1

Please sign in to comment.