Hashicorp Vault
Introduction to Hashicorp Vault
HashiCorp Vault is a tool for securely accessing secrets (passwords, API keys, certificates, etc.) through a common interface with detailed logs for auditing.
Some of the Vault’s features include:
- Encryption
- Dynamic secret generation with expiration time
- Secret renewal and revocation
Many of these features are also available in services like Azure Key Vault and AWS Key Management Service, but Vault has the advantage of being open source and not tied to a specific cloud (cloud agnostic).
Secrets are stored in a storage backend that can be memory, disk, database, Azure Blob, AWS S3, among others. The Vault serves as an interface. In another article, I showed how to configure Vault to use Consul’s key-value store.
Vault was written in Go and is distributed as a single binary. It can be interacted with through the command line, HTTP REST, or the web interface.
Secrets Engines
Secret engines are responsible for storing, generating, and encrypting data.
Some secret engines only store data, while others connect to services and generate dynamic credentials on demand. Others provide encryption as a service, generation of TOTP, and certificates.
The Vault site lists nearly 50 secret engines. Among them are AWS, Azure, MySQL, PostgreSQL, Active Directory, PKI, and SSH.
The engines are activated in a path. The vault path-help
command can be used to obtain a
description of the engine and discover which paths it responds to.
Lifecycle of a secret engine:
- Enable:
vault secrets enable
- Disable:
vault secrets disable
- Move:
vault secrets move
- Tune:
vault secrets tune
KV Secrets Engine
The kv secret engine is used to store key-value type secrets.
There are two versions:
- KV version 1:
- No versioning
- A deleted secret cannot be recovered
- Better performance
- KV version 2:
- Has versioning (10 versions by default)
- Has soft delete
- Lower performance
Secrets Lifecycle
- Create:
kv put
- Read:
kv get
- Update:
kv put
- Delete/Undelete:
kv delete
/kv undelete
- Destroy:
kv destroy
- Metadata Destroy:
kv metadata delete
Authentication Methods
Vault supports multiple authentication methods, such as LDAP, Azure Active Directory, and AWS Identity & Access Management.
- List active methods:
vault auth list
- Enable a method:
vault auth enable [method]
- Configure an authentication method:
vault write auth/[method]/config
- Add a role:
vault write auth/[method]/role/[role_name]
- Disable a method:
vault auth disable [method]
Access Policies
A Vault access policy (Vault Policy) is a document in HCL (HashiCorp Configuration Language) or JSON that specifies who can do what in what way.
It is possible to have fine-grained control over what each user can or cannot access, including which commands they can/should use in each path. The official documentation has several useful examples.
- List policies:
vault policy list
- Create a policy:
vault policy write [policy] [policy_file.hcl]
- Update a policy:
vault write sys/policy/[policy]/ policy=[policy_file.hcl]
- Delete a policy:
vault delete sys/policy/[policy]
Dynamic Secrets
Dynamic Secrets are secrets created on-demand with a lease period. If not renewed, the secret is revoked.
Cubbyhole Response Wrapping
Instead of providing a token directly to the user, we can place that token in a cubbyhole and generate a token for one-time use with a short duration that has access to the cubbyhole. This is called Cubbyhole Response Wrapping.
Advantages:
- The secret is exposed for a short time (lease TTL)
- If the token leaks, it cannot be used again
- The user does not have direct access to the secret, but rather a reference
Audit
Vault logs all requests made to the server and all responses sent, including errors.
The logs are in JSON format and can be saved in more than one location (audit device: file, syslog, or socket) at the same time.
If the log_raw=true
option is not passed during activation, only the hashes of the secrets will be
saved in the log.
For security, Vault stops accepting requests if it cannot access any of the activated audit devices.
- Enable an audit device:
vault audit enable -path=vault_path [type]
- Disable an audit device:
vault audit disable [path]
- List audit devices:
vault audit list --detailed
Practical Step-by-Step
Examples involving everything explained above.
-
Install and start Vault
Using the package manager of your distribution or as detailed in the other article.
# pacman -S vault
# cat /etc/vault.hcl backend "file" { path = "/var/lib/vault" } listener "tcp" { tls_disable = 1 }
# Start the vault service
$ vault operator init Unseal Key 1: p/rCDw28akkqMBogtkpuH7OoB0IbwNK6fOPE1/K56mCw Unseal Key 2: 4Qy/bymi4BShb0JaoMnQm8Qry/MrrM3pP3CQDeJu93s+ Unseal Key 3: +Qdh8mBl1yzqi5V2u1tKG1u3pWtRfXEQ1cwMjoYpuSAy Unseal Key 4: lG6Vl8iiJhUpvmmWF5S8EgKd89wMUHsL4PNHuT4xGZ82 Unseal Key 5: rsufBM3HiobRE39El8ErIkzx+qkxz9unzIrMb/w5gZTB Initial Root Token: s.3iZvNY9lhNdeJVH5fIHU1RDv
-
Export the environment variables with the address and token
$ export VAULT_ADDR='http://127.0.0.1:8200' $ export VAULT_TOKEN='s.3iZvNY9lhNdeJVH5fIHU1RDv'
If you are running Vault in a Docker container, it’s worth creating the following alias:
$ alias vault='docker exec -it -e VAULT_TOKEN=$VAULT_TOKEN vault.server1 vault "$@"'
-
Unseal
$ vault operator unseal p/rCDw28akkqMBogtkpuH7OoB0IbwNK6fOPE1/K56mCw … Sealed true Unseal Progress 1/3 …
$ vault operator unseal +Qdh8mBl1yzqi5V2u1tKG1u3pWtRfXEQ1cwMjoYpuSAy … Sealed true Unseal Progress 2/3 …
$ vault operator unseal rsufBM3HiobRE39El8ErIkzx+qkxz9unzIrMb/w5gZTB Sealed false
-
Login
$ vault login
-
Enable the secrets backend
$ vault secrets enable -path=kv -version=2 kv Success! Enabled the kv secrets engine at: kv/
-
Configure to keep a maximum of 2 versions
$ vault write kv/config max_versions=2 Success! Data written to: kv/config
-
Write a secret
$ vault kv put kv/meu_app senha=123456 Key Value --- ----- created_time 2021-04-19T04:11:34.948088026Z deletion_time n/a destroyed false version 1
-
Create a second version of the secret
$ vault kv put kv/meu_app senha=123456 usuario=julio Key Value --- ----- created_time 2021-04-19T04:11:44.839504165Z deletion_time n/a destroyed false version 2
-
Create a third version of the secret
$ vault kv put kv/meu_app senha=654321 usuario=julio Key Value --- ----- created_time 2021-04-19T04:11:53.768980035Z deletion_time n/a destroyed false version 3
-
Read the value of the latest version
$ vault kv get kv/meu_app ====== Metadata ====== Key Value --- ----- created_time 2021-04-19T04:11:53.768980035Z deletion_time n/a destroyed false version 3 ===== Data ===== Key Value --- ----- senha 654321 usuario julio
-
Read the value of the second version
$ vault kv get -version=2 kv/meu_app ====== Metadata ====== Key Value --- ----- created_time 2021-04-19T04:11:44.839504165Z deletion_time n/a destroyed false version 2 ===== Data ===== Key Value --- ----- senha 123456 usuario julio
-
Read the value of the first version
This version was destroyed because we set a limit to save only the last two.
$ vault kv get -version=1 kv/meu_app
No value found at kv/data/meu_app
- Get the value of the second version in JSON
$ vault kv get -format=json -version=2 kv/meu_app
{
"request_id": "379bbb04-a21b-cebe-9e99-392b85372592",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"data": {
"senha": "123456",
"usuario": "julio"
},
"metadata": {
"created_time": "2021-04-19T04:11:44.839504165Z",
"deletion_time": "",
"destroyed": false,
"version": 2
}
},
"warnings": null
}
-
Filter with
jq
$ vault kv get -format=json -version=2 kv/meu_app | jq -r .data.data.senha 123456
-
Get the value of the second version using the API
$ curl -k --header "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/kv/data/meu_app?version=2 | jq .data.data { "senha": "123456", "usuario": "julio" }
-
Delete versions 2 and 3 of the secret
If you do not specify the version, the last one is deleted.
$ vault kv delete -versions="2,3" kv/meu_app Success! Data deleted (if it existed) at: kv/meu_app
$ vault kv get -version=2 kv/meu_app ====== Metadata ====== Key Value --- ----- created_time 2021-04-19T04:11:44.839504165Z deletion_time 2021-04-19T04:14:27.514456987Z destroyed false version 2
$ vault kv get -version=3 kv/meu_app ====== Metadata ====== Key Value --- ----- created_time 2021-04-19T04:11:53.768980035Z deletion_time 2021-04-19T04:14:27.514457229Z destroyed false version 3
- Retrieve version 2
$ vault kv undelete -versions=2 kv/meu_app Success! Data written to: kv/undelete/meu_app
-
Destroy version 2 of the secret
$ vault kv destroy -versions=2 kv/meu_app Success! Data written to: kv/destroy/meu_app
$ vault kv get -version=2 kv/meu_app ====== Metadata ====== Key Value --- ----- created_time 2021-04-19T04:11:44.839504165Z deletion_time n/a destroyed true version 2
-
Delete all metadata
$ vault kv list kv/ Keys ---- meu_app
$ vault kv metadata delete kv/meu_app Success! Data deleted (if it existed) at: kv/metadata/meu_app
$ vault kv list kv/ No value found at kv/metadata
-
Create the admin policy
$ cat policy_admin.hcl
# Mount secrets engines
path "sys/mounts/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
# Configure the database secrets engine and create roles
path "database/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
# Manage the leases
path "sys/leases/+/database/creds/readonly/*" {
capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
}
path "sys/leases/+/database/creds/readonly" {
capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
}
# Write ACL policies
path "sys/policies/acl/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
# Manage tokens for verification
path "auth/token/create" {
capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
}
$ vault policy write admin policy_admin.hcl
Success! Uploaded policy: admin
- Create the apps policy
$ cat policy_apps.hcl
# Get credentials from the database secrets engine 'readonly' role.
path "database/creds/readonly" {
capabilities = [ "read" ]
}
$ vault policy write apps policy_apps.hcl
Success! Uploaded policy: apps
-
List the policies
$ vault policy list admin apps default root
-
Start Docker and pull the PostgreSQL image
# systemctl start docker.service $ docker pull postgres:13
-
Create a database
$ docker run \ --name postgres \ --env POSTGRES_USER=root \ --env POSTGRES_PASSWORD=rootpassword \ --detach \ --publish 5432:5432 \ postgres:13
-
Connect to the database and create a role named
ro
with read permission on all tables$ docker exec -it postgres psql root=# CREATE ROLE ro NOINHERIT; CREATE ROLE root=# GRANT SELECT ON ALL TABLES IN SCHEMA public TO "ro"; GRANT root=# \q
-
Enable the database secrets engine
$ vault secrets enable database Success! Enabled the database secrets engine at: database/
-
Configure the engine to work with Postgres
$ vault write database/config/postgresql \ plugin_name=postgresql-database-plugin \ connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" \ allowed_roles=readonly \ username="root" \ password="rootpassword"
-
SQL used to create credentials
$ cat readonly.sql CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT; GRANT ro TO "{{name}}";
-
Create the
readonly
rolevault write database/roles/readonly \ db_name=postgresql \ creation_statements=@readonly.sql \ default_ttl=1h \ max_ttl=24h Success! Data written to: database/roles/readonly
-
Generate a credential in the DB
$ vault read database/creds/readonly Key Value --- ----- lease_id database/creds/readonly/hTFHqyO2AVcCBQRFmRTv8bGV lease_duration 1h lease_renewable true password NMVS-H2YO5BNMAkzP6Nl username v-root-readonly-sKjgJBT41RYrrLtB1MtS-1618807755
-
List the users of Postgres
$ docker exec -it postgres psql root=# \du List of roles Role name | Attributes | Member of ----------------------------+-------------------------------------------+----------- ro | No inheritance, Cannot login | {} root | Superuser, Create…Replication, Bypass RLS | {} v-root-readonly-sKj…7755 | Password valid until 2021-04-19 05:49:20+00 | {ro} root=# \q
-
List leases
$ vault list sys/leases/lookup/database/creds/readonly Keys ---- hTFHqyO2AVcCBQRFmRTv8bGV
-
Store the ID in a variable
$ LEASE_ID=$(vault list -format=json sys/leases/lookup/database/creds/readonly | jq -r ".[0]")
$ echo $LEASE_ID hTFHqyO2AVcCBQRFmRTv8bGV
-
Lease renew
If the requested value exceeds the limit, it will stay at the limit.
$ vault lease renew -increment=3600 database/creds/readonly/$LEASE_ID
$ vault lease renew -increment=96400 database/creds/readonly/$LEASE_ID WARNING! The following warnings were returned from Vault: * TTL of "26h46m40s" exceeded the effective max_ttl of "23h33m18s"; TTL value is capped accordingly Key Value --- ----- lease_id database/creds/readonly/hTFHqyO2AVcCBQRFmRTv8bGV lease_duration 23h33m18s lease_renewable true
root=# \du List of roles Role name | Attributes | Member of ----------------------------+-------------------------------------------+----------- ro | No inheritance, Cannot login | {} root | Superuser, Create…Replication, Bypass RLS | {} v-root-readonly-sKj…7755 | Password valid until 2021-04-20 04:49:20+00 | {ro} root=# \q
-
Lease revoke
$ vault lease revoke database/creds/readonly/$LEASE_ID All revocation operations queued successfully!
$ docker exec -it postgres psql root=# \du List of roles Role name | Attributes | Member of -----------+----------------------------------------------------+----------- ro | No inheritance, Cannot login | {} root | Superuser, Create role, …, Replication, Bypass RLS | {} root=# \q
$ vault list sys/leases/lookup/database/creds/readonly No value found at sys/leases/lookup/database/creds/readonly
-
List active authentication methods:
$ vault auth list Path Type Accessor Description ---- ---- -------- ----------- token/ token auth_token_5e067aef token based credentials
-
Enable
userpass
:$ vault auth enable userpass Success! Enabled userpass auth method at: userpass/
-
View available options
$ vault path-help auth/userpass
-
Add a user
$ vault write auth/userpass/users/julio \ password=minha_senha \ policies=admin Success! Data written to: auth/userpass/users/julio
-
List users
$ vault list auth/userpass/users Keys ---- julio
-
Open a new terminal and log in with the created user:
$ export VAULT_ADDR='http://127.0.0.1:8200' $ vault login -method=userpass username=julio Success! You are now authenticated. The token information displayed below is already stored in the token helper. You do NOT need to run "vault login" again. Future Vault requests will automatically use this token. Key Value --- ----- token s.8vRGmBVEQ1indCPtGL796od3 token_accessor CmTWhmRHFq4RMLb6wccFgOFT token_duration 768h token_renewable true token_policies ["admin" "default"] identity_policies [] policies ["admin" "default"] token_meta_username julio
-
Delete the user:
$ vault delete auth/userpass/users/julio Success! Data deleted (if it existed) at: auth/userpass/users/julio
-
Save a secret in Vault:
$ vault kv put kv/app-server api-key=123456 Key Value --- ----- created_time 2021-04-19T06:02:13.217806976Z deletion_time n/a destroyed false version 1
-
Create a wrapping token valid for 5 minutes:
$ vault kv get -wrap-ttl=300 kv/app-server Key Value --- ----- wrapping_token: s.wqyqcD8lPcaOGznSg4ZNVFPC wrapping_accessor: sy6OAxfoKcmPLbUufzGhxkFY wrapping_token_ttl: 5m wrapping_token_creation_time: 2021-04-19 13:08:13.210327049 -0300 -03 wrapping_token_creation_path: kv/data/app-server
-
Access the secret using this token
$ curl --header "X-Vault-Token: s.wqyqcD8lPcaOGznSg4ZNVFPC" --request POST \ $VAULT_ADDR/v1/sys/wrapping/unwrap | jq { "request_id": "69598fb3-0f90-a1c8-22d8-387f5c79564e", "lease_id": "", "renewable": false, "lease_duration": 0, "data": { "data": { "api-key": "123456" }, "metadata": { "created_time": "2021-04-19T06:02:13.217806976Z", "deletion_time": "", "destroyed": false, "version": 1 } }, "wrap_info": null, "warnings": null, "auth": null }
-
Create a kv store at the webkv path
$ vault secrets enable -path=webkv kv Success! Enabled the kv secrets engine at: webkv/
-
Add a secret to webkv
$ vault kv put webkv/app-server api-key=123456 Success! Data written to: webkv/app-server
-
Create a web policy
$ cat webpol.hcl path "webkv/*" { capabilities = ["read", "list"] }
$ vault policy write web webpol.hcl Success! Uploaded policy: web
-
Create a token valid for 5 minutes using the web policy
$ vault token create -policy=web -wrap-ttl=300 Key Value --- ----- wrapping_token: s.FPSHG7GJ3pWWliA0zSDEb77w wrapping_accessor: oLR1tRBpyQACPICrMIWrtfDm wrapping_token_ttl: 5m wrapping_token_creation_time: 2021-04-19 14:58:31.807307058 -0300 -03 wrapping_token_creation_path: auth/token/create wrapped_accessor: eP85Zj7EmwrDgE1GoagsNJ6p
-
Retrieve the token
$ curl --header "X-Vault-Token: s.FPSHG7GJ3pWWliA0zSDEb77w" --request POST \ $VAULT_ADDR/v1/sys/wrapping/unwrap | jq { "request_id": "4cfe3fd2-942a-d7e3-34e5-2b7d644b3e28", "lease_id": "", "renewable": false, "lease_duration": 0, "data": null, "wrap_info": null, "warnings": null, "auth": { "client_token": "s.lDqthe94B6bFVyXxNqLMuwYO", "accessor": "eP85Zj7EmwrDgE1GoagsNJ6p", "policies": [ "default", "web" ], "token_policies": [ "default", "web" ], "metadata": null, "lease_duration": 2764800, "renewable": true, "entity_id": "", "token_type": "service", "orphan": false } }
-
Retrieve the secret using the token received in the response of the previous command
$ curl --header "X-Vault-Token: s.lDqthe94B6bFVyXxNqLMuwYO" $VAULT_ADDR/v1/webkv/app-server | jq { "request_id": "f63dcc67-b9f6-43f6-dbb8-756c709e6a2f", "lease_id": "", "renewable": false, "lease_duration": 2764800, "data": { "api-key": "123456" }, "wrap_info": null, "warnings": null, "auth": null }
Links
- https://github.com/scarolan/aws-vault-in-five-minutes/blob/master/docs/index.md
- https://github.com/scarolan/vault-aws-cf
- https://jessequinn.info/blog/articles/hashicorp-boundary
- https://learn.hashicorp.com/tutorials/consul/deployment-guide
- https://learn.hashicorp.com/tutorials/vault/ha-with-consul
- https://medium.com/@pcarion/a-consul-a-vault-and-a-docker-walk-into-a-bar-d5a5bf897a87
- https://www.katacoda.com/courses/docker-production/vault-secrets
- https://www.youtube.com/watch?v=a-fq30II2cM
- https://discuss.hashicorp.com/t/vault-enable-https-with-openshift4-and-helm3/21574
- https://churrops.io/2017/08/30/meetup-churrops-iniciando-a-trilha-com-o-vault-hands-on/
- https://jovandeginste.github.io/2016/07/20/use-vault-with-client-certificates.html
- http://javierblog.com/deploy-a-hashicorp-vault-cluster-with-consul-backend/
- https://werner-dijkerman.nl/2017/01/15/setting-up-a-secure-vault-with-a-consul-backend/
- https://itspyworld.blogspot.com/2020/07/installing-hashicorp-vault-password.html
- https://learn.hashicorp.com/tutorials/consul/tls-encryption-openssl-secure
- https://github.com/ned1313/Getting-Started-Vault