Quarkus - File Vault

Introduction

This project provides Quarkus CredentialsProvider and MicroProfile [ConfigSource](https://quarkus.io/guides/config-extending-support#custom-config-source) which extracts passwords and other sensitive data from Java KeyStore.

Java KeyStore is used as a file-based Vault. Sensitive data can be imported to and securely stored in this Vault as Java SecretKey values. Imported certificates are also supported.

Installation

If you would like to use File Vault as CredentialsProvider then add the io.quarkiverse.file-vault:quarkus-file-vault dependency in your pom.xml:

<dependency>
    <groupId>io.quarkiverse.file-vault</groupId>
    <artifactId>quarkus-file-vault</artifactId>
</dependency>

If you would like to use File Vault as ConfigSource then add the io.quarkiverse.file-vault:quarkus-file-vault-config-source dependency in your pom.xml:

<dependency>
    <groupId>io.quarkiverse.file-vault</groupId>
    <artifactId>quarkus-file-vault-config-source</artifactId>
</dependency>

Getting Started

First a Java Keystore has to be created and the data imported to it using a Java keytool and its -importpass option, for example:

keytool -importpass -alias quarkus_test -keystore dbpasswords.p12 -storepass storepassword -storetype PKCS12

This command creates a keystore dbpasswords.p12 with a secret key whose alias is quarkus_test.

Use File Vault CredentialsProvider

Once you have one or more keystore prepared you can use File Vault as CredentialsProvider with Quarkus extensions which integrate with CredentialsProvider.

For example, here is how you can configure it with Agroal:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkus_test
quarkus.datasource.credentials-provider=quarkus.file.vault.provider.db1

quarkus.file.vault.provider.db1.path=dbpasswords.p12
quarkus.file.vault.provider.db1.secret=storepassword
quarkus.file.vault.provider.db1.alias=quarkus_test

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.sql-load-script=import.sql

In this example quarkus.datasource.credentials-provider refers to FileVaultCredentialsProvider as quarkus.file.vault.provider.db1.

The name format is quarkus.file.vault.provider.<name>, where <name> identifies a specific keystore configuration which in this case is:

quarkus.file.vault.provider.db1.path=dbpasswords.p12
quarkus.file.vault.provider.db1.secret=storepassword
quarkus.file.vault.provider.db1.alias=quarkus_test

You can configure as many keystores as required.

Note setting a keystore alias (quarkus.file.vault.provider.db1.alias=quarkus_test) in the properties is optional. Instead you can pass it like this:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkus_test
quarkus.datasource.credentials-provider=quarkus.file.vault.provider.db1.quarkus_test

quarkus.file.vault.provider.db1.path=dbpasswords.p12
quarkus.file.vault.provider.db1.secret=storepassword

This is way you can refer to the same keystore but use a different alias each time.

FileVaultCredentialsProvider will return the extracted secret key as a password property. It will also use alias value to return a user property. The extensions such as Agroal will accept both properties.

However, you can choose for only a password property be returned with quarkus.file.vault.set-alias-as-user=false. In this case you will need to configure a username with the extension specific property, for example, when working with Agroal you can use quarkus.datasource.username.

Finally, if a keystore alias is not set in application.properties (for example, quarkus.file.vault.provider.db1.alias=quarkus_test) and is not encoded in the credentials provider name (for example, quarkus.datasource.credentials-provider=quarkus.file.vault.provider.db1.quarkus_test) then the provider will return all the keystore entries.

Use FileVaultCredentialsProvider directly

You can access this CredentialsProvider like this from your code:

package io.quarkiverse.filevault.it;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import java.util.Map;
import io.quarkus.credentials.CredentialsProvider;
import io.quarkus.credentials.runtime.CredentialsProviderFinder;
...

CredentialsProvider cp = CredentialsProviderFinder.find("quarkus.file.vault");

// Use a `quarkus_test` alias to get a secret value from the keystore `db1`
// where the alias is set in `application.properties`:

cp.getCredentials("quarkus.file.vault.provider.db1");

// Use a `quarkus_test` alias to get a secret value from the keystore `db1` by passing it directly to the provider:

Map<String, String> props = cp.getCredentials("quarkus.file.vault.provider.db1.quarkus_test");
String user = props.get(CredentialsProvider.USER_PROPERTY_NAME);
String secret = props.get(CredentialsProvider.PASSWORD_PROPERTY_NAME);

// Use a `quarkus_cert` alias to get the encoded `X509Certificate` from the keystore `db1` by passing it directly to the provider:

Map<String, String> props = cp.getCredentials("quarkus.file.vault.provider.db1.quarkus_cert");
String user = props.get(CredentialsProvider.USER_PROPERTY_NAME);
String encodedCert = props.get("certificate");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf
   .generateCertificate(new ByteArrayInputStream(encodedCert.getBytes(StandardCharsets.ISO_8859_1)));

Use File Vault ConfigSource

Once you have a keystore prepared you can use File Vault as ConfigSource.

For example, here is how you can configure it with Agroal:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkus_test
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=${quarkus_test}

quarkus.file.vault-config-source.keystore-path=dbpasswords.p12
quarkus.file.vault-config-source.keystore-secret=storepassword

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.sql-load-script=import.sql

Protect keystore passwords.

You need to specify a keystore password in application.properties for Quarkus File Vault be able to extract secrets from the keystore. However this keystore password is a sensitive value and therefore you should consider how to minimize a risk of leaking it and how to protect it.

One important thing you should be aware of is that leaking this password does not necessarily mean the actual secrets stored in the keystore will also be leaked since an unauthorized person will also need to access the actual keystore file. Restricting an access to the keystore file to a limited number of roles and having Quarkus processes running in one of these roles will make it harder for anyone outside of this group access the keystore. The keystore password can be set as an environment variable and this password should be periodically changed to limit a window during which an attacker can try to get to the keystore.

Use ConfigSource

However if you do need to avoid setting a keystore password in application.properties then you can use a custom MicroProfile ConfigSource, for example.

package io.quarkiverse.filevault.it;

import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

public class KeyStoreConfigSource implements ConfigSource {

    @Override
    public Set<String> getPropertyNames() {
        return Set.of("db1.storepassword");
    }

    @Override
    public String getValue(String propertyName) {
        return "db1.storepassword".equals(propertyName) ? "storepassword" : null;
    }

    @Override
    public String getName() {
        return "file-vault-config-source";
    }

}

add org.eclipse.microprofile.config.spi.ConfigSource service provider entry listing io.quarkiverse.filevault.it.KeyStoreConfigSource to META-INF/services.

Next, refer to the keystore password like this if you use File Vault as CredentialsProvider:

quarkus.file.vault.provider.db1.path=dbpasswords.p12
quarkus.file.vault.provider.db1.secret=${db1.storepassword}

or refer to the keystore password like this if you use File Vault as ConfigSource:

quarkus.file.vault-config-source.keystore-path=dbpasswords.p12
quarkus.file.vault-config-source.keystore-secret=${db1.storepassword}

Please note that in this example, hardcoding a keystore password such as db1.storepassword in KeyStoreConfigSource is only done to simplify the documentation. In real world applications, custom ConfigSource implementations will read it from a DB or other secure storage.

For example, Quarkus Vault extension provides a ConfigSource which can fetch secrets from a HashiCorp Vault.

Mask Keystore Password

If you need to mask a keystore password (quarkus.file.vault.provider.<name>.secret) you will need to build and package the Vault Utils project.

mvn clean install

After that, you will be able to execute the --help command:

$ java -jar target/quarkus-app/quarkus-run.jar --help

Usage: Encrypt Secret Util [-hV] [-e=<encryptionKey>] -p=<keystorePassword>
  -e, --encryption-key=<encryptionKey> (optional) Encryption Key
  -h, --help          Show this help message and exit.
  -p, --keystore-password=<keystorePassword> (mandatory) Keystore password
  -V, --version       Print version information and exit.

The only mandatory parameter is a keystore password: -p, --keystore-password. The encryption key will be auto-generated unless it is provided with: -e, --encryption-key.

Here is how you can mask a keystore password:

$ java -jar target/quarkus-app/quarkus-run.jar -p storedpass -e justsomestringhere

You should see something like this at the output:

#######################################################################################################################################################
Please add the following parameters to application.properties if you use File Vault as CredentialsProvider and replace the <keystore-name>:

quarkus.file.vault.provider.<name>.encryption-key=justsomestringhere
quarkus.file.vault.provider.<name>.secret=4VLLc9bk+WMnQMR3ezJcpw

Please add the following parameters to application.properties if you use File Vault as ConfigSource:

quarkus.file.vault-config-source.encryption-key=justsomestringhere
quarkus.file.vault-config-source.keystore-secret=4VLLc9bk+WMnQMR3ezJcpw
########################################################################################################################################################

Note even though you now have the keystore password storedpass masked as 4VLLc9bk+WMnQMR3ezJcpw, the encryption key which was used to mask it remains in a clear text so it should be protected similarly to how an unmasked keystore password should be, for example, using a ConfigSource or environment variables. As such the main advantage of masking the password is to introduce an extra level of indirection to make it more complex to get to the keystore password itself.

HashiCorp Vault

Quarkus Vault extension can be used as a ConfigSource with Quarkus File Vault to protect the keystore passwords, but it can also act as an alternative to Quarkus File Vault as a CredentialsProvider implementation.

Configuration Reference

File Vault CredentialsProvider

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Determine if the File Vault extension is enabled

boolean

true

Key Store configuration which can include Map path (path to the keystore), secret (keystore password), encryption-key (required if secret is a masked keystore password) and alias (required secret key alias) properties, for example, quarkus.file.vault.provider.db1.path, quarkus.file.vault.provider.db1.secret, quarkus.file.vault.provider.db1.encryption-key, quarkus.file.vault.provider.db1.alias.

Map

Set the alias which is used to extract a secret from the key store as a 'user' property.

boolean

true

File Vault ConfigSource

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

String

String

Encryption key which was used to mask the keystore secret.

String