RDS Database Authentication with Spring Boot: Part 1, Secrets Manager

by
Tags: , ,
Category:

How do you configure database connections for your Spring Boot application? Here’s one way:

spring.datasource.url=jdbc:postgresql://db.example.com:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=my_super_secret_password

The problem with this approach is that “my_super_secret_password” isn’t: it’s sitting in a file in plain text, available to anyone who can either check out your source code or copy your deployment bundle. And even if the only people who can see these files already know the password, a second and bigger problem is that it’s part of the build artifact. If you ever need to change the password, you need to rebuild the application.

A better approach is to use environment variables, using the ${VARIABLE_NAME} syntax.

spring.datasource.url=jdbc:postgresql://${PGHOST}:${PGPORT}/${PGDATABASE}
spring.datasource.username=${PGUSER}
spring.datasource.password=${PGPASSWORD}

While better, there is still a problem: you’ll need to restart your application whenever you change the database password. And regularly changing your database password is a good way to reduce the “blast radius” of leaked credentials (for more on this, listen to this SERadio episode). And while it’s not that onerous to change the password and restart a single application, it gets quite complex when you have a micro-service architecture with possibly dozens of database-using deployments.

To make password changes work smoothly, your application needs to lookup the current password at the time it makes a connection. Unfortunately, if you’re using Spring Boot that’s a challenge, because of the amount of behind-the-scenes work that Spring Boot does to let you write a program that “just works” with minimal configuration.

This post, and my next, look at two solutions to the problem. Today I’ll be using a JDBC driver library from AWSLabs that retrieves connection information from Secrets Manager. Tomorrow I’ll dive a little deeper, and create a custom Postgres DataSource implementation that uses IAM to generate limited-use passwords.

I’m starting with Secrets Manager because credential rotation is one of its core features, and because the AWSLabs driver can easily be used with Spring Boot. Once you’ve added the driver’s JAR to your build, you simply change your Spring Boot configuration to reference it:

spring.datasource.url=jdbc-secretsmanager:postgresql://${PGHOST}:${PGPORT}/${PGDATABASE}
spring.datasource.driver-class-name=com.amazonaws.secretsmanager.sql.AWSSecretsManagerPostgreSQLDriver
spring.datasource.username=${SECRET_NAME}

Moving through this line-by line, the first thing that jumps out is the datasource URL, which uses the scheme jdbc-secretsmanager rather than jdbc. This is actually a rather controversial decision by the AWSLabs team, as some frameworks do not recognize non-standard schemes. Fortunately, Spring Boot isn’t one of them, and that’s important because the datasource URL performs an additional function for Spring Boot: it signifies that the application is connecting to an external database server, and should use a connection pool. This means that you don’t need any application-level configuration to set up the connection pool (unlike tomorrow’s example).

Next up is the driver class name. The AWSLabs library does not implement the JDBC service provider mechanism, so you must explicitly configure the class (in this case, their version of the standard Postgres driver).

The last piece of configuration is username, which does not in fact contain a username. Instead, it holds the name (or ARN) of a secret, and that secret is assumed to have both username and password fields in its JSON blob.

You’ll note that I’ve continued to use the environment variables PGHOST, PGPORT, and PGDATABASE. Depending on whether or not you’ve attached your secret to a database instance, this information might be available from the secret. But the AWSLabs driver doesn’t look for it there, so you have to provide it explicitly. As I said above, environment variables are far preferable to hardcoded values because you don’t have to rebuild your application to change them.

So, that’s approach number one. If you’d like to try it out, I’ve created an example program, including a CloudFormation template that will create your database instance and corresponding master-user secret.