Helidon Integration with Vault

Tomas Langer
Helidon
Published in
5 min readMay 21, 2021

HashiCorp Cloud Platform (HCP) Vault is a commonly used Vault in many microservices. The APIs are REST-based and Helidon implements them using reactive client.

Helidon integration with HCP Vault is still experimental and not intended for production use. APIs and features have not yet been fully tested and are subject to change.

Vault integration supports the following features:

  • Secret Engines
    Key/Value version 2, Key/Value version 1, Cubbyhole, PKI, Transit, Database
  • Authentication Methods
    Token, Kubernetes (k8s), AppRole
  • Other Sys Operations and Configurations
    Enabling and disabling secret engines and authentication methods

Each of these features is implemented as a separate module, with the Vault class binding them together. In Helidon MP, with injection, this binding is done automatically, and you can simply inject your favorite secret engine.

In addition to these features, Vault can also be authenticated as follows:

  • Token authentication — token is configured when connecting to Vault
  • AppRole authentication — AppRole ID and secret ID are configured, integration exchanges these for a temporary token that is used to connect to Vault
  • K8s authentication — the k8s JWT token is discovered on current node and used to obtain a temporary token that is used to connect to Vault

Extensibility

New secret engines and authentication methods can be implemented quite easily, as the integration is based on service providers (using ServiceLoader). This gives you the option to add new secret engines and/or authentication methods without adding multiple methods to the Vault class.

See the following SPIs:

io.helidon.integrations.vault.spi.AuthMethodProvider
io.helidon.integrations.vault.spi.SecretsEngineProvider
io.helidon.integrations.vault.spi.SysProvider
io.helidon.integrations.vault.spi.VaultAuth
io.helidon.integrations.vault.spi.InjectionProvider

Maven Coordinates for Vault Modules

The following is a list of Maven coordinates of all Vault modules available in Helidon 2.3.0:

<dependency>
<groupId>io.helidon.integrations.vault</groupId>
<artifactId>helidon-integrations-vault</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.auths</groupId>
<artifactId>helidon-integrations-vault-auths-token</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.auths</groupId>
<artifactId>helidon-integrations-vault-auths-approle</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.auths</groupId>
<artifactId>helidon-integrations-vault-auths-k8s</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.secrets</groupId>
<artifactId>helidon-integrations-vault-secrets-kv1</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.secrets</groupId>
<artifactId>helidon-integrations-vault-secrets-kv2</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.secrets</groupId>
<artifactId>helidon-integrations-vault-secrets-cubbyhole</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.secrets</groupId>
<artifactId>helidon-integrations-vault-secrets-transit</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.secrets</groupId>
<artifactId>helidon-integrations-vault-secrets-database</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.vault.sys</groupId>
<artifactId>helidon-integrations-vault-sys</artifactId>
</dependency>

Local Testing

Vault is available as a docker image, so to test locally you can simply run the following:

docker run -e VAULT_DEV_ROOT_TOKEN_ID=my-token -d --name=vault -p8200:8200 vault

This will create a Vault docker image, run it in the background and open it on localhost:8200 with a custom root token my-token, using name vault. This is of course only suitable for local testing, as the root token has too many rights, but it can be easily used with the examples below.

Vault Integration with Helidon MP

To use Vault integration in MP, the following module must be added, as it enables injection of all features:

<dependency>
<groupId>io.helidon.integrations.vault</groupId>
<artifactId>helidon-integrations-vault-cdi</artifactId>
</dependency>

Configuration to connect to Vault (using token and AppRole authentication):

vault.default.address=http://localhost:8200
vault.default.token=my-token
vault.approle.address=http://localhost:8200
vault.approle.auth.app-role.role-id=app-role-id
vault.approle.auth.app-role.secret-id=app-role-secret-id

The following classes can be injected into any CDI bean (if appropriate module is on the classpath):

  • Kv2Secrets — Key/Value Version 2 Secrets (versioned secrets, default)
  • Kv1Secrets — Key/Value Version 1 Secrets (unversioned secrets, legacy)
  • CubbyholeSecrets — Cubbyhole secrets (token bound secrets)
  • DbSecrets — Database secrets (for generating temporary DB credentials)
  • PkiSecrets — PKI secrets (for generating keys and X.509 certificates)
  • TransitSecrets — Transit operations (encryption, signatures, HMAC)
  • AppRoleAuth — AppRole authentication method (management operations)
  • K8sAuth — Kubernetes authentication method (management operations)
  • TokenAuth — Token authentication method (management operations)
  • Sys — System operations (management of Vault — enabling/disabling secret engines and authentication methods)
  • *Rx — reactive counterparts to the classes defined above, usually not recommended in CDI, as it is a blocking environment

