Skip to content
This repository was archived by the owner on Jan 19, 2022. It is now read-only.

Commit 908b029

Browse files
Polish Secrets Manager Integration (#743)
- fixed handling failFast and optional flag - added logs - added tests
1 parent 66e5446 commit 908b029

File tree

10 files changed

+129
-48
lines changed

10 files changed

+129
-48
lines changed
Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,67 @@
11
|===
22
|Name | Default | Description
33

4-
|aws.paramstore.default-context | application |
4+
|aws.paramstore.default-context | application |
55
|aws.paramstore.enabled | true | Is AWS Parameter Store support enabled.
66
|aws.paramstore.endpoint | | Overrides the default endpoint.
77
|aws.paramstore.fail-fast | true | Throw exceptions during config lookup if true, otherwise, log warnings.
88
|aws.paramstore.name | | Alternative to spring.application.name to use in looking up values in AWS Parameter Store.
99
|aws.paramstore.prefix | /config | Prefix indicating first level for every property. Value must start with a forward slash followed by a valid path segment or be empty. Defaults to "/config".
10-
|aws.paramstore.profile-separator | _ |
10+
|aws.paramstore.profile-separator | _ |
1111
|aws.paramstore.region | | If region value is not null or empty it will be used in creation of AWSSimpleSystemsManagement.
12-
|aws.secretsmanager.default-context | application |
12+
|aws.secretsmanager.default-context | application |
1313
|aws.secretsmanager.enabled | true | Is AWS Secrets Manager support enabled.
1414
|aws.secretsmanager.endpoint | | Overrides the default endpoint.
1515
|aws.secretsmanager.fail-fast | true | Throw exceptions during config lookup if true, otherwise, log warnings.
1616
|aws.secretsmanager.name | | Alternative to spring.application.name to use in looking up values in AWS Secrets Manager.
1717
|aws.secretsmanager.prefix | /secret | Prefix indicating first level for every property. Value must start with a forward slash followed by a valid path segment or be empty. Defaults to "/config".
18-
|aws.secretsmanager.profile-separator | _ |
18+
|aws.secretsmanager.profile-separator | _ |
1919
|aws.secretsmanager.region | | If region value is not null or empty it will be used in creation of AWSSecretsManager.
2020
|cloud.aws.credentials.access-key | | The access key to be used with a static provider.
2121
|cloud.aws.credentials.instance-profile | false | Configures an instance profile credentials provider with no further configuration.
2222
|cloud.aws.credentials.profile-name | | The AWS profile name.
2323
|cloud.aws.credentials.profile-path | | The AWS profile path.
2424
|cloud.aws.credentials.secret-key | | The secret key to be used with a static provider.
25-
|cloud.aws.elasticache.cache-names | |
25+
|cloud.aws.elasticache.cache-names | |
2626
|cloud.aws.elasticache.clusters | | Configures the cache clusters for the caching configuration. Support one or multiple caches {@link Cluster} configurations with their physical cache name (as configured in the ElastiCache service) or their logical cache name if the caches are configured inside a stack and {@link org.springframework.cloud.aws.context.config.annotation.EnableStackConfiguration} annotation is used inside the application.
2727
|cloud.aws.elasticache.default-expiration | 0 | Configures the default expiration time in seconds if there is no custom expiration time configuration with a {@link Cluster} configuration for the cache. The expiration time is implementation specific (e.g. Redis or Memcached) and could therefore differ in the behaviour based on the cache implementation.
2828
|cloud.aws.elasticache.enabled | true | Enables ElastiCache integration.
29-
|cloud.aws.elasticache.expiry-time-per-cache | |
29+
|cloud.aws.elasticache.expiry-time-per-cache | |
3030
|cloud.aws.instance.data.enabled | false | Enables Instance Data integration.
3131
|cloud.aws.loader.core-pool-size | 1 | The core pool size of the Task Executor used for parallel S3 interaction. @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor#setCorePoolSize(int)
3232
|cloud.aws.loader.max-pool-size | | The maximum pool size of the Task Executor used for parallel S3 interaction. @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor#setMaxPoolSize(int)
3333
|cloud.aws.loader.queue-capacity | | The maximum queue capacity for backed up S3 requests. @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor#setQueueCapacity(int)
3434
|cloud.aws.mail.enabled | true | Enables Mail integration.
35-
|cloud.aws.mail.endpoint | |
36-
|cloud.aws.mail.region | |
35+
|cloud.aws.mail.endpoint | |
36+
|cloud.aws.mail.region | |
3737
|cloud.aws.rds.enabled | true | Enables RDS integration.
38-
|cloud.aws.rds.endpoint | |
38+
|cloud.aws.rds.endpoint | |
3939
|cloud.aws.rds.instances | | List of RdsInstances.
40-
|cloud.aws.rds.region | |
40+
|cloud.aws.rds.region | |
4141
|cloud.aws.s3.endpoint | | Overrides the default endpoint.
4242
|cloud.aws.s3.region | | Overrides the default region.
4343
|cloud.aws.sns.enabled | true | Enables SNS integration.
44-
|cloud.aws.sns.endpoint | |
45-
|cloud.aws.sns.region | |
44+
|cloud.aws.sns.endpoint | |
45+
|cloud.aws.sns.region | |
4646
|cloud.aws.sqs.enabled | true | Enables SQS integration.
47-
|cloud.aws.sqs.endpoint | |
47+
|cloud.aws.sqs.endpoint | |
4848
|cloud.aws.sqs.handler.default-deletion-policy | | Configures global deletion policy used if deletion policy is not explicitly set on {@link SqsListener}.
4949
|cloud.aws.sqs.listener.auto-startup | true | Configures if this container should be automatically started.
5050
|cloud.aws.sqs.listener.back-off-time | | The number of milliseconds the polling thread must wait before trying to recover when an error occurs (e.g. connection timeout).
5151
|cloud.aws.sqs.listener.max-number-of-messages | 10 | The maximum number of messages that should be retrieved during one poll to the Amazon SQS system. This number must be a positive, non-zero number that has a maximum number of 10. Values higher then 10 are currently not supported by the queueing system.
5252
|cloud.aws.sqs.listener.queue-stop-timeout | | The queue stop timeout that waits for a queue to stop before interrupting the running thread.
5353
|cloud.aws.sqs.listener.visibility-timeout | | The duration (in seconds) that the received messages are hidden from subsequent poll requests after being retrieved from the system.
5454
|cloud.aws.sqs.listener.wait-timeout | 20 | The wait timeout that the poll request will wait for new message to arrive if the are currently no messages on the queue. Higher values will reduce poll request to the system significantly. The value should be between 1 and 20. For more information read the <a href= "https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-long-polling.html">documentation</a>.
55-
|cloud.aws.sqs.region | |
55+
|cloud.aws.sqs.region | |
5656
|cloud.aws.stack.auto | true | Enables the automatic stack name detection for the application.
5757
|cloud.aws.stack.enabled | true | Enables Stack integration.
5858
|cloud.aws.stack.name | | The name of the manually configured stack name that will be used to retrieve the resources.
5959
|spring.cloud.aws.security.cognito.algorithm | RS256 | Encryption algorithm used to sign the JWK token.
6060
|spring.cloud.aws.security.cognito.app-client-id | | Non-dynamic audience string to validate.
6161
|spring.cloud.aws.security.cognito.enabled | true | Enables Cognito integration.
62-
|spring.cloud.aws.security.cognito.region | |
63-
|spring.cloud.aws.security.cognito.user-pool-id | |
62+
|spring.cloud.aws.security.cognito.region | |
63+
|spring.cloud.aws.security.cognito.user-pool-id | |
6464
|spring.cloud.aws.ses.enabled | true | Enables Simple Email Service integration.
6565
|spring.cloud.aws.ses.region | | Overrides the default region.
6666

67-
|===
67+
|===

spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySource.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.cloud.aws.secretsmanager;
1818

19-
import java.io.IOException;
2019
import java.util.LinkedHashMap;
2120
import java.util.Map;
2221
import java.util.Set;
@@ -25,35 +24,40 @@
2524
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
2625
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
2726
import com.amazonaws.services.secretsmanager.model.ResourceNotFoundException;
27+
import com.fasterxml.jackson.core.JsonProcessingException;
2828
import com.fasterxml.jackson.core.type.TypeReference;
2929
import com.fasterxml.jackson.databind.ObjectMapper;
3030

3131
import org.springframework.core.env.EnumerablePropertySource;
3232

3333
/**
3434
* Retrieves secret value under the given context / path from the AWS Secrets Manager
35-
* using the provided SM client.
35+
* using the provided Secrets Manager client.
3636
*
3737
* @author Fabio Maia
38+
* @author Maciej Walkowiak
3839
* @since 2.0.0
3940
*/
4041
public class AwsSecretsManagerPropertySource extends EnumerablePropertySource<AWSSecretsManager> {
4142

4243
private final ObjectMapper jsonMapper = new ObjectMapper();
4344

44-
private String context;
45+
private final String context;
4546

46-
private Map<String, Object> properties = new LinkedHashMap<>();
47+
private final Map<String, Object> properties = new LinkedHashMap<>();
4748

4849
public AwsSecretsManagerPropertySource(String context, AWSSecretsManager smClient) {
4950
super(context, smClient);
5051
this.context = context;
5152
}
5253

54+
/**
55+
* Loads properties from the Secrets Manager secret.
56+
* @throws ResourceNotFoundException if specified secret does not exist in the
57+
* database.
58+
*/
5359
public void init() {
54-
GetSecretValueRequest secretValueRequest = new GetSecretValueRequest();
55-
secretValueRequest.setSecretId(context);
56-
readSecretValue(secretValueRequest);
60+
readSecretValue(new GetSecretValueRequest().withSecretId(context));
5761
}
5862

5963
@Override
@@ -78,10 +82,7 @@ private void readSecretValue(GetSecretValueRequest secretValueRequest) {
7882
properties.put(secretEntry.getKey(), secretEntry.getValue());
7983
}
8084
}
81-
catch (ResourceNotFoundException e) {
82-
logger.debug("AWS secret not found from " + secretValueRequest.getSecretId());
83-
}
84-
catch (IOException e) {
85+
catch (JsonProcessingException e) {
8586
throw new RuntimeException(e);
8687
}
8788
}

spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocator.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
* name and default context permutations. Mostly copied from Spring Cloud Consul's config
3939
* support.
4040
*
41+
* Note: this class is used only by legacy Spring Cloud Bootstrap phase based config
42+
* loading.
43+
*
4144
* @author Fabio Maia
4245
* @author Matej Nedic
4346
* @author Eddú Meléndez
@@ -86,9 +89,11 @@ public PropertySource<?> locate(Environment environment) {
8689
CompositePropertySource composite = new CompositePropertySource(this.propertySourceName);
8790

8891
for (String propertySourceContext : this.contexts) {
89-
PropertySource<AWSSecretsManager> propertySource = sources.createPropertySource(propertySourceContext, true,
90-
this.smClient);
91-
composite.addPropertySource(propertySource);
92+
PropertySource<AWSSecretsManager> propertySource = sources.createPropertySource(propertySourceContext,
93+
!this.properties.isFailFast(), this.smClient);
94+
if (propertySource != null) {
95+
composite.addPropertySource(propertySource);
96+
}
9297
}
9398

9499
return composite;

spring-cloud-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySources.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,30 @@ private void addProfiles(List<String> contexts, String baseContext, List<String>
6464
}
6565
}
6666

67+
/**
68+
* Creates property source for given context.
69+
* @param context property source context equivalent to the secret name
70+
* @param optional if creating context should fail with exception if secret cannot be
71+
* loaded
72+
* @param client Secret Manager client
73+
* @return a property source or null if secret could not be loaded and optional is set
74+
* to true
75+
*/
6776
public AwsSecretsManagerPropertySource createPropertySource(String context, boolean optional,
6877
AWSSecretsManager client) {
78+
log.info("Loading secrets from AWS Secret Manager secret with name: " + context + ", optional: " + optional);
6979
try {
7080
AwsSecretsManagerPropertySource propertySource = new AwsSecretsManagerPropertySource(context, client);
7181
propertySource.init();
7282
return propertySource;
7383
// TODO: howto call close when /refresh
7484
}
7585
catch (Exception e) {
76-
if (this.properties.isFailFast() || !optional) {
86+
if (!optional) {
7787
throw new AwsSecretsManagerPropertySourceNotFoundException(e);
7888
}
7989
else {
80-
log.warn("Unable to load AWS secret from " + context, e);
90+
log.warn("Unable to load AWS secret from " + context + ". " + e.getMessage());
8191
}
8292
}
8393
return null;

spring-cloud-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceLocatorTest.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
2323
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
2424
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
25+
import com.amazonaws.services.secretsmanager.model.ResourceNotFoundException;
2526
import org.junit.jupiter.api.Test;
2627

28+
import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerPropertySources.AwsSecretsManagerPropertySourceNotFoundException;
29+
import org.springframework.core.env.CompositePropertySource;
2730
import org.springframework.core.env.PropertySource;
2831
import org.springframework.mock.env.MockEnvironment;
2932

3033
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3135
import static org.mockito.ArgumentMatchers.any;
3236
import static org.mockito.Mockito.mock;
3337
import static org.mockito.Mockito.when;
@@ -37,6 +41,7 @@
3741
*
3842
* @author Anthony Foulfoin
3943
* @author Matej Nedic
44+
* @author Maciej Walkowiak.
4045
*/
4146
class AwsSecretsManagerPropertySourceLocatorTest {
4247

@@ -74,7 +79,7 @@ void locate_nameNotSpecifiedInConstructor_returnsPropertySourceWithDefaultName()
7479
}
7580

7681
@Test
77-
public void contextExpectedToHave2Elements() {
82+
void contextExpectedToHave2Elements() {
7883
AwsSecretsManagerProperties properties = new AwsSecretsManagerPropertiesBuilder()
7984
.withDefaultContext("application").withName("application").build();
8085

@@ -91,7 +96,7 @@ public void contextExpectedToHave2Elements() {
9196
}
9297

9398
@Test
94-
public void contextExpectedToHave4Elements() {
99+
void contextExpectedToHave4Elements() {
95100
AwsSecretsManagerProperties properties = new AwsSecretsManagerPropertiesBuilder()
96101
.withDefaultContext("application").withName("messaging-service").build();
97102

@@ -108,7 +113,7 @@ public void contextExpectedToHave4Elements() {
108113
}
109114

110115
@Test
111-
public void contextSpecificOrderExpected() {
116+
void contextSpecificOrderExpected() {
112117
AwsSecretsManagerProperties properties = new AwsSecretsManagerPropertiesBuilder()
113118
.withDefaultContext("application").withName("messaging-service").build();
114119

@@ -127,7 +132,34 @@ public void contextSpecificOrderExpected() {
127132
assertThat(contextToBeTested.get(1)).isEqualTo("/secret/messaging-service");
128133
assertThat(contextToBeTested.get(2)).isEqualTo("/secret/application_test");
129134
assertThat(contextToBeTested.get(3)).isEqualTo("/secret/application");
135+
}
136+
137+
@Test
138+
void whenFailFastIsTrueAndSecretDoesNotExistThrowsException() {
139+
AwsSecretsManagerProperties properties = new AwsSecretsManagerProperties();
140+
properties.setFailFast(true);
141+
142+
when(smClient.getSecretValue(any(GetSecretValueRequest.class))).thenThrow(ResourceNotFoundException.class);
143+
144+
AwsSecretsManagerPropertySourceLocator locator = new AwsSecretsManagerPropertySourceLocator(smClient,
145+
properties);
146+
assertThatThrownBy(() -> locator.locate(env))
147+
.isInstanceOf(AwsSecretsManagerPropertySourceNotFoundException.class);
148+
}
149+
150+
@Test
151+
void whenFailFastIsFalseAndSecretDoesNotExistReturnsEmptyPropertySource() {
152+
AwsSecretsManagerProperties properties = new AwsSecretsManagerProperties();
153+
properties.setFailFast(false);
154+
155+
when(smClient.getSecretValue(any(GetSecretValueRequest.class))).thenThrow(ResourceNotFoundException.class);
156+
157+
AwsSecretsManagerPropertySourceLocator locator = new AwsSecretsManagerPropertySourceLocator(smClient,
158+
properties);
159+
160+
CompositePropertySource result = (CompositePropertySource) locator.locate(env);
130161

162+
assertThat(result.getPropertySources()).isEmpty();
131163
}
132164

133165
private final static class AwsSecretsManagerPropertiesBuilder {

spring-cloud-aws-secrets-manager-config/src/test/java/org/springframework/cloud/aws/secretsmanager/AwsSecretsManagerPropertySourceTest.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,28 @@
1919
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
2020
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
2121
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
22+
import com.amazonaws.services.secretsmanager.model.ResourceNotFoundException;
2223
import org.junit.jupiter.api.Test;
2324

2425
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2527
import static org.mockito.ArgumentMatchers.any;
2628
import static org.mockito.Mockito.mock;
2729
import static org.mockito.Mockito.when;
2830

2931
class AwsSecretsManagerPropertySourceTest {
3032

31-
private AWSSecretsManager smClient = mock(AWSSecretsManager.class);
33+
private AWSSecretsManager client = mock(AWSSecretsManager.class);
3234

3335
private AwsSecretsManagerPropertySource propertySource = new AwsSecretsManagerPropertySource("/config/myservice",
34-
smClient);
36+
client);
3537

3638
@Test
3739
void shouldParseSecretValue() {
3840
GetSecretValueResult secretValueResult = new GetSecretValueResult();
3941
secretValueResult.setSecretString("{\"key1\": \"value1\", \"key2\": \"value2\"}");
4042

41-
when(smClient.getSecretValue(any(GetSecretValueRequest.class))).thenReturn(secretValueResult);
43+
when(client.getSecretValue(any(GetSecretValueRequest.class))).thenReturn(secretValueResult);
4244

4345
propertySource.init();
4446

@@ -47,4 +49,12 @@ void shouldParseSecretValue() {
4749
assertThat(propertySource.getProperty("key2")).isEqualTo("value2");
4850
}
4951

52+
@Test
53+
void throwsExceptionWhenSecretNotFound() {
54+
when(client.getSecretValue(any(GetSecretValueRequest.class)))
55+
.thenThrow(new ResourceNotFoundException("secret not found"));
56+
57+
assertThatThrownBy(() -> propertySource.init()).isInstanceOf(ResourceNotFoundException.class);
58+
}
59+
5060
}

spring-cloud-starter-aws-secrets-manager-config/src/main/java/org/springframework/cloud/aws/autoconfigure/secretsmanager/AwsSecretsManagerConfigDataLoader.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
import org.springframework.cloud.aws.secretsmanager.AwsSecretsManagerPropertySource;
2828

2929
/**
30+
* Loads config data from AWS Secret Manager.
31+
*
3032
* @author Eddú Meléndez
33+
* @author Maciej Walkowiak
3134
* @since 2.3.0
3235
*/
3336
public class AwsSecretsManagerConfigDataLoader implements ConfigDataLoader<AwsSecretsManagerConfigDataResource> {
@@ -38,7 +41,12 @@ public ConfigData load(ConfigDataLoaderContext context, AwsSecretsManagerConfigD
3841
AWSSecretsManager ssm = context.getBootstrapContext().get(AWSSecretsManager.class);
3942
AwsSecretsManagerPropertySource propertySource = resource.getPropertySources()
4043
.createPropertySource(resource.getContext(), resource.isOptional(), ssm);
41-
return new ConfigData(Collections.singletonList(propertySource));
44+
if (propertySource != null) {
45+
return new ConfigData(Collections.singletonList(propertySource));
46+
}
47+
else {
48+
return null;
49+
}
4250
}
4351
catch (Exception e) {
4452
throw new ConfigDataResourceNotFoundException(resource, e);

0 commit comments

Comments
 (0)