Skip to content

Commit

Permalink
Add enabled flag to Secrets Manager integration. (#1009)
Browse files Browse the repository at this point in the history
Fixes #1005
  • Loading branch information
maciejwalkowiak authored Jan 8, 2024
1 parent e6f9e2d commit f6aabcd
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 29 deletions.
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/secrets-manager.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,35 @@ protected String getPrefix() {
public List<SecretsManagerConfigDataResource> 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<String> contexts = getCustomContexts(location.getNonPrefixedValue(PREFIX));

List<SecretsManagerConfigDataResource> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit f6aabcd

Please sign in to comment.