What is Vault ?

HashiCorp Vault is a popular open-source tool for securely managing secrets, such as passwords, tokens, and certificates, in modern cloud and container environments.

Vault provides a centralized way to store and manage secrets, which can be accessed programmatically through a variety of APIs or via a web interface. It also provides access control mechanisms to ensure that only authorized users and applications can access specific secrets.

Vault uses a variety of encryption techniques to ensure that secrets are stored securely, including encryption at rest and in transit. It also supports various authentication methods, such as LDAP, Kerberos, and OAuth, to authenticate users and applications.

Some common use cases for Vault include managing database credentials, API keys, SSH keys, and TLS certificates. By using Vault, organizations can improve their security posture by ensuring that secrets are stored securely and accessed only by authorized users and applications.

  1. Centralized Management: Store all secrets securely in one place.
  2. Encryption: Protect secrets with encryption.
  3. Dynamic Secrets: Generate credentials on-demand.
  4. Access Control: Define detailed access policies.
  5. Revocation and Expiration: Revoke and expire secrets as needed.
  6. Encryption Service: Encrypt/decrypt data with Vault keys.
  7. Audit and Compliance: Monitor secret usage.
  8. High Availability: Ensure access during failures.
  9. Integration: Seamlessly integrate with various tools.
  10. Community Support: Benefit from active community and updates.

Installation with Docker

Dev mode

Take care, data are stored only in :memory: when vault is run in dev mode !

docker-compose.yaml
version: "3.3"
services:
vault:
command: vault server -dev
image: hashicorp/vault:1.13.3 # <- change the tag for a newer one
ports:
- 8200:8200
environment:
- VAULT_DEV_ROOT_TOKEN_ID=alex
- VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200
- VAULT_ADDR=http://0.0.0.0:8200

Production mode

Here, the data are stored in a volume !

docker-compose.yaml
version: "3.3"
volumes:
volume_vault:
services:
vault:
command: vault server -config=/tmp/config.hcl
image: hashicorp/vault:1.13.2
ports:
- 8200:8200
cap_add:
- IPC_LOCK
environment:
- VAULT_ADDR=http://0.0.0.0:8200
volumes:
- ./vault.hcl:/tmp/config.hcl:ro
- volume_vault:/vault/
healthcheck:
test: ["CMD-SHELL", 'vault status | grep "Seal.*false"']
interval: 5s
timeout: 5s
retries: 20