The following example shows usage of Vault to encrypt a secret using the default Vault configuration (in a JAX-RS resource):

private final TransitSecrets secrets;

@Inject
TransitResource(TransitSecrets secrets) {
this.secrets = secrets;
}
//...
@Path("/encrypt/{secret: .*}")
@GET
public String encrypt(@PathParam("secret") String secret) {
return secrets.encrypt(Encrypt.Request.builder()
.encryptionKeyName(ENCRYPTION_KEY)
.data(Base64Value.create(secret)))
.encrypted()
.cipherText();
}

And the following example shows usage of Vault to get a secret using the AppRole Vault configuration:

private final Kv2Secrets secrets;

@Inject
Kv2Resource(@VaultName("app-role") Kv2Secrets secrets) {
this.secrets = secrets;
}
@GET
@Path("/secrets/{path: .*}")
public Response getSecret(@PathParam("path") String path) {

Optional<Kv2Secret> secret = secrets.get(path);

if (secret.isPresent()) {
Kv2Secret kv2Secret = secret.get();
return Response.ok()
.entity("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString())
.build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}

Advanced Configuration

Secret engines can be enabled on a custom path by injecting an engine with @VaultPath("path") to get the correct path. Usage of @VaultName is shown in example code above, and is used to distinguish configurations.

Vault Integration with Helidon SE

In Reactive code, we use the APIs with suffix Rx and we have to obtain them “by hand”, as we do not use injection in Helidon SE.

Minimal configuration to connect to Vault:

vault:
token: "my-token"
address: "http://localhost:8200"

Code to set up Vault and obtain a specific secret engine:

Vault vault = Vault.builder()
.config(config.get("vault"))
.build();
Kv2SecretsRx secrets = vault.secrets(Kv2SecretsRx.ENGINE);

Similar code can be used for any secret engine available:

  • Kv2SecretsRx — Key/Value Version 2 Secrets (versioned secrets, default)
  • Kv1SecretsRx — Key/Value Version 1 Secrets (non-versioned secrets, legacy)
  • CubbyholeSecretsRx — Cubbyhole secrets (token bound secrets)
  • DbSecretsRx — Database secrets (for generating temporary DB credentials)
  • PkiSecretsRx — PKI secrets (for generating keys and X.509 certificates)
  • TransitSecretsRx — Transit operations (encryption, signatures, HMAC)

Code to obtain a specific authentication method:

K8sAuthRx auth = vault.auth(K8sAuthRx.AUTH_METHOD)

Similar code can be used for any authentication method available:

  • AppRoleAuthRx — AppRole authentication method (management operations)
  • K8sAuthRx — Kubernetes authentication method (management operations)
  • TokenAuthRx — Token authentication method (management operations)

And finally code to get the Sys operations of Vault:

SysRx sys = vault.sys(SysRx.API);

Vault with WebServer

Now we can build an example with WebServer using end-to-end reactive behavior. We will use ServerRequest (req) and ServerResponse (res) in addition to Vault APIs.

The following example shows server methods to obtain a secret and to encrypt/decrypt data:

private void getSecret(ServerRequest req, ServerResponse res) {
String path = req.path().param("path");

secrets.get(path)
.thenAccept(secret -> {
if (secret.isPresent()) {
// using toString so we do not need to depend on JSON-B
Kv2Secret kv2Secret = secret.get();
res.send("Version " + kv2Secret.metadata().version() + ", secret: " + kv2Secret.values().toString());
} else {
res.status(Http.Status.NOT_FOUND_404);
res.send();
}
})
.exceptionally(res::send);
}
private void encryptSecret(ServerRequest req, ServerResponse res) {
String secret = req.path().param("text");

secrets.encrypt(Encrypt.Request.builder()
.encryptionKeyName(ENCRYPTION_KEY)
.data(Base64Value.create(secret)))
.forSingle(response -> res.send(response.encrypted().cipherText()))
.exceptionally(res::send);
}
private void decryptSecret(ServerRequest req, ServerResponse res) {
String encrypted = req.path().param("text");

secrets.decrypt(Decrypt.Request.builder()
.encryptionKeyName(ENCRYPTION_KEY)
.cipherText(encrypted))
.forSingle(response -> res.send(String.valueOf(response.decrypted().toDecodedString())))
.exceptionally(res::send);
}

Learn More

For more information about Helidon, see our main project site at https://helidon.io and our GitHub repository at https://github.com/oracle/helidon.

--

--