Using vault-java-driver to fetch Hashicorp Vault secrets
Hashicorp Vault addresses secret sprawl by centrally storing, accessing, rotating and distributing secrets. In this article, I will demonstrate the use of the community-supported Java vault-java-driver library to fetch a secret using the Vault static secrets engine. While Hashicorp Vault does provide a quick start Java example using Spring Boot, there is no comparable quick start example for vault-java-driver for those Java development teams that do not use Spring.
NOTE: This example assumes the Vault instance has already been 'stood up' with a static secret populated (i.e., see the first four steps in the Start Vault section listed https://github.com/hashicorp-education/learn-vault-external-kubernetes)
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
2. Modify the pom.xml to include the vault-java-driver dependency
NOTE: See APPENDIX for the working pom.xml where I've added the maven shade plugin to package the artifacts into an uber-jar with the necessary dependencies
<dependency>
<groupId>io.github.jopenlibs</groupId>
<artifactId>vault-java-driver</artifactId>
<version>6.2.0</version>
</dependency>
3. Do a quick test to make sure the project compiles and you can execute the classic 'Hello World' using the uber-jar
mvn clean package
java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
Hello World!
4. Modify the app.java to import the relevant jopenlib Vault classes and then fetch the key value pair for the previously populated secret (see APPENDIX for the working App.java)
final VaultConfig config = new VaultConfig().address("http://127.0.0.1:8200").token("root").build();
vault = Vault.create(config);
final String value = vault.logical()
.read("secret/devwebapp/config")
.getData().get("password");
5. Rerun maven and execute the main method to fetch the secret via the vault-java-driver Java community SDK
mvn clean package
java -cp target/m-app-1.0-SNAPSHOT.jar com.mycompany.app.App
May 01, 2024 1:49:11 PM io.github.jopenlibs.vault.VaultImpl <init>
INFO: Constructing a Vault instance with no provided Engine version, defaulting to version 2.
Secret Value:salsa
6. Success!
IMPORTANT: This example can be made more 'production ready' by using production grade SSL certificates for Vault per this snippet
Path path = Paths.get("/var/mycertchain.pem");
File file = path.toFile();
try {
final VaultConfig config = new VaultConfig().address("https://127.0.0.1:8200").sslConfig(new SslConfig().verify(true).pemFile(file).build()).token("root").build();
and removing the above hard-coded Vault root token with say App Role ID and App Secret ID authentication (replace <role id> & <secretid> in the below snippet with the proper AppRole Role ID and Secret ID)
final AuthResponse response = vault.auth.loginByAppRole ("approle", "<role id>", "<secret id">))
APPENDIX
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<name>my-app</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.jopenlibs</groupId>
<artifactId>vault-java-driver</artifactId>
<version>6.2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.mycompany.app.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
package com.mycompany.app;
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.SslConfig;
import io.github.jopenlibs.vault.response.AuthResponse;
import io.github.jopenlibs.vault.response.LogicalResponse;
import java.util.HashMap;
import java.util.Map;
public class App
{
private static Vault vault;
public static void main( String[] args )
{
try {
final VaultConfig config = new VaultConfig().address("http://127.0.0.1:8200").token("root").build();
vault = Vault.create(config);
final String value = vault.logical()
.read("secret/devwebapp/config")
.getData().get("password");
System.out.println("Secret Value:" + value);
}
catch (VaultException e)
{
throw new IllegalStateException("Unable to initialize Vault", e);
}
}
}
This is well written and presented. Good job on scoping the code to the specific task at hand and not bloating it with extra fluff. The readability of the code is clear. Including try, catch, and throw, along with comments where everyone needs to modify for their app, make this a great starter to pick up and use.