Hashicorp Vault is an open-source tool for storing and controlling access to secrets, such as tokens, passwords, certificates, encryption keys, and other sensitive data. It can be accessed manually through a UI, programmatically using the CLI, or via the HTTP API [Link].

With Vault, specific actions (e.g., read, create, update, delete) can be granted on specific secrets, enabling full separation of duties for a defined duration (auto-expire) or a designated call source.


INSTALLING VAULT

Using Snap on Ubuntu/Debian:

sudo apt install snapd -y
sudo snap install vault

Alternatively, add the official repository to the system’s package manager.

wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vault -y

See more installation methods at [Link].

Edit the configuration file:

sudo nano /etc/vault.d/vault.hcl
ui = true
disable_mlock = true
default_lease_ttl ="87600"
max_lease_ttl = "87600"
storage "file" {
  path = "/opt/vault/data"
}

# HTTP listener
listener "tcp" {
  address = "127.0.0.1:8200"
  tls_disable = 1
}
systemctl enable vault --now
export VAULT_ADDR=http://127.0.0.1:8200
vault status

Note: Vault is not yet initialized and is currently sealed.

Navigate to http://127.0.0.1:8200, or create an SSH tunnel as follows.


INITIALIZE AND UNSEAL THE VAULT

ssh root@192.168.1.200 -L 8200:localhost:8200

Vault can be initialized via the Web UI:

Or via the CLI:

export VAULT_ADDR=http://127.0.0.1:8200
vault operator init

OR

vault operator init -key-shares=5 -key-threshold=3 > keys.json

Now unseal the Vault:

vault operator unseal

Provide the Unseal Keys from the previous step.

Vault is now ready to be accessed with the Initial Root Token.

Log in with the Initial Root Token:

vault login

Development (Unsafe) Deployment

In a development environment where reduced security is acceptable:

export VAULT_ADDR=http://127.0.0.1:8200
vault operator init -key-shares=1 -key-threshold=1 -format=json > init.json
vault operator unseal $(jq -r '.unseal_keys_b64[0]' init.json)
vault login $(jq -r '.root_token' init.json)

MANAGING POLICIES AND TOKENS

Using the Initial Root Token, enable a KV secrets engine:

export VAULT_ADDR=http://127.0.0.1:8200
vault login
vault secrets enable -path=kv kv

Create Policies:

cat <<EOF > readonly-policy.hcl
path "kv/*" {
capabilities = ["read", "list"]
}
EOF
vault policy write readonly readonly-policy.hcl
cat <<EOF > readwrite-policy.hcl
path "kv/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
EOF
vault policy write readwrite readwrite-policy.hcl

Note: These policies are overly permissive and should be used for reference only.

vault policy list

Create Tokens linked to policies:

vault token create -policy=readonly -orphan -no-default-policy -ttl=0
vault token create -policy=readwrite -orphan -no-default-policy -ttl=0

Note: These tokens are insecure and should be used for reference only. If needed, set the TTL to 87600 for a 10-year expiry.

To check a token’s permissions and status:

vault token lookup <TOKEN>

PUSHING AND PULLING SECRETS

Set the environment variables:

export VAULT_ADDR='http://127.0.0.1:8200/'
export VAULT_TOKEN=hvs.CAESIAH6vrn9oT0ITzzaCTSc4m9auVNuOULnpB8NqUsLdmv0Gh4KHGh2cy5wQVFpNzJodm1DcE5pRWNENFlBb21KWk4

Test writing and reading a secret from another host:

vault kv put kv/example.com cert=test key=test root_ca=test
vault kv get kv/example.com

Write (create or update) a secret using Read+Write access:

vault kv put kv/example.com cert=@/etc/letsencrypt/live/example.com/cert.pem key=@/etc/letsencrypt/live/example.com/cert.pem root_ca=@/etc/letsencrypt/live/example.com/fullchain.pem

Note: The @ symbol loads the file’s contents, not the path string.

From another host, read the secret using Read-Only access:

export VAULT_ADDR='http://127.0.0.1:8200/'
export VAULT_TOKEN=hvs.CAESIDRbO6D1-LpgJrZK-bnM-J67C5SrUl7LR7hsmJhCdyNCGh4KHGh2cy50RU1oWmtub3VBNTUxZ3RkSWpOb2RhZWk
vault kv get kv/example.com


ISSUING AN SSL/TLS CERTIFICATE FOR VAULT

Before exposing Vault outside of localhost, secure it with a TLS certificate.

sudo apt update
sudo apt install certbot python3-certbot-nginx -y
sudo nano /etc/nginx/sites-available/default

Set the server name to your public domain. It must be reachable on port 80 from the internet with no restrictions, as Let’s Encrypt will send HTTP requests from multiple random sources for the challenge.

    server_name vault.example.com;

Test and apply the configuration:

sudo nginx -t && sudo nginx -s reload

Run certbot and follow the prompts to enter your email, accept the terms, and so on.

echo 127.0.0.1 vault.example.com | sudo tee -a /etc/hosts
sudo certbot --nginx

Confirm that automatic renewal is configured in systemd:

sudo systemctl list-timers | grep certbot

If needed, place scripts in the following directories to run at specific points during renewal:

  • /etc/letsencrypt/renewal-hooks/pre/
    • Run before renewal
  • /etc/letsencrypt/renewal-hooks/post/
    • Run after successful renewal
  • /etc/letsencrypt/renewal-hooks/deploy/
    • Run after certificates are deployed

Update the Vault configuration to use the new certificates:

sudo nano /etc/vault.d/vault.hcl
# HTTP listener (disabled)
#listener "tcp" {
#  address = "127.0.0.1:8200"
#  tls_disable = 1
#}

# HTTPS listener
listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/etc/letsencrypt/live/example.com/cert.pem"
  tls_key_file  = "/etc/letsencrypt/live/example.com/privatekey.pem"
}

Grant Vault permission to read the certificate files:

sudo groupadd pki
sudo chgrp -R pki /etc/letsencrypt/archive
sudo chgrp -R pki /etc/letsencrypt/live
sudo chmod -R g+rx /etc/letsencrypt/archive
sudo chmod -R g+rx /etc/letsencrypt/live
sudo usermod -a -G pki vault

Restart Vault:

sudo systemctl restart vault.service

API calls can now use the following HTTPS address:

export VAULT_ADDR=https://vault.example.com:8200

BONUS

To issue SSL/TLS certificates using a DNS challenge (required for wildcards, and avoids exposing port 80 for the HTTP challenge), then store them in Vault.

Install ACME Shell [Link]:

curl https://get.acme.sh | sh -s [email protected]
acme.sh -h

Set the cPanel environment variables and issue the certificate:

export cPanel_Username="devops"
export cPanel_Apitoken="apitoken"
export cPanel_Hostname="https://cpanel.example.com:2083"
acme.sh --insecure --issue --keylength ec-256 --dns dns_cpanel -d example.com -d '*.example.com' --force --dnssleep 120

For Cloudflare:

export CF_Token="your_cloudflare_api_token"
export CF_Account_ID="your_cloudflare_account_id"
acme.sh --issue --keylength ec-256 --dns dns_cf -d example.com -d '*.example.com'

Upload the certificate to Vault:

cd ~/.acme.sh/example.com/
vault kv put kv/example.com [email protected] [email protected] [email protected] [email protected]

A notable alternative to Vault is Sealed Secrets by Bitnami [Link], designed for Kubernetes to prevent base64-encoded secrets from being stored in plaintext in manifests.