Using AWS Secrets Manager to manage secrets in Spring Boot Applications

Introduction

AWS Secrets Manager is a managed service for storing secrets such as database credentials, API keys and tokens. Application retrieves a secret stored in the Secrets Manager via AWS SDK or HTTP requests. Access is controlled via AWS IAM and resource based policies. The AWS Secrets Manager also provides native support for password rotations for AWS RDS databases.

A Spring Boot application typically needs to store a number of secrets. For example, username and password to its database, credentials for calling an external API, password to access a message broker such as ActiveMQ etc.. Typically such secrets are stored as system environment variables or files in the machine in which the Spring Boot application is deployed.

AWS Secrets Manager provides a few potential advantages than the above:

  1. Easy to manage – this is particular so if you have a large number of applications e.g. in a Microservices architecture. AWS Secrets Manager provides a central repository where you can setup, control and monitor access to the secrets used by those many applications.
  2. Automatic secret rotation – this provides extra security with minimum disruption to the applications. No need to update environment variables or property files. This feature is implemented using AWS Lambda function and is available for AWS RDS, Redshift and DocumentDB. Support for other services (AWS or on-premise) can be made by modifying the codes in the sample Lambda functions.
  3. Compliance – HIPAA, PCI-DSS, ISO 27001 etc.

Solution Overview

This post demonstrates how you can modify a Spring Boot application to retrieve a database username and password stored in the AWS Secrets Manager. The diagram below provides an overview of the solution which comprises of the following 4 steps:

  1. A user creates a new secret in AWS Secrets Manager
  2. A Spring Boot application uses the secret name to access the database username and password stored in AWS Secrets Manager
  3. The Spring Boot application connects to the RDS database using the credential it retrieves from AWS Secrets Manager using AWS Java SDK
  4. AWS Secrets Manager performs automatic rotations of the secret (e.g. every 90 days) by invoking a Lambda function

blog-aws-secrets-manager

1. Create a New Secret

Use the AWS Console to create and store a new secret in AWS Secrets Manager. As we are storing a secret for RDS, select secret type “Credentials for RDS Database” radio button and specify the user name and password as well as select the RDS database

blog-aws-secrets-manager-console

Click Next and enter the secret name and optional tags

blog-aws-secrets-manager-console-2

Click Next and Enable automatic rotation. Select rotation interval from the drop down, e.g. 90 days and click “Create a new Lambda to perform rotation” radio button (default) and input name for the new Lambda function in the input text box below

blog-aws-secrets-manager-console-3

Click Next, review and click Store to create the new secret.

2. Modify the Spring Boot Application

Now we need to update Spring Boot to retrieve the database password from AWS Secrets Manager. To do that, we add a ApplicationListener to listen to ApplicationPreparedEvent. This allows us to intercept Spring Boot before it starts running but after the ApplicationContext and Environment are fully ready so that we can insert our codes to update the database username and password properties (e.g. spring.datasource.username, spring.datasource.password)

public class DatabasePropertiesListener implements ApplicationListener<ApplicationPreparedEvent> {

	private final static String SPRING_DATASOURCE_USERNAME = "spring.datasource.username";
	private final static String SPRING_DATASOURCE_PASSWORD = "spring.datasource.password";

	private ObjectMapper mapper = new ObjectMapper();

	@Override
	public void onApplicationEvent(ApplicationPreparedEvent event) {
		// Get username and password from AWS Secret Manager using secret name
		String secretJson = getSecret();
		String dbUser = getString(secretJson, "username");
		String dbPassword = getString(secretJson, "password");

		ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
		Properties props = new Properties();
		props.put(SPRING_DATASOURCE_USERNAME, dbUser);
		props.put(SPRING_DATASOURCE_PASSWORD, dbPassword);
		environment.getPropertySources().addFirst(new PropertiesPropertySource("aws.secret.manager", props));
		
	}

        // sample codes from AWS
	private String getSecret() {
		String secretName = "test-secret";
		String region = "ap-southeast-2";
                ...
		// Decrypts secret using the associated KMS CMK.
		// Depending on whether the secret is a string or binary, one of these fields
		// will be populated.
		if (getSecretValueResult.getSecretString() != null) {
			secret = getSecretValueResult.getSecretString();
		} else {
			decodedBinarySecret = new String(
					Base64.getDecoder().decode(getSecretValueResult.getSecretBinary()).array());
		}
		return secret != null ? secret : decodedBinarySecret;
        }

	private String getString(String json, String path) {
        try {
			JsonNode root = mapper.readTree(json);
			return root.path(path).asText();
		} catch (IOException e) {
			logger.error("Can't get {} from json {}", path, json, e);
			return null;
		}
	}
}

Note:

The codes in the method getSecret() are sample codes provided by AWS and can be copied from AWS Console for Secrets Manager. It uses the AWS SDK for Java so you have to include the following dependencies, e.g. in Maven:

<dependency>
     <groupId>com.amazonaws</groupId>
     <artifactId>aws-java-sdk-secretsmanager</artifactId>
     <version>1.11.549</version>
</dependency>

We will also need to add the new application listener to the spring.factories file in the folder src/main/resources/META-INF:

# file src/main/resources/META-INF/spring.factories
org.springframework.context.ApplicationListener=<your package name>.DatabasePropertiesListener

As usual with AWS SDK, the Spring Boot application needs to have the IAM permission to access the Secrets Manager.

3. Access the RDS Database

Nothing to do here. The Spring Boot application retrieves the database username and password at start up in step 2 above and use them to connect to the database for queries and updates as usual.

4. Automatic password rotation

Since we enable automatic rotation in Step 1, every 90 days, AWS Secrets Manager will trigger the underlying Lambda function to update the password in the RDS database. The Spring Boot application will also need to be restarted so it can retrieve the new password from the AWS Secrets Manager. This process can be automated e.g. by updating the Lambda function to trigger refresh of Spring Boot application (context).

Summary

That’s it! We can now store, monitor and securely access secrets in AWS Secrets Manager from Spring Boot. We also enable automatic rotation for added security and compliance.