HashiCorp Vault - Technological watch
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.
- Centralized Management: Store all secrets securely in one place.
- Encryption: Protect secrets with encryption.
- Dynamic Secrets: Generate credentials on-demand.
- Access Control: Define detailed access policies.
- Revocation and Expiration: Revoke and expire secrets as needed.
- Encryption Service: Encrypt/decrypt data with Vault keys.
- Audit and Compliance: Monitor secret usage.
- High Availability: Ensure access during failures.
- Integration: Seamlessly integrate with various tools.
- 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 !
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 !
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
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:
-
KV (Key-Value) Engine : The KV engine is a fundamental secrets engine that allows you to store arbitrary key-value secrets.
-
PKI (Public Key Infrastructure) Engine : The PKI engine is used to manage X.509 certificates and private keys.
-
SSH Engine : With the SSH engine, Vault can dynamically generate SSH key pairs and sign SSH keys for secure access to servers.
-
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.
-
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:
-
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.
-
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.
-
Username & Password : Vault supports traditional username and password-based authentication for users who need direct access to Vault.
-
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 secretspath "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:
# PUT OUR TOKEN TO BEGIN TO INTERACTexport VAULT_TOKEN="TOKEN" # Or add flag -mount in your next commands
# SET SECRETSvault kv put secret/petclinic MYSQL_USER=petclinicUSER MYSQL_PASSWORD=petclinicPASSWORD
# GET SECRETSvault kv get -field=MYSQL_USER secret/petclinicvault kv get -field=MYSQL_PASSWORD secret/petclinic
# DELETE SECRETSvault kv delete secret/test
You can define new authentication methods:
vault auth enable approle
You can define policies. It is specific permissions on secrets (["create", "read", "update", "patch", "delete", "list"]
)
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:
GET http://localhost:8200/v1/secret/data/petclinic HTTP/1.1X-Vault-Token: <TOKEN>
# -----
curl -H "X-Vault-Token: $VAULT_TOKEN" $VAULT_URL
Example : Manage an AppRole with REST API
# First, we define our Token hereexport 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 applicationcurl \ --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 applicationcurl \ --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 parameterscurl \ --request POST \ --data '{"role_id":"76ec19c8-7998-d306-ab0c-50ef2adf7309","secret_id":"190ca1d5-9200-d18e-a2dd-1615fc97ea31"}' \ http://localhost:8200/v1/auth/approle/login
# 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!
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:
export VAULT_TOKEN="ROOT_TOKEN"
# Store secrets of our applicationvault 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 worksvault kv get -field=MYSQL_USER secret/petclinicvault kv get -field=MYSQL_PASS secret/petclinic
# Create a policyecho 'path "secret/data/petclinic" { capabilities = ["read", "list"]}' > policy.hcl
## Write the policy to vaultvault policy write petclinic-policy ./policy.hcl
# Create a Token with the Policyvault token create -policy petclinic-policy
Approle approach
Instead of getting directly tokens, we can get them from approle:
# https://developer.hashicorp.com/vault/tutorials/auth-methods/approle#prerequisites
# Enable approle authentication methodvault auth enable approle
# Create a policyvault 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 approlevault write auth/approle/role/app-1234 token_policies="app-1234" \ token_ttl=1h token_max_ttl=4h
# Read role informationvault read auth/approle/role/app-1234vault read auth/approle/role/app-1234/role-id # Get role_id
# Generate secret idvault write -force auth/approle/role/app-1234/secret-id # Get secret_id
# Login using approlevault write auth/approle/login role_id="0aeaff2d-9882-520a-3d89-afcce4c80bcb" \ secret_id="b4e8b0b9-7e1c-3f28-d351-db48f01e10fb"
# Set and use the vault tokenexport 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:
FROM eclipse-temurin:17-jdk-alpine AS buildWORKDIR /app/COPY ./petclinic ./RUN ./mvnw package
FROM eclipse-temurin:17-jre-alpine AS prodWORKDIR /opt/spring-petclinic/RUN apk add jq curl bash
COPY --from=build /app/target/spring-petclinic-3.0.0-SNAPSHOT.jar ./lib/application.jarCOPY ./entrypoint_petclinic.bash ./bin/entrypoint.bashCOPY --from=build /app/src/main/resources/application-template.properties ./etc/RUN chmod +x ./bin/entrypoint.bash
# We will retrieve the database secrets in this scriptENTRYPOINT [ "./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:
# Database# database init, supports mysql toodatabase=mysqlspring.datasource.url={{MYSQL_URL}}spring.datasource.username={{MYSQL_USER}}spring.datasource.password={{MYSQL_PASS}}# SQL is written to be idempotent so this is safespring.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 ! 🎉
Practice code with the "Quick Sort" algorithm
Enhance your coding skills by learning how the Quick Sort algorithm works!
Atomic design - Technological watch
Learn what is the atomic design in less than 5 minutes !
Redis - Technological watch
Learn what is Redis in less than 5 minutes !
The SOLID/STUPID principles
Learn what are the SOLID and STUPID principles with examples
Create a Docker Swarm playground
Let's create Docker Swarm playground on your local machine
Svelte - Technological watch
Learn what is Svelte in less than 5 minutes !
Create an Ansible playground with Docker
Let's create an Ansible playground with Docker
OpenAPI / Swagger - Technological watch
Learn what is OpenAPI / Swagger in less than 5 minutes !