Skip to content
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
9 changes: 9 additions & 0 deletions docs/src/main/asciidoc/secrets-manager.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ Spring Boot 2.4 changed the ways files are loaded which means profile loading ha
Link to Spring Boot reference docs about file specific loading: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config-files-profile-specific[Reference Docs]
Read more on the official https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4[Spring Blog]

==== Using plain text secrets

If a `SecretString` is a plain text, use secret name to retrieve its value.
For example, we have JDBC url as plain text secret type with name `/secrets/jdbc-url`. Secret value can be retrieved by referencing secret name:
[source,properties]
----
spring.datasource.url=${jdbc-url}
----

==== IAM Permissions
Following IAM permissions are required by Spring Cloud AWS:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
import com.amazonaws.services.secretsmanager.model.ResourceNotFoundException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -87,8 +88,8 @@ public Object getProperty(String name) {
}

private void readSecretValue(GetSecretValueRequest secretValueRequest) {
GetSecretValueResult secretValueResult = source.getSecretValue(secretValueRequest);
try {
GetSecretValueResult secretValueResult = source.getSecretValue(secretValueRequest);
Map<String, Object> secretMap = jsonMapper.readValue(secretValueResult.getSecretString(),
new TypeReference<Map<String, Object>>() {
});
Expand All @@ -99,6 +100,14 @@ private void readSecretValue(GetSecretValueRequest secretValueRequest) {
properties.put(propertyKey, secretEntry.getValue());
}
}
catch (JsonParseException e) {
// If the secret is not a JSON string, then it is a simple "plain text" string
String[] parts = secretValueResult.getName().split("/");
String secretName = parts[parts.length - 1];
LOG.debug("Populating property retrieved from AWS Secrets Manager: " + secretName);
String propertyKey = prefix != null ? prefix + secretName : secretName;
properties.put(propertyKey, secretValueResult.getSecretString());
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
import com.amazonaws.services.secretsmanager.model.ResourceNotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
Expand Down Expand Up @@ -87,13 +86,31 @@ void throwsExceptionWhenSecretNotFound() {
}

@Test
void throwsExceptionWhenSecretIsNotJsonSecret() {
void shouldProcessTextSecretWhenSecretIsNotJsonSecret() {
GetSecretValueResult secretValueResult = new GetSecretValueResult()
.withSecretString("plain text secret string, not json secret");
when(client.getSecretValue(any(GetSecretValueRequest.class))).thenReturn(secretValueResult);
.withSecretString("plain text secret string, not json secret").withName("/config/myservice");
when(client.getSecretValue(secretValueRequestArgumentCaptor.capture())).thenReturn(secretValueResult);

propertySource.init();

assertThat(secretValueRequestArgumentCaptor.getValue().getSecretId()).isEqualTo("/config/myservice");
assertThat(propertySource.getPropertyNames()).containsExactly("myservice");
assertThat(propertySource.getProperty("myservice")).isEqualTo("plain text secret string, not json secret");
}

@Test
void shouldProcessTextSecretWithPrefix() {
propertySource = new AwsSecretsManagerPropertySource("/config/myservice2?prefix=service2.", client);
GetSecretValueResult secretValueResult = new GetSecretValueResult()
.withSecretString("plain text secret string, not json secret").withName("/config/myservice2");
when(client.getSecretValue(secretValueRequestArgumentCaptor.capture())).thenReturn(secretValueResult);

assertThatThrownBy(() -> propertySource.init()).isInstanceOf(RuntimeException.class)
.extracting(Throwable::getCause).isInstanceOf(JsonProcessingException.class);
propertySource.init();

assertThat(secretValueRequestArgumentCaptor.getValue().getSecretId()).isEqualTo("/config/myservice2");
assertThat(propertySource.getPropertyNames()).containsExactly("service2.myservice2");
assertThat(propertySource.getProperty("service2.myservice2"))
.isEqualTo("plain text secret string, not json secret");
}

}