Using AWS Parameter Store and Secret Manager with Spring Boot

 

Overview

This page covers the use of AWS System Manager (SSM) Parameter Store and AWS Secrets Manager within a Spring Boot application.

It will show application level properties as well as configuring RDS credentials. Data gets retrieved at both startup and runtime.

SSM Parameter Store

Parameter Store provides secure and hierarchical storage for configuration data management and secrets management. You can store items such as license codes, API details, database strings etc. as parameter values. You can store values as plain text or encrypted data.

AWS System Parameter Store Console Access

AWS Secrets Manager

AWS Secrets Manager is an AWS service that makes it easier for you to manage secrets. Secrets can be database credentials, authentication credentials, others passwords, third-party API keys, and even just some arbitrary text.

These can be managed centrally by using the Secrets Manager console, the Secrets Manager command-line interface (CLI), or the Secrets Manager API and SDKs.

Secrets Manager also provides functionality needed for certain security requirements such as Automatic Rotation or integration with AWS Key Manager Service (KMS) for organizational provided encryption keys.

Parameter Store vs Secrets Manager

There is considerable overlap between Parameter Store and Secrets Manager in terms of functionality. There are these main differences.

  • Secrets Manager was specifically created for storing confidential information such as like database credentials, credential keys, API keys etc. Parameter Store was created for wider use cases.
  • Parameter Store is free for standard parameters where Secrets Manager always costs on a per secret per month basis.
  • Secrets Manager provides key rotation facilities that Parameter Store does not.
  • Secrets Manager has automatic integration with RDS database credentials in Spring Boot that Parameter Store does not.

Recommendation: Use Secrets Manager to store confidential secrets like database credentials, API keys, OAuth tokens where key rotation is a consideration. Use Parameter Store for all else to store other application settings, environmental config data, license codes, etc.

Parameter Store Application Properties

Include the org.springframework.cloud:spring-cloud-starter-aws-parameter-store-config dependency in the project

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws-parameter-store-config</artifactId>
<version>${spring-cloud.secret.version}</version>
</dependency>

In the environment bootstrap set the following parameters

aws:
paramstore:
name: backend
prefix: /poc
enabled: true
defaultContext: application
profileSeparator: _

and sample of corresponding secrets within Parameter Store

A property called application.title can then be referenced within the application.

That means that for a service called backend the module by default would find and use these parameters:

These are the configurable items with defaults.

Secrets Manager Application Properties

Spring Cloud provides support for centralized configuration, which can be read and made available as a regular Spring PropertySource when the application is started. The Secrets Manager Configuration allows you to use this mechanism with the Secrets Manager.

To enable this, add a dependency on the org.springframework.cloud:spring-cloud-starter-aws-secrets-manager-config starter module and support will be activated.

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-aws-secrets-manager-config</artifactId>
<version>${spring-cloud.secret.version}</version>
</dependency>

All configuration parameters are retrieved from a common path prefix, which defaults to /secret. From there shared parameters are retrieved from a path that defaults to application and service-specific parameters use a path that defaults to the configured spring.application.name. You can use both dots and forward slashes to specify the names of configuration keys. Names of activated profiles will be appended to the path using a separator that defaults to an underscore.

An example of a configuration is

aws:
secretsmanager:
name: backend
prefix: /secret
defaultContext: application
profileSeparator: _
failFast: true

and corresponding configured secrets

That means that for a service called my-service the module by default would find and use these parameters:

You can configure the following settings in a Spring Cloud bootstrap.properties or bootstrap.yml file (note that relaxed property binding is applied, so you don’t have to use this exact syntax):

Database Properties

AWS Secrets Manager is also completely integrated with RDS.

This is activated by adding this dependency.

<dependency>
<groupId>com.amazonaws.secretsmanager</groupId>
<artifactId>aws-secretsmanager-jdbc</artifactId>
<version>${aws-jdbc.version}</version>
</dependency>

When selecting the secret choose the “Credentials for Amazon RDS database” option. The relevant information should be entered and then respective DB can be selected.

The environment specific property file would be configured as follows:

spring:
datasource:
url: jdbc-secretsmanager:postgresql://xxxxxx.eu-west-1.rds.amazonaws.com:5432/postgres
username: xxxxxx
driver-class-name: com.amazonaws.secretsmanager.sql.AWSSecretsManagerPostgreSQLDriver

Notes:

  • Use of jdbc-secretsmanager:postgresql as prefix to database URL.
  • Driver class name is com.amazonaws.secretsmanager.sql.AWSSecretsManagerPostgreSQLDriverwhich is available when adding above described dependency.
  • username is defined as the secret name xxxxxx

Note that many examples show using the supplied code from AWS. When using the above method and added dependency it also provides automatic support for password rotation. Password rotation can be selected when configuring the secret and also option of generating a lambda for handling the rotation.

IAM Permissions

For access IAM permissions can be setup.The required Spring Cloud AWS permission is: ssm:GetParameters

Sample IAM policy granting access to Parameter Store:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:DescribeParameters"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ssm:GetParameters"
],
"Resource": "*"
}
]
}

secretsmanager:GetSecretValue

Sample IAM policy granting access to Secret Manager:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": "<arn of specific secret manager resource>"
},
{
"Effect": "Allow",
"Action": "secretsmanager:ListSecrets",
"Resource": "*"
}
]
}

Retrieve Parameter Store value on the fly

Up to this point, we are able to read the values from Parameter Store when app starts, but what if some one updates a new value and we need the latest value from Parameter Store?

Add the following dependencies

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.java.sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ssm</artifactId>
<version>${aws.ssm.version}</version>
</dependency>

the following code would show how to read values from parameter store on the fly

public static String getParaValue(String paraName) {

try {
SsmClient ssmClient = SsmClient.builder()
.region(region)
.credentialsProvider(ProfileCredentialsProvider.create()) -----note
.build();
log.debug("Parameter path" + paraName);
GetParameterRequest parameterRequest = GetParameterRequest.builder()
.name(paraName)
.build();
log.debug("Parameter request" + parameterRequest);
GetParameterResponse parameterResponse = ssmClient
.getParameter(parameterRequest);
return parameterResponse.parameter().value();
} catch (SsmException e) {
log.error(e.getMessage());
return null;
}
}

Noticed that line with note, it would look for the aws credential from .aws file. If the app is run with an appropriate role, remove this line.

JDK 17 Note

When running using the required dependencies there is the issue where application received an IllegalAccessException Exception.

This is prevented by adding this in the IDE under VM options or on the command line:

--add-opens=java.base/java.lang=ALL-UNNAMED

Comments