vault.hcl
ui = true
storage "file" {
path = "/vault/data"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
disable_mlock = true

Functionnalities of Hashicorps Vault

Secrets engines

Vault provides various secrets engines, which are plugins that enable Vault to interact with different types of secrets and securely store them. Some of the most commonly used secrets engines include:

  1. KV (Key-Value) Engine : The KV engine is a fundamental secrets engine that allows you to store arbitrary key-value secrets.

  2. PKI (Public Key Infrastructure) Engine : The PKI engine is used to manage X.509 certificates and private keys.

  3. SSH Engine : With the SSH engine, Vault can dynamically generate SSH key pairs and sign SSH keys for secure access to servers.

  4. TOTP (Time-Based One-Time Password) Engine : The TOTP engine generates time-based one-time passwords, often used for two-factor authentication (2FA) in applications.

  5. Kubernetes Engine : The Kubernetes engine allows Vault to issue short-lived client certificates to Kubernetes pods.

And many more! The availability of multiple secrets engines gives you the flexibility to securely store different types of secrets according to your specific requirements.

Access

Managing authentication methods and controlling access to Vault is essential for maintaining a secure environment. Vault offers several authentication methods to verify users’ identities:

  1. AppRole : AppRole is designed for applications and services to authenticate with Vault to access secrets. It provides a way for applications to authenticate without exposing sensitive credentials.

  2. Tokens : Tokens are a core authentication method in Vault. They are issued to users upon successful login and act as temporary access credentials to interact with the Vault.

  3. Username & Password : Vault supports traditional username and password-based authentication for users who need direct access to Vault.

  4. JWT (JSON Web Tokens) : With JWT authentication, users can authenticate using JSON Web Tokens, which are commonly used in web applications and APIs.

By configuring the appropriate authentication methods, you can ensure that only authorized users and applications can access sensitive data stored in Vault.

Policies

Vault policies define the access control rules and permissions for different paths within Vault. These policies are written in HashiCorp Configuration Language (HCL) and allow fine-grained control over what actions users and applications can perform. Here’s an example of a Vault policy:

# List, create, update, and delete key/value secrets
path "secret/app-1"
{
capabilities = ["create", "read", "update", "delete", "list"]
}
path "secret/app-2"
{
capabilities = ["read", "list"]
}

In the above example, the policy grants full CRUD (Create, Read, Update, Delete) access to the path secret/app-1, while allowing only read and list access to secret/app-2. By crafting well-defined policies, you can ensure that users and applications have the appropriate level of access to secrets within Vault.

Usage

You can connect on port 8200 with your browser.

  • If you are in dev mode, use the VAULT_DEV_ROOT_TOKEN_ID to sign in.
  • If you are in production mode, look at the logs in the vault container.

You have multiple ways to interact with Vault, Vault UI, Vault CLI and Vault REST API.

Vault CLI

Let’s use Vault CLI:

Terminal window
# PUT OUR TOKEN TO BEGIN TO INTERACT
export VAULT_TOKEN="TOKEN" # Or add flag -mount in your next commands
# SET SECRETS
vault kv put secret/petclinic MYSQL_USER=petclinicUSER MYSQL_PASSWORD=petclinicPASSWORD
# GET SECRETS
vault kv get -field=MYSQL_USER secret/petclinic
vault kv get -field=MYSQL_PASSWORD secret/petclinic
# DELETE SECRETS
vault kv delete secret/test

You can define new authentication methods:

Terminal window
vault auth enable approle

You can define policies. It is specific permissions on secrets (["create", "read", "update", "patch", "delete", "list"])

Terminal window
echo 'path "secret/data/petclinic" {
capabilities = ["read", "list"]
}' > policy.hcl
vault policy write petclinic-policy ./policy.hcl
vault token create -policy petclinic-policy

Take care, don’t forget to add /data.

You can find information about policy here : https://developer.hashicorp.com/vault/tutorials/policies/policies. You can create an admin policy to avoid using root token.

REST API

You can use REST API to manipulate secrets. This would be a request:

Terminal window
GET http://localhost:8200/v1/secret/data/petclinic HTTP/1.1
X-Vault-Token: <TOKEN>
# -----
curl -H "X-Vault-Token: $VAULT_TOKEN" $VAULT_URL

Example : Manage an AppRole with REST API

Terminal window
# First, we define our Token here
export VAULT_TOKEN="alex"
# ENABLE APP ROLE ; only the first time :)
curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data '{"type": "approle"}' \
http://localhost:8200/v1/sys/auth/approle
# Then, we need to create our application
curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data '{
"policies": "admin",
"token_num_uses": 10,
"token_ttl": "20m",
"token_max_ttl": "30m",
"secret_id_ttl": 0,
"secret_id_num_uses": 0
}' \
http://localhost:8200/v1/auth/approle/role/ansible-app
# We fetch the "login" of the application
curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
http://localhost:8200/v1/auth/approle/role/ansible-app/role-id
# ROLE_ID
# 76ec19c8-7998-d306-ab0c-50ef2adf7309
# And we need to fetch the "password" of the application
# Note : we can have multiple passwords for one application
# We can say like : "We can use a password only 10 times", or : "This password is valid only for 20 minutes".
curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
http://localhost:8200/v1/auth/approle/role/ansible-app/secret-id
# SECRET_ID
# 190ca1d5-9200-d18e-a2dd-1615fc97ea31
# Then, we log in with the 2 parameters
curl \
--request POST \
--data '{"role_id":"76ec19c8-7998-d306-ab0c-50ef2adf7309","secret_id":"190ca1d5-9200-d18e-a2dd-1615fc97ea31"}' \
http://localhost:8200/v1/auth/approle/login
Terminal window
# Then, we can fetch a secret with this command :
curl -H "X-Vault-Token: hvs.CAESIAT7aV3eV3jAKUxQy7vQb1rF9RpAPUUmMPYUlY5ZxD5LGh4KHGh2cy5pUGt1ZjZ6WlFkR1lXZ1R5cWxPeVZZM1I" http://localhost:8200/v1/secret/data/servers

