Reload HashiCorp Vault secrets in Spring applications
You can refactor your application to use a Vault client library to inject secrets from Vault into your code. Many client libraries support authentication to and retrieval of secrets from Vault. Writing code that directly interfaces with Vault allows you to control the injection and handling of new secrets without deploying a separate process to monitor changes in Vault. How do you reload the application to use new credentials and minimize downtime?
This tutorial shows you how to configure a Spring application to reload static and dynamic secrets from Vault without restarting the application. You will configure an application to retrieve a database username and password from Vault's key-value secrets engine on a scheduled interval and reconnect to the database with new credentials. Then, you will add annotations to help the application reload dynamic database credentials from Vault's database secrets engine. Finally, if your application cannot use Spring annotations or runs on GraalVM, you use Spring Vault to wait for a secret renewal and update connection properties.
Prerequisites
Set up tutorial
Clone the repository.
Or download the repository.
The repository contains supporting content for this Spring application tutorial.
Change directories to the
reload/
directory.In your terminal, use Docker Compose to create a PostgreSQL database for the application, PostgreSQL configuration container to set up the schema, Vault server in dev mode, and Vault configuration container to set up the key-value and database secrets engines.
Reload static secrets
You manually update static secrets stored in Vault's key-value secrets engine. Rather than manually reload the application, you can include code to reload new versions of static secrets on a scheduled interval. This workflow requires the use of Spring Cloud Vault to retrieve Spring application properties from Vault's key-value secrets engine.
Tip
Your application can also subscribe to Vault Enterprise's event notifications if it must immediately reload upon changes to static secrets.
After you start the Vault server, verify that it has the key-value secrets engine enabled at
kv/
and contains the root database username and password atkv/vault-static-secrets
. Spring Cloud Vault retrieves application properties from a Vault path defined by the application, context, or profile. This tutorial stores the root database credentials at thekv/${application_name}
path. The keys for each secret use the format for common application properties defined for database connections in Spring applications.Verify the current working directory is
reload/
.Change directories to the
vault-static-secrets/
directory.Review
pom.xml
to check the application's dependencies. The Spring application in this tutorial uses the Spring Cloud Vault library. It provides client-side support for connecting to Vault in a distributed environment. The application also needs JDBC and PostgreSQL to access the database.pom.xml1 2 3 4 5 6 7 8 9 101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
Open
application.properties
in/src/main/resources
. The application setsspring.cloud.vault.uri
andspring.cloud.vault.token
to the address and root token of the Vault server in dev mode. You can change the Vault authentication to any supported authentication methods based on your organization's Vault setup.application.properties1 2 3 4 5 6 7 8 9 1011121314151617
Continue to review
application.properties
. Instead of writing code to directly interface with Vault and retrieve static secrets, configure Spring Cloud Config Server to use Vault as a backend. The config server automatically reads secrets from Vault paths based on the application name, context, and profile in the secret mount. Override the mount with thespring.cloud.vault.kv.backend
property. When the application starts, Spring Cloud Config will retrieve the properties from Vault and merge them with common properties. This allows the application to authenticate to the database.application.properties1 2 3 4 5 6 7 8 9 1011121314151617
Additional properties like
spring.datasource.url
inapplication.properties
define application access to the database. Finally, create a custom property namedsecrets.refresh-interval-ms
. It configures the scheduled interval in milliseconds for getting new secrets from Vault. The application checks for new credentials every 180000 milliseconds (3 minutes).application.properties1 2 3 4 5 6 7 8 9 1011121314151617
Open
VaultStaticSecretsApplication.java
in/src/main/java/com/hashicorp/vaultstaticsecrets
. The class includes a few annotations, includingEnableScheduling
, which schedules a task in the application.VaultStaticSecretsApplication.java1 2 3 4 5 6 7 8 9 101112131415161718192021222324252627
Other objects defined in
VaultStaticSecretsApplication.java
include theDataSource
defining the database connection. Annotate it as aBean
and add theRefreshScope
annotation.RefreshScope
identifies beans that the application can rebuild, such as database connections or clients to upstream services.VaultStaticSecretsApplication.java1 2 3 4 5 6 7 8 9 101112131415161718192021222324252627
Examine
VaultRefresher.java
in/src/main/java/com/hashicorp/vaultstaticsecrets
. The class includes theComponent
annotation to ensure the context refresh task runs automatically on application startup. Therefresher
method runs a context refresh, which allows the application to shut down existing database connections and start connections with new credentials. Annotate therefresher
method with@Scheduled
and define the task delay using custom configuration properties for the refresh interval. The application schedules a task to refresh context every three minutes.VaultRefresher.java1 2 3 4 5 6 7 8 9 1011121314151617
Review
PaymentsController.java
in/src/main/java/com/hashicorp/vaultstaticsecrets
. The controller handles requests to get a list of payments and create a payment in the database.PaymentsController.java1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728293031323334353637383940414243444546474849505152
In your terminal, build and run the application using the Apache Maven Wrapper included in the repository.
Open another terminal session. Update Vault's key-value secrets engine with a new database password.
Wait three minutes before switching back to the terminal running the application. The logs indicate that the application refreshed a key-value secret after three minutes.
Open another terminal session. Request the application for a list of payments in the database using its API. The database should return an error, as the updated credentials do not have access to the database.
When you make the request, the application uses the new password to connect to the database. However, the new password does not have access. The application throws an error.
Roll back the database password to the previous version.
Check the application refreshes the secret by examining the application's logs.
Open another terminal session. Request the application for a list of payments in the database using its API. The database should return an empty list, as it did not record any payments.
When you make the request, the application uses the previous version of the password to connect to the database and successfully rebuilds the database connection.
Shut down the application run by Maven.
Reload dynamic secrets
Secrets engines in Vault that handle dynamic secrets manage the revocation and creation of credentials based on leases. Leases include a time-to-live (TTL) before Vault revokes the credentials. This tutorial uses the Spring Cloud Vault database backend to retrieve database credentials from Vault and add them to application properties. Each time the Spring Vault library detects a secret expiration event, the application refreshes any objects using the credentials.
Note
The code in this section works with secrets engines that have leases for credentials. If a secrets engine or role does not issue credentials with a lease, Spring Vault does not generate events related to leases.After you start the Vault server, verify you can get a database and username from the PostgreSQL database secrets engine enabled at
database/
with thepayments-app
Vault role. This database secrets engine has a TTL of one minute, with a maximum TTL of two minutes. The application will renew the credentials at least once before the username and password expire after two minutes.Change the current working directory to
reload/
.Change directories to the
vault-dynamic-secrets/
directory.Review
pom.xml
to check the application's dependencies. The Spring application in this tutorial uses the Spring Cloud Vault library. It provides client-side support for connecting to Vault in a distributed environment. To use the Spring Cloud Vault database backend, include thespring-cloud-vault-config-databases
library.pom.xml1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
Open
application.properties
in/src/main/resources
. The application setsspring.cloud.vault.uri
andspring.cloud.vault.token
to the address and root token of the Vault server in dev mode. You can change the Vault authentication to any supported authentication methods based on your organization's Vault setup.application.properties1 2 3 4 5 6 7 8 9 1011121314151617181920212223
The
spring.cloud.vault.database.enabled
andspring.cloud.vault.database.backend
attributes inapplication.properties
configure the Spring Cloud Config database backend to get a username and password from the database secrets engine at thedatabase/
path in Vault. Setspring.cloud.vault.database.role
to thepayments-app
Vault role. This example disables the KV backend, as the application does not need to retrieve the root database username and password. When the application starts, Spring Cloud Config retrieves the database username and password from Vault and injects them into data source properties.application.properties1 2 3 4 5 6 7 8 9 1011121314151617181920212223
Set
spring.cloud.vault.config.lifecycle.min-renewal
andspring.cloud.vault.config.lifecycle.expiry-threshold
to shorter intervals inapplication.properties
. These attributes determine when Spring Cloud Vault renews the lease for dynamic credentials or makes a request to Vault for new credentials. Since the maximum TTL of the database credential expires after two minutes, the configuration defines a shorter expiration threshold than renewal threshold.application.properties1 2 3 4 5 6 7 8 9 1011121314151617181920212223
Open
VaultDynamicSecretsApplication.java
in/src/main/java/com/hashicorp/vaultdynamicsecrets
. The class includes aDataSource
defining the database connection. Annotate it as aBean
and add theRefreshScope
annotation.RefreshScope
identifies beans that the application can rebuild, such as database connections or clients to upstream services.VaultDynamicSecretsApplication.java1 2 3 4 5 6 7 8 9 1011121314151617181920212223242526
Examine
VaultRefresher.java
in/src/main/java/com/hashicorp/vaultdynamicsecrets
. The class includes theComponent
annotation to ensure the application scans it at startup. The constructor includes aSecretLeaseContainer
to listen for events related to leases at the database secrets engine path. Each time the application receives aSecretLeaseExpiredEvent
, it refreshes application context to get new credentials from Vault.VaultRefresher.java1 2 3 4 5 6 7 8 9 101112131415161718192021
Review
PaymentsController.java
in/src/main/java/com/hashicorp/vaultdynamicsecrets
. The controller handles requests to get a list of payments and create a payment in the database.PaymentsController.java1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728293031323334353637383940414243444546474849505152
In your terminal, build and run the application using the Apache Maven Wrapper included in the repository. The application creates a data source based on a database username and password generated by Vault.
Wait for two minutes for the application logs to indicate that it refreshed database credentials.
Open a new terminal session. Request the application for a list of payments in the database using its API. The database should return an empty list, as it did not record any payments.
When you make the request, the application gets a new database username and password from Vault to connect to the database and successfully rebuilds the database connection.
Shut down the application run by Maven.
Reload secrets in GraalVM
GraalVM compiles a Java application
ahead of time into a smaller, more performant, and secure standalone binary by removing
unused code and classes. As a result, it supports dynamic language features like reflection
at build time and not runtime. Application context refreshing uses reflection at runtime,
which means annotating beans with @RefreshScope
and calling a context refresh will
not work in GraalVM. In order to reload a database connection with new secrets, use the
Runtime Hints API
to guide the compiler to include a refreshable data source.
Note
You can apply the patterns in this section to Spring applications that cannot refresh application context.Change the current working directory to
reload/
.Change directories to the
vault-graalvm/
directory.Check you have GraalVM installed.
Review
pom.xml
to check the application's dependencies. The Spring application in this tutorial uses the Spring Cloud Vault library. It provides client-side support for connecting to Vault in a distributed environment. To use the Spring Cloud Vault database backend, include thespring-cloud-vault-config-databases
library. The dependencies include the Maven plugin for GraalVM.pom.xml1 2 3 4 5 6 7 8 9 1011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
Open
application.properties
in/src/main/resources
. In GraalVM, you must disable Spring Cloud refresh with thespring.cloud.refresh.enabled
attribute. The remainingapplication.properties
set up the Vault address and token, disables KV backend and enables the database backend for Spring Cloud Config, and tunes the lease lifecycle renewal and expiration thresholds.application.properties1 2 3 4 5 6 7 8 9 1011121314151617181920212223242526
Open
VaultGraalvmApplication.java
in/src/main/java/com/hashicorp/vaultgraalvm
. The class includes themain
function to start the application.VaultGraalvmApplication.java1234567
Examine
RefreshableDataSourceVaultConfiguration.java
. The constructor uses Spring Vault’sSecretLeaseContainer
to add a lease listener and wait for aSecretLeaseExpiredEvent
and a secret renewal. Your application requests a secret rotation using Spring Vault. If the lease container detects a new lease and a rotated secret, it sets the username and password into the data source properties and refreshes the data source.RefreshableDataSourceVaultConfiguration.java1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728293031323334353637383940414243
The
RefreshableDataSourceVaultConfiguration.java
file in/src/main/java/com/hashicorp/vaultgraalvm
also creates aDataSource
with a Spring AOP proxy that rebuilds the data source on an application event.RefreshableDataSourceVaultConfiguration.java1 2 3 4 5 6 7 8 9 1011121314151617181920212223242526272829303132333435363738
Continue reviewing
RefreshableDataSourceVaultConfiguration.java
. The class defines arefresh
method to publish an application event for refreshing credentials. This refresh event rebuilds the data source. Create a listener for the refresh event.RefreshableDataSourceVaultConfiguration.java1 2 3 4 5 6 7 8 9 1011121314
RefreshableDataSourceVaultConfiguration.java
defines a runtime hint for the refreshable data source. This allows the compiler to recognize the refresh event for the data source. Register the data source proxy and the refresh event listener as a hint. Finally, make sure to importRefreshableDataSourceHints
with theImportRuntimeHints
annotation.RefreshableDataSourceVaultConfiguration.java1 2 3 4 5 6 7 8 9 1011121314151617
Review
PaymentsController.java
. The controller handles requests to get a list of payments and create a payment in the database.PaymentsController.java1 2 3 4 5 6 7 8 9 10111213141516171819202122232425262728293031323334353637383940414243444546474849505152
In your terminal, build and run the application using the
native.sh
script. The script cleans up any previous builds with JVM and rebuilds the application on GraalVM using the Apache Maven Wrapper. Note it will take a few minutes to build. Once it starts, the application creates a data source based on a database username and password generated by Vault.Wait for two minutes for credentials to expire. Then, open a new terminal session. Request the application for a list of payments in the database using its API. The database should return an empty list, as it did not record any payments.
When you make the request, the application gets a new database username and password from Vault to connect to the database and successfully rebuilds the database connection.
Clean up
Stop the application and remove the Vault server and database.
Stop the application running with Maven.
Change the working directory to
reload/
.Remove the Vault server and database with Docker Compose.
Next steps
In this tutorial, you learned how to reload a Spring application with new static secrets on a scheduled interval. Then, you used application context refreshing to reload dynamic secrets for a database based on lease expiration. For applications that run on GraalVM or cannot use context refreshing, you refactored the application to reload dynamic database credentials based on application events.
By configuring your Spring application with code to handle context refreshing, you can reload connections to databases or upstream APIs with minimal downtime in the application. Writing code allows you to control the application’s handling and behavior when it finds a new secret. For applications that you cannot refactor to interface with Vault, you can use Vault agent to check Vault for new secrets and reload the application without adding new code.
For more information, review the following resources:
- Spring Cloud Vault
- Spring Vault, the core library for Spring Cloud Vault
- GraalVM
- Vault Agent