HashiCorp Vault — Metadata & Versioning

3 minute read

KV v2 provides rich metadata and version control for every secret. This page covers how to work with versions, custom metadata, delete/destroy operations, and version lifecycle management.


Version Lifecycle

Write v1 → Write v2 → Write v3 → Soft-Delete v2 → Undelete v2 → Destroy v1
   │          │          │           │                 │              │
   ▼          ▼          ▼           ▼                 ▼              ▼
 Active    Active     Active    v2 marked as       v2 restored    v1 data
                     (current)  deleted (data       (accessible    permanently
                                preserved)          again)         gone

View All Versions

# Write multiple versions
vault kv put secret/app password="v1-pass"
vault kv put secret/app password="v2-pass"
vault kv put secret/app password="v3-pass"

# View metadata with version info
vault kv metadata get secret/app

Expected output:

======= Metadata =======
Key                     Value
---                     -----
cas_required            false
created_time            2024-01-15T10:00:00.000000Z
current_version         3
custom_metadata         <nil>
delete_version_after    0s
max_versions            0
oldest_version          0
updated_time            2024-01-15T10:05:00.000000Z

====== Version 1 ======
Key              Value
---              -----
created_time     2024-01-15T10:00:00.000000Z
deletion_time    n/a
destroyed        false

====== Version 2 ======
Key              Value
---              -----
created_time     2024-01-15T10:02:00.000000Z
deletion_time    n/a
destroyed        false

====== Version 3 ======
Key              Value
---              -----
created_time     2024-01-15T10:05:00.000000Z
deletion_time    n/a
destroyed        false

Custom Metadata

Custom metadata is attached to the secret (not a specific version) and is useful for tagging, ownership, and compliance:

# Set custom metadata
vault kv metadata put \
    -custom-metadata=owner="sre-team" \
    -custom-metadata=environment="production" \
    -custom-metadata=compliance="pci-dss" \
    -custom-metadata=last-rotated="2024-01-15" \
    secret/payments/stripe-key

# View custom metadata
vault kv metadata get secret/payments/stripe-key
# Update a single custom metadata field
vault kv metadata patch \
    -custom-metadata=last-rotated="2024-02-15" \
    secret/payments/stripe-key

API equivalent:

curl --header "X-Vault-Token: $VAULT_TOKEN" \
     --request POST \
     --data '{
       "custom_metadata": {
         "owner": "sre-team",
         "environment": "production"
       }
     }' \
     "$VAULT_ADDR/v1/secret/metadata/payments/stripe-key"

Version Limits

Max Versions

Control how many versions are retained:

# Set max versions for a specific secret
vault kv metadata put -max-versions=5 secret/app

# Set default for the entire mount
vault secrets tune -options=max_versions=10 secret/

# When version 6 is written, version 1 is automatically deleted

Delete Version After (Auto-Expiry)

Automatically soft-delete versions after a time period:

# Versions auto-expire after 30 days
vault kv metadata put -delete-version-after=720h secret/app

# Set at mount level
vault secrets tune -options=delete_version_after=720h secret/

Version States

A version can be in one of three states:

State Description Can Read? Can Recover?
Active Normal, accessible version ✅ N/A
Soft-deleted Marked as deleted, data preserved ❌ ✅ undelete
Destroyed Data permanently removed ❌ ❌
# Active → Soft-deleted
vault kv delete -versions=2 secret/app

# Soft-deleted → Active
vault kv undelete -versions=2 secret/app

# Active → Destroyed (PERMANENT)
vault kv destroy -versions=1 secret/app

Patch Operations

Update specific fields without replacing the entire secret:

# Write initial secret
vault kv put secret/app host="db.local" port="5432" user="admin" pass="old"

# Patch only the password (other fields preserved)
vault kv patch secret/app pass="new-password"

# Verify — all fields present, pass updated
vault kv get secret/app

Expected output:

====== Data ======
Key     Value
---     -----
host    db.local
pass    new-password
port    5432
user    admin

API equivalent:

curl --header "X-Vault-Token: $VAULT_TOKEN" \
     --header "Content-Type: application/merge-patch+json" \
     --request PATCH \
     --data '{"data": {"pass": "new-password"}}' \
     "$VAULT_ADDR/v1/secret/data/app"

Comparing Versions

# Read version 1
vault kv get -format=json -version=1 secret/app | jq '.data.data'

# Read version 2
vault kv get -format=json -version=2 secret/app | jq '.data.data'

# Quick diff with diff command
diff <(vault kv get -format=json -version=1 secret/app | jq -S '.data.data') \
     <(vault kv get -format=json -version=2 secret/app | jq -S '.data.data')

Rollback to a Previous Version

Vault doesn’t have a built-in rollback command, but you can re-write an old version’s data:

# Get the data from version 1
OLD_DATA=$(vault kv get -format=json -version=1 secret/app | jq -c '.data.data')

# Write it as a new version
echo "$OLD_DATA" | vault kv put secret/app -

# Or use the API
curl --header "X-Vault-Token: $VAULT_TOKEN" \
     --request POST \
     --data "{\"data\": $OLD_DATA}" \
     "$VAULT_ADDR/v1/secret/data/app"

Quick Reference

Command Description
vault kv metadata get secret/path View all version metadata
vault kv get -version=N secret/path Read specific version
vault kv metadata put -max-versions=N secret/path Set max versions
vault kv metadata put -delete-version-after=Nh secret/path Auto-expire
vault kv metadata put -custom-metadata=k=v secret/path Custom metadata
vault kv metadata patch -custom-metadata=k=v secret/path Update metadata
vault kv patch secret/path key=value Update single field
vault kv delete -versions=N secret/path Soft-delete
vault kv undelete -versions=N secret/path Restore
vault kv destroy -versions=N secret/path Permanent destroy
vault kv metadata delete secret/path Delete everything

Next: Practice Exercises