Skip to main content

Key/Value Secrets Engine - API Calls

Challenge

The KV secrets engine v1 does not provide a way to version or roll back secrets. This made it difficult to recover from unintentional data loss or overwrite when more than one user is writing at the same path.

Solution

Run the version 2 of KV secrets engine which can retain a configurable number of secret versions. This enables older versions' data to be retrievable in case of unwanted deletion or updates of the data. In addition, its Check-and-Set operations can be used to protect the data from being overwritten unintentionally.

Step 1: Check the KV secrets engine version

Ensure $VAULT_TOKEN value is set on the command line.

(Persona: admin)

The Vault server started in dev mode, automatically enables v2 of the KV secrets engine at the secret/ path. Verify that KV secrets engine is enabled and is set to version 2.

Display all the enabled secrets engine.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/sys/mounts | jq

NOTE: This example uses jq to process the JSON output for readability.

Step 2: Write secrets

To understand how the versioning works, let's write some test data.

Create an API request payload containing some test data.

tee payload.json <<EOF
{
"data": {
"name": "ACME Inc.",
"contact_email": "[email protected]"
}
}
EOF

Write some data at secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload.json \
$VAULT_ADDR/v1/secret/data/customer/acme | jq

Notice that the endpoint for KV v2 is /secret/data/<path>; therefore to write secrets at secret/customer/acme, the API endpoint becomes /secret/data/customer/acme.

Create an API request payload containing the data to update the current data.

tee payload.json <<EOF
{
"data": {
"name": "ACME Inc.",
"contact_email": "[email protected]"
}
}
EOF

Now you have two versions of the secret/customer/acme data.

Read back the secret.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/customer/acme | jq -r ".data"

Step 3: Retrieve a specific version of secret

You may run into a situation where you need to view the secret before an update.

Get version 1 of the secret defined at the path secret/customer/acme

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/customer/acme\?version=1 | jq -r ".data"

Get the metadata of the secret defined at the path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/metadata/customer/acme | jq -r ".data"

Step 4: Specify the number of versions to keep

By default, the kv-v2 secrets engine keeps up to 10 versions. Let's limit the maximum number of versions to keep to be 4.

Create an API request payload specifying the max_versions to 4.

tee payload-config.json<<EOF
{
"max_versions": 4,
"cas_required": false
}
EOF

Configure the secrets engine at path secret/ to limit all secrets to a maximum of 4 versions.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload-config.json \
$VAULT_ADDR/v1/secret/config

Every secret stored for this engine are set to a maximum of 4 versions.

Display the secrets engine configuration settings.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/config | jq -r ".data"

Configure the secret at path secret/customer/acme to limit secrets to a maximum of 4 versions.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload.json \
$VAULT_ADDR/v1/secret/metadata/customer/acme

The secret can also define the maximum number of versions.

Create four more secrets at the path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload.json \
$VAULT_ADDR/v1/secret/data/customer/acme

Get the metadata of the secret defined at the path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/metadata/customer/acme | jq -r ".data"

The metadata displays the current_version and the history of versions stored. Secrets stored at this path are limited to 4 versions. Version 1 and 2 are deleted.

Verify that version 1 of the secret defined at the path secret/customer/acme are deleted.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/customer/acme\?version=1 | jq

The output should look as follow:

{
"errors": []
}

Step 5: Delete versions of secret

Delete version 4 and 5 of the secrets at path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{ "versions":[4,5] }' \
$VAULT_ADDR/v1/secret/delete/customer/acme

Get the metadata of the secret defined at the path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/metadata/customer/acme | jq -r ".data.versions"

The metadata on versions 4 and 5 reports its deletion timestamp (deletion_time); however, the destroyed parameter is set to false.

Undelete version 5 of the secrets at path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{ "versions":[5] }' \
$VAULT_ADDR/v1/secret/undelete/customer/acme

Step 6: Permanently delete data

Destroy version 4 of the secrets at path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{ "versions":[4] }' \
$VAULT_ADDR/v1/secret/destroy/customer/acme

Get the metadata of the secret defined at the path secret/customer/acme.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/metadata/customer/acme | jq -r ".data.versions"

The metadata indicates that Version 4 is destroyed.

Delete all versions of the secret at the path secret/customer/acme

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request DELETE \
$VAULT_ADDR/v1/secret/metadata/customer/acme

Step 7: Configure automatic data deletion

As of Vault 1.2, you can configure the length of time before a version gets deleted. For example, if your organization requires data to be deleted after 10 days from its creation, you can configure the K/V v2 secrets engine to do so by setting the delete_version_after parameter.

Configure the secrets at path secret/test to delete versions after 40 seconds.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{ "delete_version_after": "40s" }' \
$VAULT_ADDR/v1/secret/metadata/test

Create a secret at the path secret/test.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{"data": {"message": "data1"}}' \
$VAULT_ADDR/v1/secret/data/test | jq -r ".data"

Again, create a secret at the path secret/test.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{"data": {"message": "data2"}}' \
$VAULT_ADDR/v1/secret/data/test

Again, create a secret at the path secret/test.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{"data": {"message": "data3"}}' \
$VAULT_ADDR/v1/secret/data/test

Get the metadata of the secret defined at the path secret/test

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/metadata/test | jq -r ".data"

The metadata displays a deletion_time set on each version. After 40 seconds, the data gets deleted automatically. The data has not been destroyed.

Get version 1 of the secret defined at the path `secret/test.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/test\?version=1 | jq -r ".data"

Step 8: Check-and-Set operations

The v2 of KV secrets engine supports a Check-And-Set operation to prevent unintentional secret overwrite. When you pass the cas flag to Vault, it first checks if the key already exists.

Display the secrets engine configuration settings.

vault read secret/config

The cas_required setting is false. The KV secrets engine defaults to disable the Check-And-Set operation.

Create an API request payload that sets cas_required to true.

tee payload-cas.json<<EOF
{
"max_versions": 10,
"cas_required": true
}
EOF

Configure the secrets engine at path secret/ to enable Check-And-Set.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload-cas.json \
$VAULT_ADDR/v1/secret/config

Configure the secret at path secret/partner to enable Check-And-Set.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload.json \
$VAULT_ADDR/v1/secret/metadata/partner

Once check-and-set is enabled, every write operation requires the cas parameter with the current version of the secret. Set cas to 0 when a secret at that path does not already exist.

Create an API request payload with secret data and the cas value to 0.

tee payload.json <<EOF
{
"options": {
"cas": 0
},
"data": {
"name": "Example Co.",
"partner_id": "123456789"
}
}
EOF

Create a new secret at the path secret/partner.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload.json \
$VAULT_ADDR/v1/secret/data/partner | jq -r ".data"

Create an API request payload with updated secret data and the cas value to 1.

tee payload.json <<EOF
{
"options": {
"cas": 1
},
"data": {
"name": "Example Co.",
"partner_id": "ABCDEFGHIJKLMN"
}
}
EOF

Overwrite the secret at the path secret/partner.

curl --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data @payload.json \
$VAULT_ADDR/v1/secret/data/partner | jq -r ".data"