Skip to content

Commit

Permalink
Add aws-secretsmanager: prefix config import (spring-attic/spring-clo…
Browse files Browse the repository at this point in the history
…ud-aws#721)

In `spring-boot` 2.4, `Volume Mounted Config Directory Trees` was
added. This commit introduces the prefix `aws-secretsmanager:` which
will resolve the values given the configuration properties supported
by secrets manager integration. Also, if keys are added after the
prefix then just these will be resolved.

Use: `aws-secretsmanager:` or `aws-secretsmanager:my-secret-key` or
`aws-secretsmanager:my-secret-key;my-anoter-secret-key`

Closes spring-attic/spring-cloud-aws#655
Closes spring-attic/spring-cloud-aws#515

Co-authored-by: Maciej Walkowiak <walkowiak.maciej@yahoo.com>
  • Loading branch information
eddumelendez and maciejwalkowiak authored Dec 4, 2020
1 parent 13b5559 commit c7d2b71
Show file tree
Hide file tree
Showing 11 changed files with 511 additions and 49 deletions.
16 changes: 16 additions & 0 deletions docs/src/main/asciidoc/secrets-manager.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,19 @@ dots, dashes, forward slashes, backward slashes and underscores next to alphanum
|`true`
|Can be used to disable the Secrets Manager Configuration support even though the auto-configuration is on the classpath.
|===

In `spring-cloud` `2020.0.0` (aka Ilford), the bootstrap phase is no longer enabled by default. In order
enable it you need an additional dependency:

[source,xml,indent=0]
----
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>{spring-cloud-version}</version>
</dependency>
----

However, starting at `spring-cloud-aws` `2.3`, allows import default aws' secretsmanager keys
(`spring.config.import=aws-secretsmanager:`) or individual keys
(`spring.config.import=aws-secretsmanager:secret-key;other-secret-key`)
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>3.0.0-M4</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath/><!-- lookup parent from repository -->
</parent>

Expand All @@ -48,7 +48,7 @@
<javax-mail.version>1.5.5</javax-mail.version>
<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
<javax.activation.version>1.2.0</javax.activation.version>
<spring-cloud-commons.version>3.0.0-M4</spring-cloud-commons.version>
<spring-cloud-commons.version>3.0.0-SNAPSHOT</spring-cloud-commons.version>
<spring-javaformat.version>0.0.25</spring-javaformat.version>
</properties>

Expand Down
2 changes: 1 addition & 1 deletion spring-cloud-aws-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<version>2.3.2.BUILD-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-aws-dependencies</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.ReflectionUtils;

/**
* Builds a {@link CompositePropertySource} with various
Expand All @@ -41,17 +40,18 @@
*
* @author Fabio Maia
* @author Matej Nedic
* @author Eddú Meléndez
* @since 2.0.0
*/
public class AwsSecretsManagerPropertySourceLocator implements PropertySourceLocator {

private String propertySourceName;
private final String propertySourceName;

private AWSSecretsManager smClient;
private final AWSSecretsManager smClient;

private AwsSecretsManagerProperties properties;
private final AwsSecretsManagerProperties properties;

private final Set<String> contexts = new LinkedHashSet();
private final Set<String> contexts = new LinkedHashSet<>();

private Log logger = LogFactory.getLog(getClass());

Expand All @@ -78,56 +78,20 @@ public PropertySource<?> locate(Environment environment) {

ConfigurableEnvironment env = (ConfigurableEnvironment) environment;

String appName = properties.getName();

if (appName == null) {
appName = env.getProperty("spring.application.name");
}
AwsSecretsManagerPropertySources sources = new AwsSecretsManagerPropertySources(properties, logger);

List<String> profiles = Arrays.asList(env.getActiveProfiles());

String prefix = this.properties.getPrefix();

String appContext = prefix + "/" + appName;
addProfiles(this.contexts, appContext, profiles);
this.contexts.add(appContext);

String defaultContext = prefix + "/" + this.properties.getDefaultContext();
addProfiles(this.contexts, defaultContext, profiles);
this.contexts.add(defaultContext);
this.contexts.addAll(sources.getAutomaticContexts(profiles));

CompositePropertySource composite = new CompositePropertySource(this.propertySourceName);

for (String propertySourceContext : this.contexts) {
try {
composite.addPropertySource(create(propertySourceContext));
}
catch (Exception e) {
if (this.properties.isFailFast()) {
logger.error(
"Fail fast is set and there was an error reading configuration from AWS Secrets Manager:\n"
+ e.getMessage());
ReflectionUtils.rethrowRuntimeException(e);
}
else {
logger.warn("Unable to load AWS secret from " + propertySourceContext, e);
}
}
PropertySource<AWSSecretsManager> propertySource = sources.createPropertySource(propertySourceContext, true,
this.smClient);
composite.addPropertySource(propertySource);
}

return composite;
}

private AwsSecretsManagerPropertySource create(String context) {
AwsSecretsManagerPropertySource propertySource = new AwsSecretsManagerPropertySource(context, this.smClient);
propertySource.init();
return propertySource;
}

private void addProfiles(Set<String> contexts, String baseContext, List<String> profiles) {
for (String profile : profiles) {
contexts.add(baseContext + this.properties.getProfileSeparator() + profile);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2013-2020 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 org.springframework.cloud.aws.secretsmanager;

import java.util.ArrayList;
import java.util.List;

import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import org.apache.commons.logging.Log;

import org.springframework.util.StringUtils;

public class AwsSecretsManagerPropertySources {

private final AwsSecretsManagerProperties properties;

private final Log log;

public AwsSecretsManagerPropertySources(AwsSecretsManagerProperties properties, Log log) {
this.properties = properties;
this.log = log;
}

public List<String> getAutomaticContexts(List<String> profiles) {
List<String> contexts = new ArrayList<>();
String prefix = this.properties.getPrefix();
String defaultContext = getContext(prefix, this.properties.getDefaultContext());

String appName = this.properties.getName();

String appContext = prefix + "/" + appName;
addProfiles(contexts, appContext, profiles);
contexts.add(appContext);

addProfiles(contexts, defaultContext, profiles);
contexts.add(defaultContext);
return contexts;
}

protected String getContext(String prefix, String context) {
if (StringUtils.hasLength(prefix)) {
return prefix + "/" + context;
}
return context;
}

private void addProfiles(List<String> contexts, String baseContext, List<String> profiles) {
for (String profile : profiles) {
contexts.add(baseContext + this.properties.getProfileSeparator() + profile);
}
}

public AwsSecretsManagerPropertySource createPropertySource(String context, boolean optional,
AWSSecretsManager client) {
try {
AwsSecretsManagerPropertySource propertySource = new AwsSecretsManagerPropertySource(context, client);
propertySource.init();
return propertySource;
// TODO: howto call close when /refresh
}
catch (Exception e) {
if (this.properties.isFailFast() || !optional) {
throw new AwsSecretsManagerPropertySourceNotFoundException(e);
}
else {
log.warn("Unable to load AWS secret from " + context, e);
}
}
return null;
}

static class AwsSecretsManagerPropertySourceNotFoundException extends RuntimeException {

AwsSecretsManagerPropertySourceNotFoundException(Exception source) {
super(source);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerPropertySourceLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
* Spring Cloud Bootstrap Configuration for setting up an
Expand All @@ -46,15 +47,28 @@
@ConditionalOnProperty(prefix = AwsSecretsManagerProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
public class AwsSecretsManagerBootstrapConfiguration {

private final Environment environment;

public AwsSecretsManagerBootstrapConfiguration(Environment environment) {
this.environment = environment;
}

@Bean
AwsSecretsManagerPropertySourceLocator awsSecretsManagerPropertySourceLocator(AWSSecretsManager smClient,
AwsSecretsManagerProperties properties) {
if (StringUtils.isNullOrEmpty(properties.getName())) {
properties.setName(this.environment.getProperty("spring.application.name"));
}
return new AwsSecretsManagerPropertySourceLocator(smClient, properties);
}

@Bean
@ConditionalOnMissingBean
AWSSecretsManager smClient(AwsSecretsManagerProperties properties) {
return createSecretsManagerClient(properties);
}

public static AWSSecretsManager createSecretsManagerClient(AwsSecretsManagerProperties properties) {
AWSSecretsManagerClientBuilder builder = AWSSecretsManagerClientBuilder.standard()
.withClientConfiguration(SpringCloudClientConfiguration.getClientConfiguration());
if (!StringUtils.isNullOrEmpty(properties.getRegion())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2013-2020 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 org.springframework.cloud.aws.autoconfigure.secretsmanager;

import java.util.Collections;

import com.amazonaws.services.secretsmanager.AWSSecretsManager;

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.cloud.aws.secretsmanager.AwsSecretsManagerPropertySource;

/**
* @author Eddú Meléndez
* @since 2.3.0
*/
public class AwsSecretsManagerConfigDataLoader implements ConfigDataLoader<AwsSecretsManagerConfigDataResource> {

@Override
public ConfigData load(ConfigDataLoaderContext context, AwsSecretsManagerConfigDataResource resource) {
try {
AWSSecretsManager ssm = context.getBootstrapContext().get(AWSSecretsManager.class);
AwsSecretsManagerPropertySource propertySource = resource.getPropertySources()
.createPropertySource(resource.getContext(), resource.isOptional(), ssm);
return new ConfigData(Collections.singletonList(propertySource));
}
catch (Exception e) {
throw new ConfigDataResourceNotFoundException(resource, e);
}
}

}
Loading

0 comments on commit c7d2b71

Please sign in to comment.