Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

S3 access grants plugin autoconfiguration #1247

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/src/main/asciidoc/s3.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,21 @@ public class SimpleResourceLoadingBean {
Resolving resources throughout all buckets can be very time consuming depending on the number of buckets a user owns.
====

=== Using S3 Access grants

Sometimes there is a need to make access control to S3 bucket contents fine grained.
Since IAM polices and S3 Policies only support 10kbs size, S3 Access Grant is solving this by allowing fine grained access control over content in bucket.

To use S3 Access Grants out of the box with `S3Client` and `S3Template` introduce following plugin:

[source,xml]
----
<dependency>
<groupId>software.amazon.s3.accessgrants</groupId>
<artifactId>aws-s3-accessgrants-java-plugin</artifactId>
</dependency>
----

=== Using S3Template

Spring Cloud AWS provides a higher abstraction on the top of `S3Client` providing methods for the most common use cases when working with S3.
Expand Down
5 changes: 5 additions & 0 deletions spring-cloud-aws-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@
<artifactId>amazon-dax-client</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.s3.accessgrants</groupId>
<artifactId>aws-s3-accessgrants-java-plugin</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.ClassUtils;
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;
Expand Down Expand Up @@ -78,6 +80,11 @@ S3ClientBuilder s3ClientBuilder(AwsClientBuilderConfigurer awsClientBuilderConfi
connectionDetails.getIfAvailable(), configurer.getIfAvailable(), s3ClientCustomizers.orderedStream(),
awsSyncClientCustomizers.orderedStream());

if (ClassUtils.isPresent("software.amazon.awssdk.s3accessgrants.plugin.S3AccessGrantsPlugin", null)) {
S3AccessGrantsPlugin s3AccessGrantsPlugin = S3AccessGrantsPlugin.builder()
.enableFallback(properties.getPlugin().getEnableFallback()).build();
builder.addPlugin(s3AccessGrantsPlugin);
}
Optional.ofNullable(this.properties.getCrossRegionEnabled()).ifPresent(builder::crossRegionAccessEnabled);

builder.serviceConfiguration(this.properties.toS3Configuration());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.awspring.cloud.autoconfigure.s3.properties;

public class S3PluginProperties {

/**
* If set to false if Access Grants does not find/return permissions, S3Client won't try to determine if policies
* grant access If set to true fallback policies S3/IAM will be evaluated.
*/
private boolean enableFallback;

public boolean getEnableFallback() {
return enableFallback;
}

public void setEnableFallback(boolean enableFallback) {
this.enableFallback = enableFallback;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public class S3Properties extends AwsClientProperties {
@NestedConfigurationProperty
private S3CrtClientProperties crt;

@NestedConfigurationProperty
private S3PluginProperties plugin = new S3PluginProperties();

@Nullable
public Boolean getAccelerateModeEnabled() {
return this.accelerateModeEnabled;
Expand Down Expand Up @@ -175,4 +178,12 @@ public S3Configuration toS3Configuration() {
propertyMapper.from(this::getUseArnRegionEnabled).whenNonNull().to(config::useArnRegionEnabled);
return config.build();
}

public S3PluginProperties getPlugin() {
return plugin;
}

public void setPlugin(S3PluginProperties plugin) {
this.plugin = plugin;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import software.amazon.awssdk.core.client.config.SdkClientOption;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.identity.spi.IdentityProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.utils.AttributeMap;

Expand Down Expand Up @@ -82,6 +83,10 @@ public Boolean getDualstackEnabled() {
return clientConfigurationAttributes.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED);
}

public IdentityProvider getIdentityProviders() {
return clientConfigurationAttributes.get(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER);
}

public DefaultsMode getDefaultsMode() {
return clientConfigurationAttributes.get(AwsClientOption.DEFAULTS_MODE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.s3accessgrants.plugin.S3AccessGrantsIdentityProvider;
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;
Expand All @@ -69,6 +71,30 @@ class S3AutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class,
CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class));

private final ApplicationContextRunner contextRunnerWithoutGrant = new ApplicationContextRunner()
.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));

@Test
void setsS3AccessGrantIdentityProvider() {
contextRunner.run(context -> {
S3ClientBuilder builder = context.getBean(S3ClientBuilder.class);
ConfiguredAwsClient client = new ConfiguredAwsClient(builder.build());
assertThat(client.getIdentityProviders()).isInstanceOf(S3AccessGrantsIdentityProvider.class);
});
}

@Test
void doesNotSetS3AccessGrantIdentityProvider() {
contextRunnerWithoutGrant.run(context -> {
S3ClientBuilder builder = context.getBean(S3ClientBuilder.class);
ConfiguredAwsClient client = new ConfiguredAwsClient(builder.build());
assertThat(client.getIdentityProviders()).isNotInstanceOf(S3AccessGrantsIdentityProvider.class);
});
}

@Test
void createsS3ClientBean() {
this.contextRunner.run(context -> {
Expand Down Expand Up @@ -149,6 +175,7 @@ void withCustomGlobalEndpointAndS3Endpoint() {
"spring.cloud.aws.s3.endpoint:http://localhost:9999").run(context -> {
S3ClientBuilder builder = context.getBean(S3ClientBuilder.class);
ConfiguredAwsClient client = new ConfiguredAwsClient(builder.build());
assertThat(client.getIdentityProviders()).isInstanceOf(S3AccessGrantsIdentityProvider.class);
assertThat(client.getEndpoint()).isEqualTo(URI.create("http://localhost:9999"));
assertThat(client.isEndpointOverridden()).isTrue();
});
Expand Down
7 changes: 7 additions & 0 deletions spring-cloud-aws-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<bytebuddy.version>1.14.9</bytebuddy.version>
<spring-modulith.version>1.2.3</spring-modulith.version>
<wiremock-standalone.version>3.3.1</wiremock-standalone.version>
<amazon.s3.accessgrants>2.2.0</amazon.s3.accessgrants>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -68,6 +69,12 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>software.amazon.s3.accessgrants</groupId>
<artifactId>aws-s3-accessgrants-java-plugin</artifactId>
<version>${amazon.s3.accessgrants}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
Expand Down