April 6 & 7
Learn about Vault, Consul, & more at HashiDays Sydney in Australia Register Now

Developer

Versioned Key/Value Secrets Engine

The Static Secrets guide introduced the basics of working with key-value secrets engine. Vault 0.10 introduced K/V Secrets Engine v2 with Secret Versioning. This guide highlights the key-value secrets engine v2 features.

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.

Versioned KV

Prerequisites

To perform the tasks described in this guide, you need to have a Vault environment. Refer to the Getting Started guide to install Vault. Make sure that your Vault server has been initialized and unsealed.

NOTE: An interactive tutorial is also available if you do not have a Vault environment to perform the steps described in this guide. Click the Show Tutorial button to launch the tutorial.

Policy requirements

To perform all tasks demonstrated in this guide, your policy must include the following permissions:

# Write and manage secrets in key-value secrets engine
path "secret*" {
  capabilities = [ "create", "read", "update", "delete", "list" ]
}

# To enable secrets engines
path "sys/mounts/*" {
  capabilities = [ "create", "read", "update", "delete" ]
}

If you are not familiar with policies, complete the policies guide.

Step 1: Check the KV secrets engine version

(Persona: devops)

Currently, when you start the Vault server in dev mode, it automatically enables v2 of the KV secrets engine at secret/. Before beginning, verify that you are using the v2 of the KV secrets engine.

CLI command / API call using cURL / Web UI

CLI command

To check the KV secrets engine version:

$ vault secrets list -detailed

Path          Type         Accessor           ...   Options           Description
----          ----         --------                 -------           -----------
cubbyhole/    cubbyhole    cubbyhole_9d52aeac ...   map[]             per-token private secret storage
identity/     identity     identity_acea5ba9  ...   map[]             identity store
secret/       kv           kv_2226b7d3        ...   map[version:2]    key/value secret storage
...

Under the Options, the version number is indicated. If the version is 1, you can upgrade it to v2 by executing the following command.

$ vault kv enable-versioning secret/

NOTE: If KV secrets engine is not enabled, execute the following command to enable KV v2 secrets engine at secret/ path.

$ vault secrets enable -path=secret kv-v2

API call using cURL

To check the KV secrets engine version:

$ curl --header "X-Vault-Token: <TOKEN>" \
       <VAULT_ADDRESS>/v1/sys/mounts

Where <TOKEN> is your valid token, and <VAULT_ADDRESS> is where your vault server is running.

Example:

$ curl --header "X-Vault-Token: <TOKEN>" \
       http://127.0.0.1:8200/v1/sys/mounts | jq
...
  "secret/": {
    "accessor": "kv_f05b8b9c",
    "config": {
      "default_lease_ttl": 0,
      "force_no_cache": false,
      "max_lease_ttl": 0,
      "plugin_name": ""
    },
    "description": "key/value secret storage",
    "local": false,
    "options": {
      "version": "2"
    },
    "seal_wrap": false,
    "type": "kv"
  },
...

The indicated version should be 2. If the version is 1, upgrade it to v2 by invoking the sys/mounts/secret/tune endpoint.

First, create an API request payload specifying the version.

$ tee payload.json <<"EOF"
{
  "options": {
      "version": "2"
  }
}
EOF

Invoke the sys/mounts/<mount-path>/tune endpoint to upgrade.

$ curl --header "X-Vault-Token: <TOKEN>" \
       --request POST \
       --data @payload.json \
       http://127.0.0.1:8200/v1/sys/mounts/secret/tune

NOTE: If KV secrets engine is not enabled, enable KV v2 secrets engine at secret/ path.

$ curl --header "X-Vault-Token: <TOKEN>" \
       --request POST \
       --data '{ "type": "kv-v2" }' \
       https://127.0.0.1:8200/v1/sys/mounts/secret

