Quarkus - Working with HashiCorp Vault

    • mounting a map of properties stored into theVault kv secret engine as an Eclipse MicroProfile config source

    • fetch credentials from Vault when configuring an Agroal datasource

    • access the Vault kv secret engine programmatically

    Under the hood, the Quarkus Vault extension takes care of authentication when negotiating a client Vault token plusany transparent token or lease renewals according to ttl and max-ttl.

    To complete this guide, you need:

    • roughly 20 minutes

    • an IDE

    • JDK 1.8+ installed with configured appropriately

    • Apache Maven 3.5.3+

    • Docker installed

    Starting Vault

    Let’s start Vault in development mode:

    You can check that vault is running with:

    1. docker logs dev-vault

    You should see:

    1. ==> Vault server configuration:
    2. Api Address: http://0.0.0.0:8200
    3. Cgo: disabled
    4. Cluster Address: https://0.0.0.0:8201
    5. Listener 1: tcp (addr: "0.0.0.0:8200", cluster address: "0.0.0.0:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
    6. Log Level: info
    7. Mlock: supported: true, enabled: false
    8. Storage: inmem
    9. Version: Vault v1.2.2
    10. WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
    11. and starts unsealed with a single unseal key. The root token is already
    12. authenticated to the CLI, so you can immediately begin using Vault.
    13. You may need to set the following environment variable:
    14. $ export VAULT_ADDR='http://0.0.0.0:8200'
    15. The unseal key and root token are displayed below in case you want to
    16. seal/unseal the Vault or re-authenticate.
    17. Unseal Key: 0lZ2/vzpa92pH8gersSn2h9b5tmzd4m5sqIdMC/4PDs=
    18. Root Token: s.5VUS8pte13RqekCB2fmMT3u2
    19. Development mode should NOT be used in production installations!
    20. ==> Vault server started! Log data will stream in below:

    In development mode, Vault gets configured with several options that makes it convenient:

    • Vault is already initialized with one key share (whereas in normal mode this has to be done explicitly and thenumber of key shares is 5 by default)

    • the unseal key and the root token are displayed in the logs (please write down the root token, we will need itin the following step)

    • Vault is unsealed

    • in-memory storage

    • TLS is disabled

    • a kv secret engine v2 is mounted at secret/

    First open a shell inside the vault container:

    1. docker exec -it dev-vault sh

    Set the VAULT_TOKEN with the value that was printed in the logs:

    1. export VAULT_TOKEN=s.5VUS8pte13RqekCB2fmMT3u2

    You can check Vault’s status using the CLI command vault status:

    1. Key Value
    2. --- -----
    3. Seal Type shamir
    4. Initialized true
    5. Sealed false
    6. Total Shares 1
    7. Threshold 1
    8. Version 1.2.2
    9. Cluster Name vault-cluster-b07e80d8
    10. Cluster ID 55bd74b6-eaaf-3862-f7ce-3473ab86c57f
    11. HA Enabled false

    For simplicity reasons, we are going to use a kv secret engine version 1 (which is the default) mounted at pathsecret instead of the pre-configured kv version 2 available in dev mode. So let’s disable the current kv engine,and recreate a new one:

    1. # this will disable the current kv version 2 secret engine mounted at path 'secret'
    2. vault secrets disable secret
    3. vault secrets enable -path=secret kv

    Now let’s add a secret configuration for our application:

    1. vault kv put secret/myapps/vault-quickstart/config a-private-key=123456

    We have defined a path secret/myapps/vault-quickstart in Vault that we need to give access to from the Quarkus application.

    Create a policy that gives read access to secret/myapps/vault-quickstart and subpaths:

    1. cat <<EOF | vault policy write vault-quickstart-policy -
    2. path "secret/myapps/vault-quickstart/*" {
    3. capabilities = ["read"]
    4. }
    5. EOF

    And finally, let’s enable the userpass auth secret engine, and create user bob with access to the vault-quickstart-policy:

    1. vault auth enable userpass
    2. vault write auth/userpass/users/bob password=sinclair policies=vault-quickstart-policy

    To check that the configuration is correct, try logging in:

    You should see:

    1. Success! You are now authenticated. The token information displayed below
    2. is already stored in the token helper. You do NOT need to run "vault login"
    3. again. Future Vault requests will automatically use this token.
    4. Key Value
    5. --- -----
    6. token s.s93BVzJPzBiIGuYJHBTkG8Uw
    7. token_accessor OKNipTAgxWbxsrjixASNiwdY
    8. token_duration 768h
    9. token_renewable true
    10. token_policies ["default" "vault-quickstart-policy"]
    11. identity_policies []
    12. policies ["default" "vault-quickstart-policy"]
    13. token_meta_username bob

    Now set VAULT_TOKEN to the above (instead of the root token), and try reading the vault-quickstart secret config:

    1. export VAULT_TOKEN=s.s93BVzJPzBiIGuYJHBTkG8Uw
    2. vault kv get secret/myapps/vault-quickstart/config

    You should see:

    1. ======== Data ========
    2. Key Value
    3. --- -----
    4. a-private-key 123456
    1. mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR1:create \
    2. -DprojectGroupId=org.acme \
    3. -DprojectArtifactId=vault-quickstart \
    4. -DclassName="org.acme.quickstart.GreetingResource" \
    5. -Dpath="/hello" \
    6. -Dextensions="vault,hibernate-orm,jdbc-postgresql"
    7. cd vault-quickstart

    Configure access to vault from the application.properties:

    1. # vault url
    2. quarkus.vault.url=http://localhost:8200
    3. # vault authentication
    4. quarkus.vault.authentication.userpass.username=bob
    5. quarkus.vault.authentication.userpass.password=sinclair
    6. # path within the kv secret engine where is located the vault-quickstart secret configuration
    7. quarkus.vault.secret-config-kv-path=myapps/vault-quickstart/config

    This should mount whatever keys are stored in secret/myapps/vault-quickstart as MicroProfile config properties.

    Let’s verify that by adding a new endpoint in GreetingResource:

    1. @Path("/hello")
    2. public class GreetingResource {
    3. @ConfigProperty(name = "a-private-key")
    4. String privateKey;
    5. ...
    6. @GET
    7. @Path("/private-key")
    8. @Produces(MediaType.TEXT_PLAIN)
    9. public String privateKey() {
    10. return privateKey;
    11. }
    12. }

    Now compile the application and run it:

    1. ./mvnw clean install
    2. java -jar target/vault-quickstart-1.0-SNAPSHOT-runner.jar

    Finally test the new endpoint:

    1. curl http://localhost:8080/hello/private-key

    You should see:

    1. 123456

    Fetching credentials from Vault with Hibernate ORM

    Start a PostgreSQL database:

    Create a simple service:

    1. @ApplicationScoped
    2. public class SantaClausService {
    3. EntityManager em;
    4. @Transactional
    5. public List<Gift> getGifts() {
    6. return (List<Gift>) em.createQuery("select g from Gift g").getResultList();
    7. }
    8. }
    1. @Entity
    2. public class Gift {
    3. private Long id;
    4. private String name;
    5. @Id
    6. @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="giftSeq")
    7. public Long getId() {
    8. return id;
    9. }
    10. public void setId(Long id) {
    11. this.id = id;
    12. }
    13. public String getName() {
    14. return name;
    15. }
    16. public void setName(String name) {
    17. this.name = name;
    18. }
    19. }

    Finally, add a new endpoint in GreetingResource:

    1. @Inject
    2. SantaClausService santaClausService;
    3. @GET
    4. @Produces(MediaType.TEXT_PLAIN)
    5. public int geGiftCount() {
    6. return santaClausService.getGifts().size();
    7. }

    Create a new path in Vault where the database password will be added:

    1. # set the root token again
    2. export VAULT_TOKEN={root-token}
    3. vault kv put secret/myapps/vault-quickstart/db password=connor

    Since we allowed read access on secret/myapps/vault-quickstart/ subpaths in the policy, there is nothing elsewe have to do to allow the application to read it.

    When fetching credentials from Vault that are intended to be used by the Agroal connection pool, we needfirst to create a named Vault credentials provider in the application.properties:

    1. quarkus.vault.credentials-provider.mydatabase.kv-path=myapps/vault-quickstart/db

    This defines a credentials provider mydatabase that will fetch the password from key passwordat path myapps/vault-quickstart/db.

    The credentials provider can now be used in the datasource configuration, in place of the passwordproperty:

    1. # configure your datasource
    2. quarkus.datasource.url = jdbc:postgresql://localhost:5432/mydatabase
    3. quarkus.datasource.driver = org.postgresql.Driver
    4. quarkus.datasource.username = sarah
    5. #quarkus.datasource.password = connor
    6. quarkus.datasource.credentials-provider = mydatabase
    7. # drop and create the database at startup (use `update` to only update the schema)
    8. quarkus.hibernate-orm.database.generation=drop-and-create

    Restart the application after rebuilding it, and test it with the new endpoint:

    1. curl http://localhost:8080/hello/gift-count

    You should see:

    1. 0

    Sometimes secrets are retrieved from an arbitrary path that is known only at runtime through an applicationspecific property, or a method argument for instance.In that case it is possible to inject a Quarkus VaultKVSecretEngine, and retrieve secrets programmatically.

    For instance, in GreetingResource, add:

    1. @Inject
    2. VaultKVSecretEngine kvSecretEngine;
    3. ...
    4. @GET
    5. @Path("/secrets/{vault-path}")
    6. @Produces(MediaType.TEXT_PLAIN)
    7. public String getSecrets(@PathParam("vault-path") String vaultPath) {
    8. return kvSecretEngine.readSecret("myapps/vault-quickstart/" + vaultPath).toString();
    9. }

    Restart the application after rebuilding it, and test it with the new endpoint:

    You should see:

    1. {password=connor}

    TLS

    In production mode, TLS should be activated between the Quarkus application and Vault to prevent man-in-the-middle attacks.

    There are several ways to configure the Quarkus application:

    • using property quarkus.vault.tls.ca-cert, which should refer to a pem encoded file.

    If quarkus.vault.tls.ca-cert is not set and the Quarkus application is using the Kubernetes authentication,TLS will be active and use the CA certificate bundle located in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt.If you want to disable this behavior (for instance when using a trust store), you need to set it explicitly using:quarkus.vault.tls.use-kubernetes-ca-cert=false.

    The last relevant property is quarkus.vault.tls.skip-verify, which allows to communicate with Vault using TLS,but without checking the certificate authenticity. This may be convenient in development, but is stronglydiscouraged in production as it is not more secure than talking to Vault in plain HTTP.

    As a general matter, you should consider reading the extensive Vault documentationand apply best practices.

    The features exposed today through the Vault extension covers only a small fraction of what the product is capable of.Still, it addresses already some of the most common microservices scenarii (e.g. sensitive configuration and databasecredentials), which goes a long way towards creating secured applications.

    Configuration Reference