Full vault example with docker, setup and usage

In this example, we want to use Vault with this project : https://github.com/spring-projects/spring-petclinic

Setup all the services with docker-compose

To begin, let’s create our services:

  • petclinic is our application where we will pass database credential secrets
  • mysql is our database where petclinic will store its data
  • vault will be the chest where the database secrets will be stored

Note: The petclinic service will need some environment variables. We will see how to set them up shortly!

docker-compose.yaml
version: "3.7"
volumes:
volume_mysql:
volume_vault:
services:
petclinic:
image: petclinic
build:
context: .
dockerfile: ./Dockerfile_petclinic
ports:
- 8080:8080
environment:
- MODE=VAULT
- VAULT_TOKEN=<TOKEN_WITH_POLICIES>
- VAULT_URL=http://vault:8200/v1/secret/data/petclinic
# restart: always
healthcheck:
test:
["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"]
interval: 5s
timeout: 5s
retries: 20
depends_on:
mysql:
condition: service_healthy
vault:
condition: service_healthy
mysql:
image: mysql:8.0
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=passwordRoot
- MYSQL_ALLOW_EMPTY_PASSWORD=true
- MYSQL_USER=petclinicUSER
- MYSQL_PASSWORD=petclinicPASSWORD
- MYSQL_DATABASE=petclinic
healthcheck:
test:
[
"CMD-SHELL",
"mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD",
]
interval: 5s
timeout: 5s
retries: 20
# volumes:
# - ./volume_mysql:/var/lib/mysql
vault:
command: vault server -config=/tmp/config.hcl
image: hashicorp/vault:1.13.2
ports:
- 8200:8200
cap_add:
- IPC_LOCK
environment:
- VAULT_ADDR=http://0.0.0.0:8200
volumes:
- ./vault.hcl:/tmp/config.hcl:ro
- volume_vault:/vault/
healthcheck:
test: ["CMD-SHELL", 'vault status | grep "Seal.*false"']
interval: 5s
timeout: 5s
retries: 20

Setup vault

Token approach

The petclinic tokens can be obtained with these commands, for example:

Terminal window
export VAULT_TOKEN="ROOT_TOKEN"
# Store secrets of our application
vault kv put secret/petclinic MYSQL_USER=petclinicUSER MYSQL_PASS=petclinicPASSWORD MYSQL_URL=jdbc:mysql://mysql/petclinic DB_TYPE=mysql
# Retrieve specific secrets en ensure it works
vault kv get -field=MYSQL_USER secret/petclinic
vault kv get -field=MYSQL_PASS secret/petclinic
# Create a policy
echo 'path "secret/data/petclinic" {
capabilities = ["read", "list"]
}' > policy.hcl
## Write the policy to vault
vault policy write petclinic-policy ./policy.hcl
# Create a Token with the Policy
vault token create -policy petclinic-policy

Approle approach

Instead of getting directly tokens, we can get them from approle:

Terminal window
# https://developer.hashicorp.com/vault/tutorials/auth-methods/approle#prerequisites
# Enable approle authentication method
vault auth enable approle
# Create a policy
vault policy write app-1234 -<<EOF
# Read-only permission on secrets stored at 'secret/data/mysql/webapp'
path "secret/data/app-1234" {
capabilities = [ "read", "list" ]
}
EOF
# Create an approle
vault write auth/approle/role/app-1234 token_policies="app-1234" \
token_ttl=1h token_max_ttl=4h
# Read role information
vault read auth/approle/role/app-1234
vault read auth/approle/role/app-1234/role-id # Get role_id
# Generate secret id
vault write -force auth/approle/role/app-1234/secret-id # Get secret_id
# Login using approle
vault write auth/approle/login role_id="0aeaff2d-9882-520a-3d89-afcce4c80bcb" \
secret_id="b4e8b0b9-7e1c-3f28-d351-db48f01e10fb"
# Set and use the vault token
export APP_TOKEN="hvs.CAESIBH-bjqyfQnnayKwWVJ6WfiTfU1Cari7_gUgAsdd70JlGh4KHGh2cy4wZUE5ZDdIb2xaeXdtN0RkVFFYdkVYbkQ"
VAULT_TOKEN=$APP_TOKEN vault kv get secret/app-1234