Web UI

  1. Open a web browser and launch the Vault UI (e.g. http://127.0.0.1:8200/ui) and then login. Web UI

    If secret/ does not indicates v2, you can upgrade it from v1 to v2. Click the Vault CLI shell icon (>_) to open a command shell. Execute the following command to upgrade it to v2: vault write sys/mounts/secret/tune version=2

    Tune KV Versions

    Click the icon (>_) again to hide the shell.

    NOTE: If KV secrets engine is not enabled, enable KV v2 secrets engine at secret/ path.

  2. Clicking Enable new engine. Select KV from the list, and then click Next. Enabling kv-v2

  3. Enter secret in the Path field. Under the Version, be sure to select 2.

  4. Click Enable Engine to complete.

Step 2: Write Secrets

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

CLI command / API call using cURL / Web UI

CLI command

  1. To operate with key/value secrets engine, run vault kv commands. Execute the following command to write some secrets.

    $ vault kv put secret/customer/acme name="ACME Inc." \
            contact_email="jsmith@acme.com"
    Key              Value
    ---              -----
    created_time     2018-04-14T00:05:47.115378933Z
    deletion_time    n/a
    destroyed        false
    version          1
    
  2. To update the existing secret, run the vault kv put command again.

    $ vault kv put secret/customer/acme name="ACME Inc." contact_email="john.smith@acme.com"
    Key              Value
    ---              -----
    created_time     2018-04-14T00:13:35.296018431Z
    deletion_time    n/a
    destroyed        false
    version          2
    

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

  3. Run vault kv get to read the data.

    $ vault kv get secret/customer/acme
    
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2018-04-14T00:13:35.296018431Z
    deletion_time    n/a
    destroyed        false
    version          2
    
    ======== Data ========
    Key              Value
    ---              -----
    contact_email    john.smith@acme.com
    name             ACME Inc.
    
  4. Writing to a key will replace the existing value, and sub-fields are not merged together. Therefore, running the following command can result in an unintentional data loss.

    vault kv put secret/customer/acme contact_email="admin@acme.com"

    To update the contact_email address without changing the name, execute the vault kv patch command instead.

    $ vault kv patch secret/customer/acme contact_email="admin@acme.com"
    

API call using cURL

  1. Create an API request payload containing some test data.

    $ tee payload.json <<EOF
    {
      "data": {
        "name": "ACME Inc.",
        "contact_email": "jsmith@acme.com"
      }
    }
    EOF
    
  2. Write some data at secret/customer/acme.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data @payload.json \
          http://127.0.0.1:8200/v1/secret/data/customer/acme
    

    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.

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

    $ tee payload.json <<EOF
    {
      "data": {
        "name": "ACME Inc.",
        "contact_email": "john.smith@acme.com"
      }
    }
    EOF
    
  4. Update the secret to create another version.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data @payload.json \
          http://127.0.0.1:8200/v1/secret/data/customer/acme
    

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

  5. Read back the secret.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/data/customer/acme | jq
    {
      # ...snip...
      "data": {
        "data": {
          "contact_email": "john.smith@acme.com",
          "name": "ACME Inc."
        },
        "metadata": {
          "created_time": "2018-04-14T00:59:11.27903511Z",
          "deletion_time": "",
          "destroyed": false,
          "version": 2
        }
      },
      # ...snip...
    }
    

Web UI

  1. In the Web UI, select secret/ and then click Create secret. Enter the secrets. You can click on the sensitive information toggle to show or hide the entered secret values. Write Secret

    NOTE: Notice that the Maximum Number of Versions is set to 10 by default.

  2. Click Save.

  3. To update the existing secret, select Create new version, change the contact_email value, and then click Save.

    Write Secret

Step 3: Retrieve a Specific Version of Secret

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

CLI command / API call using cURL / Web UI

CLI command

  1. To retrieve the version 1 of the secret written at secret/customer/acme.

    $ vault kv get -version=1 secret/customer/acme
    
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2018-04-14T00:05:47.115378933Z
    deletion_time    n/a
    destroyed        false
    version          1
    
    ======== Data ========
    Key              Value
    ---              -----
    contact_email    jsmith@acme.com
    name             ACME Inc.
    
  2. To read the metadata of secret/customer/acme.

    $ vault kv metadata get secret/customer/acme
    
    ========== Metadata ==========
    Key                     Value
    ---                     -----
    cas_required            false
    created_time            2019-07-31T05:57:32.959408Z
    current_version         2
    delete_version_after    0s
    max_versions            0
    oldest_version          0
    updated_time            2019-07-31T05:58:45.338017Z
    
    ====== Version 1 ======
    Key              Value
    ---              -----
    created_time     2019-07-31T05:57:32.959408Z
    deletion_time    n/a
    destroyed        false
    
    ====== Version 2 ======
    Key              Value
    ---              -----
    created_time     2019-07-31T05:58:06.354642Z
    deletion_time    n/a
    destroyed        false
    

API call using cURL

  1. To retrieve the version 1 of the secret written at secret/customer/acme.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/data/customer/acme?version=1 | jq
    {
      # ...snip...
      "data": {
        "data": {
          "contact_email": "jsmith@acme.com",
          "name": "ACME Inc."
        },
        "metadata": {
          "created_time": "2018-04-14T00:05:47.115378933Z",
          "deletion_time": "",
          "destroyed": false,
          "version": 1
        }
      },
      # ...snip...
    }
    
  2. To read the metadata of secret/customer/acme.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/metadata/customer/acme | jq
    {
      # ...snip...
      "data": {
          "cas_required": false,
          "created_time": "2019-07-31T05:57:32.959408Z",
          "current_version": 2,
          "delete_version_after": "0s",
          "max_versions": 0,
          "oldest_version": 0,
          "updated_time": "2019-07-31T05:58:45.338017Z",
          "versions": {
            "1": {
              "created_time": "2019-07-31T05:57:32.959408Z",
              "deletion_time": "",
              "destroyed": false
            },
            "2": {
              "created_time": "2019-07-31T05:58:06.354642Z",
              "deletion_time": "",
              "destroyed": false
            }
        }
      },
      # ...snip...
    }
    

Web UI

  1. Select History and then Version 1

    Read Version 1

  2. The version changes from Version 2 to Version 1. To view the raw values, click on the sensitive information toggle.

    Read Version 1

  3. You can create a new version of the secrets based on an older version. Select History > View version history. Select the version you wish to based the data on, and then select Create new version from 1.

    Create New Version

This can help revert an older version of the secrets.

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.

CLI command / API call using cURL / Web UI

CLI command

  1. To set the secret/ to keep up to 4 versions, execute the following command.

    $ vault write secret/config max_versions=4
    Success! Data written to: secret/config
    
  2. View the configuration settings.

    $ vault read secret/config
    Key             Value
    ---             -----
    cas_required    false
    max_versions    4
    

    Or, to limit the number of versions only on the secret/customer/acme path rather than the entire secret/ engine:

    $ vault kv metadata put -max-versions=4 secret/customer/acme
    
  3. Overwrite the data a few more times to see what happens to its metadata.

    $ vault kv metadata get secret/customer/acme
    ======= Metadata =======
    Key                Value
    ---                -----
    created_time       2018-04-14T00:42:25.677078177Z
    current_version    6
    max_versions       0
    oldest_version     3
    updated_time       2018-04-16T00:17:23.930473344Z
    
    ====== Version 3 ======
    Key              Value
    ---              -----
    created_time     2018-04-16T00:15:59.880368849Z
    deletion_time    n/a
    destroyed        false
    
    ====== Version 4 ======
    Key              Value
    ---              -----
    created_time     2018-04-16T00:16:18.941331243Z
    deletion_time    n/a
    destroyed        false
    
    ====== Version 5 ======
    Key              Value
    ---              -----
    created_time     2018-04-16T00:16:34.407951572Z
    deletion_time    n/a
    destroyed        false
    
    ====== Version 6 ======
    Key              Value
    ---              -----
    created_time     2018-04-16T00:17:23.930473344Z
    deletion_time    n/a
    destroyed        false
    

    In this example, the current version is 6. Notice that version 1 and 2 do not show up in the metadata. Because the kv secrets engine is configured to keep only 4 versions, the oldest two versions are permanently deleted and you won't be able to read them.

    $ vault kv get -version=1 secret/customer/acme
    No value found at secret/data/customer/data
    

API call using cURL

  1. Create an API request payload specifing the max_versions to 4.

    $ tee payload.json<<EOF
    {
      "max_versions": 4,
      "cas_required": false
    }
    EOF
    
  2. Configure the secrets engine.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data @payload.json
          http://127.0.0.1:8200/v1/secret/config
    
  3. To view the configuration.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/config | jq
    {
    "request_id": "8addfed1-41eb-6a19-8342-93f493c51538",
    "lease_id": "",
    "renewable": false,
    "lease_duration": 0,
    "data": {
      "cas_required": false,
      "max_versions": 4
    },
    "wrap_info": null,
    "warnings": null,
    "auth": null
    }
    
  4. To limit the number of versions only on the secret/customer/acme path rather than the entire secret/ engine.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data @payload.json
          http://127.0.0.1:8200/v1/secret/metadata/customer/acme
    

    Invoke the secret/metadata/customer/acme endpoint instead.

  5. Overwrite the data a few more times to see what happens to the data.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/metadata/customer/acme | jq
    {
    ...
    "data": {
      "created_time": "2018-04-14T00:42:25.677078177Z",
      "current_version": 6,
      "max_versions": 0,
      "oldest_version": 3,
      "updated_time": "2018-04-16T00:17:23.930473344Z",
      "versions": {
        "3": {
          "created_time": "2018-04-16T00:15:59.880368849Z",
          "deletion_time": "",
          "destroyed": false
        },
        "4": {
          "created_time": "2018-04-16T00:16:18.941331243Z",
          "deletion_time": "",
          "destroyed": false
        },
        "5": {
          "created_time": "2018-04-16T00:16:34.407951572Z",
          "deletion_time": "",
          "destroyed": false
        },
        "6": {
          "created_time": "2018-04-16T00:17:23.930473344Z",
          "deletion_time": "",
          "destroyed": false
        }
      }
    },
    ...
    }
    

    In this example, the current version is 6. Notice that version 1 and 2 do not show up in the metadata. Because the kv secrets engine is configured to keep only 4 versions, the oldest two versions are permanently deleted and you won't be able to read them.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/data/customer/acme?version=1 | jq
    {
    "errors": []
    }
    

Web UI

  1. Click the Vault CLI shell icon (>_) to open a command shell. Execute the following command to tune the maximum number of versions to keep: vault write secret/config max_versions=4

    Tune Max Versions

  2. Click the icon (>_) again to hide the shell.

  3. Overwrite the data a few more times to see what happens to its versions.

    Tune Max Versions

In this example, the current version is 6. Notice that version 1 and 2 do not show up in the metadata. Because the kv secrets engine is configured to keep only 4 versions, the oldest two versions are permanently deleted and you won't be able to read them.

Step 5: Delete versions of secret

CLI command / API call using cURL / Web UI

CLI command

  1. Let's delete version 4 and 5.

    $ vault kv delete -versions="4,5" secret/customer/acme
    Success! Data deleted (if it existed) at: secret/customer/acme
    
  2. Check the metadata.

    $ vault kv metadata get secret/customer/acme
    ...
    ====== Version 4 ======
    Key              Value
    ---              -----
    created_time     2018-04-16T00:12:25.404198622Z
    deletion_time    2018-04-16T01:04:01.160426888Z
    destroyed        false
    
    ====== Version 5 ======
    Key              Value
    ---              -----
    created_time     2018-04-16T00:12:47.527981267Z
    deletion_time    2018-04-16T01:04:01.160427742Z
    destroyed        false
    ...
    

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

  3. If version 5 was deleted by mistake and you wish to recover, invoke the vault kv undelete command.

    $ vault kv undelete -versions=5 secret/customer/acme
    Success! Data written to: secret/undelete/customer/acme
    

API call using cURL

  1. Let's delete versions 4 and 5.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data '{ "versions":[4,5] }'
          http://127.0.0.1:8200/v1/secret/delete/customer/acme
    
  2. Check the metadata.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/metadata/customer/acme | jq
    
    # ...snip...
    "4": {
      "created_time": "2018-04-16T00:16:18.941331243Z",
      "deletion_time": "2018-04-16T01:17:42.003111567Z",
      "destroyed": false
    },
    "5": {
      "created_time": "2018-04-16T00:16:34.407951572Z",
      "deletion_time": "2018-04-16T01:17:42.003111978Z",
      "destroyed": false
    },
    # ...snip...
    

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

  3. If version 5 was deleted by mistake and you wish to recover, invoke the /secret/undelete endpoint.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data '{ "versions":[5] }'
          http://127.0.0.1:8200/v1/secret/undelete/customer/acme
    

Web UI

Let's delete versions 4 and 5.

  1. At the secret/customer/acme path, select History > View version history. Expand the menu for Version 5 and select Delete version.

    Delete Versions

  2. At the confirmation dialog, click Delete to proceed. Repeat the steps for Version 4. Now, Version 4 and 5 should be marked as deleted.

  3. If version 5 was deleted by mistake and you wish to recover, select Undelete version from its menu.

Step 6: Permanently delete data

CLI command / API call using cURL / Web UI

CLI command

  1. To permanently delete a version of secret, use the destroy subcommand.

    $ vault kv destroy -versions=4 secret/customer/acme
    Success! Data written to: secret/destroy/customer/acme
    
  2. Check to verify the metadata.

    $ vault kv metadata get secret/customer/acme
    
    # ...snip...
    ====== Version 4 ======
    Key              Value
    ---              -----
    created_time     2018-04-16T00:12:25.404198622Z
    deletion_time    2018-04-16T01:04:01.160426888Z
    destroyed        true
    # ...snip...
    

    The metadata indicates that Version 4 is destroyed.

  3. If you wish to destroy all the keys and versions at secret/customer/acme, invoke the vault kv metadata delete command.

    $ vault kv metadata delete secret/customer/acme
    Success! Data deleted (if it existed) at: secret/metadata/customer/acme
    

API call using cURL

  1. To permanently delete a version of secret, use the secret/destroy/customer/acme endpoint.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data '{ "versions":[4] }'
          http://127.0.0.1:8200/v1/secret/destroy/customer/acme
    
  2. Check to verify the metadata.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/metadata/customer/acme | jq
    
    # ...snip...
      "4": {
        "created_time": "2018-04-16T00:16:18.941331243Z",
        "deletion_time": "2018-04-16T01:17:42.003111567Z",
        "destroyed": true
      },
    # ...snip...
    

    The metadata indicates that Version 4 is destroyed.

  3. If you wish to destroy all the keys and versions at secret/customer/acme, invoke the secret/metadata endpoint.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request DELETE
          http://127.0.0.1:8200/v1/secret/metadata/customer/acme
    

Web UI

To permanently delete a version of secret, simply select Permanently destroy version from its menu.

Destroy Versions

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.

CLI command / API call using cURL

CLI command

  1. For the purpose of demonstration, keep the delete_version_after to 40 seconds.

    $ vault kv metadata put -delete-version-after=40s secret/test
    Success! Data written to: secret/metadata/test
    
  2. To test, write some test data.

    $ vault kv put secret/test message="data1"
    

    Repeat and write some more data.

    $ vault kv put secret/test message="data2"
    

    Repeat again.

    $ vault kv put secret/test message="data3"
    

    NOTE: You can use upper-arrow key to recover the previously executed command.

  3. Now, check the metadata.

    $ vault kv metadata get secret/test
    
    ========== Metadata ==========
    Key                     Value
    ---                     -----
    cas_required            false
    created_time            2019-07-31T06:51:49.726188Z
    current_version         3
    delete_version_after    40s
    max_versions            0
    oldest_version          0
    updated_time            2019-07-31T06:55:54.203923Z
    
    ====== Version 1 ======
    Key              Value
    ---              -----
    created_time     2019-07-31T06:55:37.795176Z
    deletion_time    2019-07-31T06:56:17.795176Z
    destroyed        false
    
    ====== Version 2 ======
    Key              Value
    ---              -----
    created_time     2019-07-31T06:55:41.207457Z
    deletion_time    2019-07-31T06:56:21.207457Z
    destroyed        false
    
    ====== Version 3 ======
    Key              Value
    ---              -----
    created_time     2019-07-31T06:55:43.811997Z
    deletion_time    2019-07-31T06:56:23.811997Z
    destroyed        false
    
  4. Notice that the deletion_time is set on each version. After 40 seconds, the data gets deleted automatically. (NOTE: The data gets deleted, but not destroyed.)

    $ vault kv get -version=1 secret/test
    
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2019-07-31T06:55:37.795176Z
    deletion_time    2019-07-31T06:56:17.795176Z
    destroyed        false
    version          1
    

API call using cURL

  1. For the purpose of demonstration, keep the delete_version_after to 40 seconds.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data '{ "delete_version_after": "40s" }'
          http://127.0.0.1:8200/v1/secret/metadata/test
    
  2. To test, write some test data.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data '{"data": {"message": "data1"}}' \
          http://127.0.0.1:8200/v1/secret/data/test
    

    Repeat and write some more data.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data '{"data": {"message": "data2"}}' \
          http://127.0.0.1:8200/v1/secret/data/test
    

    Repeat again.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          --request POST \
          --data '{"data": {"message": "data3"}}' \
          http://127.0.0.1:8200/v1/secret/data/test
    
  3. Now, check the metadata.

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/metadata/test | jq
    {
      # ...snip...
      "data": {
        "cas_required": false,
        "created_time": "2019-07-31T07:10:17.162211Z",
        "current_version": 3,
        "delete_version_after": "40s",
        "max_versions": 0,
        "oldest_version": 0,
        "updated_time": "2019-07-31T07:11:32.750329Z",
        "versions": {
          "1": {
            "created_time": "2019-07-31T07:10:57.59863Z",
            "deletion_time": "2019-07-31T07:11:37.59863Z",
            "destroyed": false
          },
          "2": {
            "created_time": "2019-07-31T07:11:25.145534Z",
            "deletion_time": "2019-07-31T07:12:05.145534Z",
            "destroyed": false
          },
          "3": {
            "created_time": "2019-07-31T07:11:32.750329Z",
            "deletion_time": "2019-07-31T07:12:12.750329Z",
            "destroyed": false
          }
        }
      },
      # ...snip...
    }
    
  4. Notice that the deletion_time is set on each version. After 40 seconds, the data gets deleted automatically. (NOTE: The data gets deleted, but not destroyed.)

    $ curl --header "X-Vault-Token: <TOKEN>" \
          http://127.0.0.1:8200/v1/secret/data/test?version=1 | jq
    {
      ...
      "data": {
        "data": null,
        "metadata": {
          "created_time": "2019-07-31T07:10:57.59863Z",
          "deletion_time": "2019-07-31T07:11:37.59863Z",
          "destroyed": false,
          "version": 1
        }
      },
      ...
    }
    

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.

By default, Check-And-Set operation is not enabled on the KV secrets engine; therefore write is always allowed (no checking is performed).

$ vault read secret/config

Key             Value
---             -----
cas_required    false
max_versions    0

CLI command / API call using cURL / Web UI

CLI command

To enable the Check-And-Set operation.

$ vault write secret/config cas-required=true

To enable cas_required only on the secret/partner path:

$ vault kv metadata put -cas-required=true secret/partner

Once check-and-set is enabled, every write operation requires cas value to be passed. If you are sure that you want to overwrite the existing key-value, set cas to match the current version. Set cas to 0 if you want to write the secret only if the key does not exists.

Example:

Try writing data to see if the key does not already exists.

$ vault kv put -cas=0 secret/partner name="Example Co." partner_id="123456789"
Key              Value
---              -----
created_time     2018-04-16T22:58:15.798753323Z
deletion_time    n/a
destroyed        false
version          1

Now, overwrite the secret. You must specify the current version with -cas flag.

$ vault kv put -cas=1 secret/partner name="Example Co." \
        partner_id="ABCDEFGHIJKLMN"
Key              Value
---              -----
created_time     2018-04-16T23:00:28.66552289Z
deletion_time    n/a
destroyed        false
version          2

API call using cURL

To enable the Check-And-Set operation, set the cas_required parameter value to true in the API request paylod.

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

Enable cas_required on the secrets engine mounted at secret/.

$ curl --header "X-Vault-Token: <TOKEN>" \
       --request POST \
       --data @payload.json
       http://127.0.0.1:8200/v1/secret/config

To enable cas_required only on the secret/partner path, execute the following endpoint instead.

$ curl --header "X-Vault-Token: <TOKEN>" \
      --request POST \
      --data @payload.json
      http://127.0.0.1:8200/v1/secret/metadata/partner

Once check-and-set is enabled, every write operation requires cas value to be passed. If you are sure that you want to overwrite the existing key-value, set cas to match the current version. Set cas to 0 if you want to write the secret only if the key does not exists.

Example:

Create an API request payload containing some data.

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

Now, write the data at secret/data/partner.

$ curl --header "X-Vault-Token: <TOKEN>" \
       --request POST \
       --data @payload.json \
       http://127.0.0.1:8200/v1/secret/data/partner

To overwrite the secret, you must pass the current secret version in the request payload.

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

Now, try to create a new version.

$ curl --header "X-Vault-Token: <TOKEN>" \
       --request POST \
       --data @payload.json \
       http://127.0.0.1:8200/v1/secret/data/partner

Web UI

To enable the Check-And-Set operation, select the Require Check and Set check-box when you create secrets.

Destroy Versions

NOTE: UI will always write a new version with cas even if the check-and-set operation is not required.

Help and Reference