From f6aabcd93903b01bef09570b01fa9e38db54d635 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 8 Jan 2024 16:07:08 +0100 Subject: [PATCH] Add `enabled` flag to Secrets Manager integration. (#1009) Fixes #1005 --- docs/src/main/asciidoc/secrets-manager.adoc | 1 + .../SecretsManagerConfigDataLoader.java | 23 ++++++++--- ...retsManagerConfigDataLocationResolver.java | 40 ++++++++++++------- .../SecretsManagerConfigDataResource.java | 30 ++++++++++---- .../SecretsManagerProperties.java | 13 ++++++ ...nagerConfigDataLoaderIntegrationTests.java | 16 ++++++++ 6 files changed, 94 insertions(+), 29 deletions(-) diff --git a/docs/src/main/asciidoc/secrets-manager.adoc b/docs/src/main/asciidoc/secrets-manager.adoc index ad5e04aa3..39309b72c 100644 --- a/docs/src/main/asciidoc/secrets-manager.adoc +++ b/docs/src/main/asciidoc/secrets-manager.adoc @@ -303,6 +303,7 @@ The Spring Boot Starter for Secrets Manager provides the following configuration [cols="2,3,1,1"] |=== | Name | Description | Required | Default value +| `spring.cloud.aws.secretsmanager.enabled` | Enables the Secrets Manager integration. | No | `true` | `spring.cloud.aws.secretsmanager.endpoint` | Configures endpoint used by `SecretsManagerClient`. | No | `null` | `spring.cloud.aws.secretsmanager.region` | Configures region used by `SecretsManagerClient`. | No | `null` | `spring.cloud.aws.secretsmanager.reload.strategy` | `Enum` | `refresh` | The strategy to use when firing a reload (`refresh`, `restart_context`) diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoader.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoader.java index 769e46fe5..2b5e1a39a 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoader.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoader.java @@ -18,11 +18,13 @@ import io.awspring.cloud.autoconfigure.config.BootstrapLoggingHelper; import io.awspring.cloud.secretsmanager.SecretsManagerPropertySource; import java.util.Collections; +import java.util.Map; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataLoader; import org.springframework.boot.context.config.ConfigDataLoaderContext; import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.core.env.MapPropertySource; import org.springframework.lang.Nullable; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; @@ -46,14 +48,23 @@ public SecretsManagerConfigDataLoader(DeferredLogFactory logFactory) { @Nullable public ConfigData load(ConfigDataLoaderContext context, SecretsManagerConfigDataResource resource) { try { - SecretsManagerClient sm = context.getBootstrapContext().get(SecretsManagerClient.class); - SecretsManagerPropertySource propertySource = resource.getPropertySources() - .createPropertySource(resource.getContext(), resource.isOptional(), sm); - if (propertySource != null) { - return new ConfigData(Collections.singletonList(propertySource)); + // resource is disabled if secrets manager integration is disabled via + // spring.cloud.aws.secretsmanager.enabled=false + if (resource.isEnabled()) { + SecretsManagerClient sm = context.getBootstrapContext().get(SecretsManagerClient.class); + SecretsManagerPropertySource propertySource = resource.getPropertySources() + .createPropertySource(resource.getContext(), resource.isOptional(), sm); + if (propertySource != null) { + return new ConfigData(Collections.singletonList(propertySource)); + } + else { + return null; + } } else { - return null; + // create dummy empty config data + return new ConfigData( + Collections.singletonList(new MapPropertySource("aws-secretsmanager:" + context, Map.of()))); } } catch (Exception e) { diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java index 6f837e91b..330308c71 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java @@ -62,25 +62,35 @@ protected String getPrefix() { public List resolve(ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location) throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { - registerBean(resolverContext, AwsProperties.class, loadAwsProperties(resolverContext.getBinder())); - registerBean(resolverContext, SecretsManagerProperties.class, loadProperties(resolverContext.getBinder())); - registerBean(resolverContext, CredentialsProperties.class, - loadCredentialsProperties(resolverContext.getBinder())); - registerBean(resolverContext, RegionProperties.class, loadRegionProperties(resolverContext.getBinder())); - - registerAndPromoteBean(resolverContext, SecretsManagerClient.class, this::createAwsSecretsManagerClient); - - SecretsManagerPropertySources propertySources = new SecretsManagerPropertySources(); + SecretsManagerProperties secretsManagerProperties = loadProperties(resolverContext.getBinder()); List contexts = getCustomContexts(location.getNonPrefixedValue(PREFIX)); - List locations = new ArrayList<>(); - contexts.forEach(propertySourceContext -> locations.add( - new SecretsManagerConfigDataResource(propertySourceContext, location.isOptional(), propertySources))); + SecretsManagerPropertySources propertySources = new SecretsManagerPropertySources(); - if (!location.isOptional() && locations.isEmpty()) { - throw new SecretsManagerKeysMissingException( - "No Secrets Manager keys provided in `spring.config.import=aws-secretsmanager:` configuration."); + if (secretsManagerProperties.isEnabled()) { + registerBean(resolverContext, AwsProperties.class, loadAwsProperties(resolverContext.getBinder())); + registerBean(resolverContext, SecretsManagerProperties.class, secretsManagerProperties); + registerBean(resolverContext, CredentialsProperties.class, + loadCredentialsProperties(resolverContext.getBinder())); + registerBean(resolverContext, RegionProperties.class, loadRegionProperties(resolverContext.getBinder())); + registerAndPromoteBean(resolverContext, SecretsManagerClient.class, this::createAwsSecretsManagerClient); + + contexts.forEach( + propertySourceContext -> locations.add(new SecretsManagerConfigDataResource(propertySourceContext, + location.isOptional(), propertySources))); + + if (!location.isOptional() && locations.isEmpty()) { + throw new SecretsManagerKeysMissingException( + "No Secrets Manager keys provided in `spring.config.import=aws-secretsmanager:` configuration."); + } + } + else { + // create dummy resources with enabled flag set to false, + // because returned locations cannot be empty + contexts.forEach( + propertySourceContext -> locations.add(new SecretsManagerConfigDataResource(propertySourceContext, + location.isOptional(), false, propertySources))); } return locations; diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataResource.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataResource.java index 598d6aa21..b8468e18d 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataResource.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataResource.java @@ -32,15 +32,26 @@ public class SecretsManagerConfigDataResource extends ConfigDataResource { private final boolean optional; + /** + * If resource should be resolved. This flag has the same value as {@link SecretsManagerProperties#isEnabled()}. + */ + private final boolean enabled; + private final SecretsManagerPropertySources propertySources; - public SecretsManagerConfigDataResource(String context, boolean optional, + public SecretsManagerConfigDataResource(String context, boolean optional, boolean enabled, SecretsManagerPropertySources propertySources) { this.context = context; this.optional = optional; + this.enabled = enabled; this.propertySources = propertySources; } + public SecretsManagerConfigDataResource(String context, boolean optional, + SecretsManagerPropertySources propertySources) { + this(context, optional, true, propertySources); + } + /** * Returns context which is equal to Secret Manager secret name. * @return the context @@ -57,30 +68,33 @@ public boolean isOptional() { return this.optional; } + boolean isEnabled() { + return enabled; + } + public SecretsManagerPropertySources getPropertySources() { return this.propertySources; } @Override public boolean equals(Object o) { - if (this == o) { + if (this == o) return true; - } - if (o == null || getClass() != o.getClass()) { + if (o == null || getClass() != o.getClass()) return false; - } SecretsManagerConfigDataResource that = (SecretsManagerConfigDataResource) o; - return this.optional == that.optional && this.context.equals(that.context); + return optional == that.optional && enabled == that.enabled && Objects.equals(context, that.context); } @Override public int hashCode() { - return Objects.hash(this.optional, this.context); + return Objects.hash(context, optional, enabled); } @Override public String toString() { - return new ToStringCreator(this).append("context", context).append("optional", optional).toString(); + return new ToStringCreator(this).append("context", context).append("optional", optional) + .append("enabled", enabled).toString(); } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerProperties.java index c4ccfb7c4..86b8cce97 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerProperties.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerProperties.java @@ -45,6 +45,19 @@ public class SecretsManagerProperties extends AwsClientProperties { @NestedConfigurationProperty private ReloadProperties reload = new ReloadProperties(); + /** + * Enables Secrets Manager integration. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public ReloadProperties getReload() { return reload; } diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java index 31929ce06..01a1b89f4 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java @@ -272,6 +272,22 @@ void endpointCanBeOverwrittenWithGlobalAwsProperties() { } } + @Test + void propertyIsNotResolvedWhenIntegrationIsDisabled() { + SpringApplication application = new SpringApplication(SecretsManagerConfigDataLoaderIntegrationTests.App.class); + application.setWebApplicationType(WebApplicationType.NONE); + + try (ConfigurableApplicationContext context = application.run( + "--spring.config.import=aws-secretsmanager:/config/spring;/config/second", + "--spring.cloud.aws.secretsmanager.enabled=false", "--spring.cloud.aws.credentials.secret-key=noop", + "--spring.cloud.aws.endpoint=" + localstack.getEndpoint(), + "--spring.cloud.aws.credentials.access-key=noop", "--spring.cloud.aws.credentials.secret-key=noop", + "--spring.cloud.aws.region.static=eu-west-1")) { + assertThat(context.getEnvironment().getProperty("message")).isNull(); + assertThat(context.getBeanProvider(SecretsManagerClient.class).getIfAvailable()).isNull(); + } + } + @Test void serviceSpecificEndpointTakesPrecedenceOverGlobalAwsRegion() { SpringApplication application = new SpringApplication(SecretsManagerConfigDataLoaderIntegrationTests.App.class);