Now, we can generate a new vault token with correct policies.

Setup the petclinic application

The Dockerfile for the PetClinic application looks like this:

Dockerfile
FROM eclipse-temurin:17-jdk-alpine AS build
WORKDIR /app/
COPY ./petclinic ./
RUN ./mvnw package
FROM eclipse-temurin:17-jre-alpine AS prod
WORKDIR /opt/spring-petclinic/
RUN apk add jq curl bash
COPY --from=build /app/target/spring-petclinic-3.0.0-SNAPSHOT.jar ./lib/application.jar
COPY ./entrypoint_petclinic.bash ./bin/entrypoint.bash
COPY --from=build /app/src/main/resources/application-template.properties ./etc/
RUN chmod +x ./bin/entrypoint.bash
# We will retrieve the database secrets in this script
ENTRYPOINT [ "./bin/entrypoint.bash" ]

We will have to pass the vault secret to the petclinic application. To achieve this, we will call a script (entrypoint.bash) when the container starts up. This script will fetch the secrets and write them in a file named application-mysql.properties. Here is an example: application-mysql.properties.

We can rename this file to application-template.properties and adjust it like this to make it generic:

application-template.properties
# Database
# database init, supports mysql too
database=mysql
spring.datasource.url={{MYSQL_URL}}
spring.datasource.username={{MYSQL_USER}}
spring.datasource.password={{MYSQL_PASS}}
# SQL is written to be idempotent so this is safe
spring.sql.init.mode=always

Note : the bash script bellow will replace the variables who are inside {{ }}

#!/bin/bash
case "$MODE" in
CREDENTIALS)
echo "TODO"
exit 1
;;
VAULT)
echo "VAULT ENABLE"
RESPONSE=$(curl -H "X-Vault-Token: $VAULT_TOKEN" $VAULT_URL)
SECRET=$(echo $RESPONSE | jq -r '.data.data')
# export MYSQL_URL=$(echo "$RESPONSE" | grep -oP '(?<="MYSQL_URL":")[^"]*')
# export MYSQL_USER=$(echo "$RESPONSE" | grep -oP '(?<="MYSQL_USER":")[^"]*')
# export MYSQL_PASS=$(echo "$RESPONSE" | grep -oP '(?<="MYSQL_PASS":")[^"]*')
# export MYSQL_URL=$(echo "$RESPONSE" | jq -r '.data.data.MYSQL_URL')
# export MYSQL_USER=$(echo "$RESPONSE" | jq -r '.data.data.MYSQL_USER')
# export MYSQL_PASS=$(echo "$RESPONSE" | jq -r '.data.data.MYSQL_PASS')
DB_TYPE=$(echo "$RESPONSE" | jq -r '.data.data.DB_TYPE')
TEMPLATE_PROPERTIES_FILE='./etc/application-template.properties'
PROPERTIES_FILE="./etc/application-$(echo "$DB_TYPE").properties"
cp $TEMPLATE_PROPERTIES_FILE $PROPERTIES_FILE
grep '{{.*}}' "$PROPERTIES_FILE" |
while IFS= read -r line; do
placeholder=$(echo "$line" | awk -F= '{print $2}' | tr -d '{}')
value=$(echo "$SECRET" | jq -r ".\"$placeholder\"")
sed -i "s|{{${placeholder}}}|${value}|g" "$PROPERTIES_FILE"
done
;;
esac
java -jar ./lib/application.jar --spring.profiles.active=$DB_TYPE --spring.config.location=./etc/

Here, all the VAULT secret variables are replaced when the container starts up. We can verify it with the command docker-compose up.

🎉 TADA, the application is working ! 🎉


Recommended articles