Hashicorp Vault CLI Part 8: Secrets Management and Key-Value Engine
—

Hashicorp Vault is a tool for managing secrets and encrypted data. Upon successful authentication, a policy-based system authorizes access to Vault endpoints. All configuration aspects, as well as available functional featured, can be managed via its CLI.
In an ongoing series, all CLI commands are explored systematically. The focus of this article are commands from the secret’s management group. Specifically, the management if secret engines themselves, and commands for working with encrypted data.
The technical context of this article is hashicorp_vault_v1.21.1, released 2025-11-18. All provided information and command examples should be valid with newer versions too, baring update to the syntax of CLI commands.
The background material for this article stems from the official Hashicorp Vault documentation about Vault CLI and subsequent pages, as well as information from the binary itself.
Vault CLI Overview
The Vault CLI provides more than 30 commands. For systematically explaining and contextualizing each command, they can be structured as follows.
Groups marked with a checkmark were covered in an earlier article, and the commands marked with an at sign are the focus for this article.
- ✅ Initialization
server: Starts a server processagent: Starts an agent process, a utility to communicate with a vault server to gain access to tokensproxy: Starts a vault proxy process
- ✅ Configuration
operator: Cluster management operations, including memberships, encryption and unseal keysplugin: Manage and install additional pluginsread/list: Access stored configuration and secretswrite/patch: Modify or create any datadelete: Delete configuration data or secrets
- ✅ Introspection
status: Show status information of the vault serverversion: Shows compact version information and build timestampversion-history: Shows detailed version information about all previously used vault server instancesprint: Detailed view of the vault’s server runtime configurationpath-help: Detailed documentation about API endpointsevents: Subscribe to the event stream of a running vault instancemonitor: Print vault log messagesdebug: Shows debug information of the connected Vault serveraudit: Interact with connected audit devices
- ✅ Vault Enterprise
hcp: Operate a managed Hashicorp Vault clusternamespace: Interact with configured namespaces of the cluster
- ✅ Authorization
policy: Manage policy definitions that govern all vault operationstokens: General token managementlease: Manage current token leases, including renewal, revocation and TTL modification
- ✅ Authentication
auth: Interact with configured authentication optionslogin: Authenticates access to a Vault server
- Secrets Management
- 🌀
secrets: General configuration of secret engines - 🌀
kv: Access to the essential key-value store transform: Interact with the transform secrets enginetransit: Interact with the Vaults transit secrets engineunwrap: One-time access to arbitrary encrypted datapki: Access the private key infrastructure secrets enginessh: Initiates SSH sessions via the SSH secrets engine
- 🌀
Secret Engine Management
Secrets engines manage different types of encrypted data. They can be grouped into native, applications and services, cloud provider, and encryption keys. For a detailed explanation, see my earlier article about Secret Management Engines
The secret command governs the complete life-cycle of an engine, from activation to re-configuration and dismantling. In the following sections, a kv-v2 engine will be regarded.
kv secrets enable
Every engine needs to be activated at first. A minimalist invocation of the enable command with just the secret engine name applies its default configuration including the mount path. Alternatively, all exposed configuration options can be passed as flags to customize the engine right from the start.
To activate a kv-v2 engine with default properties:
> vault secrets enable kv-v2
# Log messages
2025-12-19T09:01:22.459+0100 [INFO] core: successful mount: namespace="" path=kv-v2/ type=kv version="v0.25.0+builtin"
Custom configurations can be applied during setup. The list of options is long, and support varies by engine. Available are:
default-lease-ttl=<duration>: The TTL value for all leases issued by the enginedescription=<string>: Additional documentation for this secret engine, intended for usersexternal-entropy-access=<bool>: Allow this engine to access external entropy sourcesforce-no-cache=<bool>: Configure the caching behavior of the enginelisting-visibility=<string>: Controls if the engine should be visible to authenticated unauthenticated users. Allowed values arehiddenandunauth.local=<bool>: Secret engines configuration and values are normally replicated in the context of running Vault as a cluster. This option can disable this behavior.max-lease-ttl=<duration>: The maximum TTL of issues leases. If this time passes, the lease can not be renewed again.options=<key=value>: Additional generic options passed to the engine.path=<string>: The mount path of the engine. Needs to be unique.plugin-name=<string>: Vaults plugin nature allows developers to implement custom engine. This configuration option sets the correct plugin to use.plugin-version=<string>: Set the plugins version to use.seal-wrap: All secrets are encrypted with Vault-internal keys. This option allows to use additional keys for storing and reading secret data.version=<int>: The version of the secret engine.
Here is an example to create the kv-v2 engine with specific options.
> vault secrets enable \
-description="datacenter secrets" \
-external-entropy-access=true \
-listing-visibility=hidden \
-force-no-cache=true -path=kv2 \
-default-lease-ttl=5m \
-max-lease-ttl=1h \
kv-v2
# Log messages
2025-12-19T09:52:44.260+0100 [INFO] core: successful mount: namespace="" path=kv2/ type=kv version="v0.25.0+builtin"
kv secrets list
This command shows all currently activated secret engines. The output can be formatted as table, JSON or YAML, and the -detailed flags
> vault secrets list -format=json
# Log messages
{
"cubbyhole/": {
"uuid": "2da6947a-8eb4-8bca-7405-8d056e87d997",
"type": "cubbyhole",
"description": "per-token private secret storage",
"accessor": "cubbyhole_376f2732",
"config": {
"default_lease_ttl": 0,
"max_lease_ttl": 0,
"force_no_cache": false
},
"options": null,
"local": true,
"seal_wrap": false,
"external_entropy_access": false,
"plugin_version": "",
"running_plugin_version": "v1.21.1+builtin.vault",
"running_sha256": "",
"deprecation_status": ""
},
"identity/": {
"uuid": "985d0186-a541-5905-fabb-70352eaf55b9",
"type": "identity",
"description": "identity store",
"accessor": "identity_6676eb9f",
"config": {
"default_lease_ttl": 0,
"max_lease_ttl": 0,
"force_no_cache": false,
"passthrough_request_headers": [
"Authorization"
]
},
"options": null,
"local": false,
"seal_wrap": false,
"external_entropy_access": false,
"plugin_version": "",
"running_plugin_version": "v1.21.1+builtin.vault",
"running_sha256": "",
"deprecation_status": ""
},
"kv2/": {
"uuid": "f7192663-902b-bc9f-da68-762c82c3738b",
"type": "kv",
"description": "datacenter secrets",
"accessor": "kv_20ee0106",
"config": {
"default_lease_ttl": 300,
"max_lease_ttl": 3600,
"force_no_cache": true,
"listing_visibility": "hidden"
},
"options": {
"version": "2"
},
"local": false,
"seal_wrap": false,
"external_entropy_access": true,
"plugin_version": "",
"running_plugin_version": "v0.25.0+builtin",
"running_sha256": "",
"deprecation_status": "supported"
},
"postgres/": {
"uuid": "50dae925-f07d-184c-a824-096e5719e213",
"type": "database",
"description": "",
"accessor": "database_53cdbc55",
"config": {
"default_lease_ttl": 0,
"max_lease_ttl": 0,
"force_no_cache": false
},
"options": null,
"local": false,
"seal_wrap": false,
"external_entropy_access": false,
"plugin_version": "",
"running_plugin_version": "v1.21.1+builtin.vault",
"running_sha256": "",
"deprecation_status": ""
},
"sys/": {
"uuid": "9fc5f342-99e3-4d0a-b7c7-f3f015417a4e",
"type": "system",
"description": "system endpoints used for control, policy and debugging",
"accessor": "system_5265b4ea",
"config": {
"default_lease_ttl": 0,
"max_lease_ttl": 0,
"force_no_cache": false,
"passthrough_request_headers": [
"Accept"
]
},
"options": null,
"local": false,
"seal_wrap": true,
"external_entropy_access": false,
"plugin_version": "",
"running_plugin_version": "v1.21.1+builtin.vault",
"running_sha256": "",
"deprecation_status": ""
},
}
kv secrets move
When a secrets engine mount path should be changed, this command can be used.
Here is an example to move the kv-v2 engine to a more descriptive mount path reflecting its intended use-case:
> vault secrets move kv2 datacenter-secrets/
# Log messages
2025-12-19T10:06:07.937+0100 [INFO] core.mounts.migration: Starting to update the mount table and revoke leases: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/
2025-12-19T10:06:08.162+0100 [INFO] core.mounts.migration: Removing the source mount from filtered paths on secondaries: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/
2025-12-19T10:06:08.162+0100 [INFO] core.mounts.migration: Updating quotas associated with the source mount: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/
2025-12-19T10:06:08.162+0100 [INFO] core.mounts.migration: Completed mount move operations: from_path=kv2/ migration_id=d5c3b5d9-d0b8-4bd8-4b9a-c29e471fb0e1 namespace="" to_path=datacenter-secrets/
kv secrets tune
Once a secret engine became operational, its configuration might need a modification. The tune command accepts most command from its activations, except those that govern the encryption itself, such as external-entropy-access
To modify the kv-v2 engine:
> vault secrets tune \
-description="stores datacenter secrets" \
-listing-visibility=unauth \
-default-lease-ttl=10m datacenter-secrets
# Log messages
2025-12-19T10:23:13.086+0100 [INFO] core: mount tuning of listing_visibility successful: path=datacenter-secrets/
2025-12-19T10:23:43.231+0100 [INFO] core: mount tuning of leases successful: path=datacenter-secrets/
2025-12-19T10:23:43.351+0100 [INFO] core: mount tuning of description successful: path=datacenter-secrets/ description="stores datacenter secrets"
kv secrets disable
When the secret engine is not required anymore, it can be turned off. All existing leases will be deleted, and all stored date is removed irreversible.
> vault secrets disable datacenter-secrets
# Log messages
2025-12-19T10:29:32.553+0100 [INFO] core: successfully unmounted: path=datacenter-secrets/ namespace=""
Key-Value Secret Engine Data Commands
Vault comes with several built-in secrets engines, and the key-value store is the most generic one. Once configured, secrets in the form of key-value pairs can be stored at any nested path, allowing to reflect organizational or logical structure.
The key-value store is available in two different versions, a quick differentiation is this:
- The kv-v1 store is more runtime efficient and requires fewer storage space. Secrets are stored unversioned. When a command overwrites data at an existing path, its data is lost. Furthermore, deletion is also non-recoverable.
- The kv-v2 store adds versioning to all paths, with a default but tunable value of 10 versions. Storing data at an already defined path increments the version counter. Older versions can be read until the increments surpass the defined maximum value. Any version can be erased recoverable with the
deletecommand, or non-recoverable with thedestroycommand. Finally, when using the vault CLI generic CRUD commands, the actual paths to access the secret need to differentiate between<mount_path>/data/<name>and<mount_path>/metadata/<name>.
Assuming a CRUD lifecycle, the kv subcommands can be seperated as follows:
- Creation
put: Adds a new secret or new version of the secret at a defined path
- Reading
list: Shows all secrets at a designated path, or at subsequent path partsget: Access a specific secret and expose all its metadata
- Update
patch: Modify existing data without incrementing the versionrollback: Restores a previous version of a secretundelete: Restores a secret, or versions of a secrets, that were marked for deletionenable-versioning: Adds versioning capabilities to a secret if not present already
- Delete
delete: Removes a secret, or versions of a secret, but keeping an internal, recoverable recorddestroy: Non-recoverable erasing of a secret or versions of secrets
Only in an kv-v2 store are all CLI commands available, and will therfore be the context for this section. The store is created with the following command:
> vault secrets enable -path=kv2 kv-v2
kv put
In its simplest form, a single key-value pair can be stored at an arbitrary path. The command can include the secret data directly, which will be stored in the Shell history and therefore exposed, or read from file.
To pass secrets directly:
> vault kv put kv2/api-creds aws=75ae33a4b907bc87796
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T08:13:57.536865Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
Another option is to store the secret data in a file, and pass multiple key-value pairs to the command, where the key is determined by the command, and its value by the content of the file.
> vault kv put kv2/api-creds value=@/tmp/aws.scr.txt
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T08:33:49.738235Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2
Interestingly, the file content can even be binary, as the following example shows:
> dd if=/dev/urandom of=/tmp/binary.scr.txt bs=1 count=30
> vault kv put kv2/api-creds binary=@/tmp/binary.scr.txt
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key binary
--- -----
created_time 2025-12-20T08:42:50.150485Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 4
kv list
To get an overview about all secrets, the list command can be called with to secrets engines root-path.
> vault kv list kv2
# Log messages
Keys
----
api-creds
databases
kubernetes
kubernetes/
As the output shows, it lists the top-level paths only. When structured paths are defined, they will be shown with a trailing /. These paths need to be exposed additionally.
> vault kv list kv2/kubernetes/
# Log messages
Keys
----
datacenter1
datacenter2
datacenter3
kv get
This command retrieves all data stored at a specific path. Unless scoped, the most recent version will be returned.
Here is an example that returns the most-recent version, also showing that binary data was stored:
> vault kv get kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T09:27:46.102492Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 3
==== Data ====
Key Value
--- -----
binary 7J\Z��!k�MT�t�����c�y�M/T
To return a different version instead:
> vault kv get -version=2 kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T09:26:19.01058Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2
==== Data ====
Key Value
--- -----
value aws=75ae33a4b907bc87796
kv patch
To amend additional data to an existing data record, two variants can be used. The put command requires the complete original data and the new, additional data to be specified. The patch command instead only requires to pass the additional data. In both cases, the version will be incremented.
Here is an example that adds a comment to the stored binary data:
> vault kv patch kv2/api-creds comment="Binary executable for retrieving secrets"
The new version has to following structure:
> vault kv get kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T10:09:59.494854Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 4
===== Data =====
Key Value
--- -----
comment Binary executable for retrieving secrets
binary 7J\Z��!k�MT�t�����c�y�M/T
kv undelete
When a kv delete command for a specific version was issued, attempts to read the data will only return the metadata section with the additional attribute deletion_time.
Here is an example:
> vault kv delete -versions=4 kv2/api-creds
> vault kv get kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T10:09:59.494854Z
custom_metadata <nil>
deletion_time 2025-12-21T06:42:28.086713Z
destroyed false
version 4
The data can be restored, and read attempts succeed once again.
> vault kv undelete -versions=4 kv2/api-creds
# Log messages
Success! Data written to: kv2/undelete/api-creds
> vault kv get kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T10:09:59.494854Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 4
===== Data =====
Key Value
--- -----
comment Binary executable for retrieving secrets
value 7J\Z��!k�MT�t�����c�y�M/T
kv rollback
The rollback command accesses a previous version of a secret, and stores the secrets value at a new, incremented version.
Here is an example of a rollback to version 2:
> vault kv rollback -versions=2 kv2/api-creds
# Log messages
Key Value
--- -----
created_time 2025-12-21T06:49:06.178864Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 5
> vault kv get kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-21T06:49:06.178864Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 5
==== Data ====
Key Value
--- -----
value aws=75ae33a4b907bc87796
It is not possible to perform a rollback for a previously deleted version.
> vault kv delete -versions=4 kv2/api-creds
# Log messages
Success! Data deleted (if it existed) at: kv2/data/api-creds
# Log messages
> vault kv rollback -version=4 kv2/api-creds
Cannot roll back to a version that has been deleted
kv enable-versioning
As explained in the introductory paragraph, a kv-v1 store does not support versioned secrets. This command effectively turns the v1 to a v2 store.
Here is an example in which a kv-v1 store is created. First, the store is enabled at path kv, and two secrets stored.
> vault secrets enable -path=kv kv-v1
# Log messages
2025-12-21T08:41:57.535+0100 [INFO] core: successful mount: namespace="" path=kv/ type=kv version="v0.25.0+builtin"
Success! Enabled the kv-v1 secrets engine at: kv/
> vault kv put kv/aws api_key=@/tmp/aws.scr.txt
# Log messages
Success! Data written to: kv/aws
> vault kv put kv/encryption_binary data==@/tmp/bin
# Log messages
Success! Data written to: kv/encryption_binary
> vault kv list kv
# Log messages
Keys
----
aws
encryption_binary
> vault kv get kv/aws
# Log messages
===== Data =====
Key Value
--- -----
api_key 75ae33a4b907bc87796
Second, kv enable-versioning is applied, and a stored secret read. As shown, it now includes a metadata section, the sure sign that it is a kv-v2 secret.
> vault kv enable-versioning kv
# Log messages
2025-12-21T08:44:53.048+0100 [INFO] core: mount tuning of options: path=kv/ options=map[version:2]
Success! Tuned the secrets engine at: kv/
2025-12-21T08:44:53.244+0100 [INFO] secrets.kv.kv_845130a3: collecting keys to upgrade
2025-12-21T08:44:53.244+0100 [INFO] secrets.kv.kv_845130a3: done collecting keys: num_keys=3
2025-12-21T08:44:54.174+0100 [INFO] secrets.kv.kv_845130a3: upgrading keys finished
> vault kv list kv
# Log messages
Keys
----
aws
encryption_binary
> vault kv get kv/aws
# Log messages
== Secret Path ==
kv/data/aws
======= Metadata =======
Key Value
--- -----
created_time 2025-12-21T07:44:53.342531Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
===== Data =====
Key Value
--- -----
api_key 75ae33a4b907bc87796
kv delete
In an kv-v2 store, the delete command modifies the data-record of a versioned secret. From that moment on, its metadata obtains a timestamp in the deletion_time attribute, and performing a kv get does not show the stored data anymore. This commands results are reversible - issuing a kv undelete restores the data, as shown above.
Here is an example:
> vault kv delete -versions=4 kv2/api-creds
# Log messages
Success! Data deleted (if it existed) at: kv2/data/api-creds
> vault kv get -version=4 kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-20T10:09:59.494854Z
custom_metadata <nil>
deletion_time 2025-12-21T06:52:17.001191Z
destroyed false
version 4
kv destroy
Similar to kv delete, this command modifies the data record. Its metadata shows destroyed true, and read attempts do not return the data anymore. This change is non-reversible - the data is removed permanently.
> vault kv destroy -versions=5 kv2/api-creds
# Log messages
Success! Data written to: kv2/destroy/api-creds
midi :: work/development/vault » vault kv get kv2/api-creds
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-21T06:49:06.178864Z
custom_metadata <nil>
deletion_time n/a
destroyed true
version 5
Key-Value Secret Engine Metadata Commands
Each kv-v2 secret contains a data and a metadata section. The metadata section of a secret stored in a kv-v2 engine can be accessed and manipulated with the three subcommands get, put, and delete.
kv metadata get
Issuing a get returns the complete metadata history of a secret.
> vault kv metadata get kv2/api-creds
# Log messages
==== Metadata Path ====
kv2/metadata/api-creds
========== Metadata ==========
Key Value
--- -----
cas_required false
created_time 2025-12-20T09:26:11.531797Z
current_version 5
custom_metadata <nil>
delete_version_after 0s
last_updated_by map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
max_versions 0
oldest_version 0
updated_time 2025-12-21T06:49:06.178864Z
====== Version 1 ======
Key Value
--- -----
created_by map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:create]
created_time 2025-12-20T09:26:11.531797Z
deleted_by <nil>
deletion_time n/a
destroyed false
====== Version 2 ======
Key Value
--- -----
created_by map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
created_time 2025-12-20T09:26:19.01058Z
deleted_by <nil>
deletion_time n/a
destroyed false
====== Version 3 ======
Key Value
--- -----
created_by map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
created_time 2025-12-20T09:27:46.102492Z
deleted_by <nil>
deletion_time n/a
destroyed false
====== Version 4 ======
Key Value
--- -----
created_by map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:patch]
created_time 2025-12-20T10:09:59.494854Z
deleted_by map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
deletion_time 2025-12-21T06:52:17.001191Z
destroyed false
====== Version 5 ======
Key Value
--- -----
created_by map[actor:root client_id:0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8= operation:update]
created_time 2025-12-21T06:49:06.178864Z
deleted_by <nil>
deletion_time n/a
destroyed true
kv metadata put
Each secrets' metadata property inherits their configuration from the store itself. These properties can be changed on a per-record base, customizing especially sensitive secrets.
Following properties are configurable:
cas-required=<*bool>: CAS is an acronym for "check-and-set". When this property is enabled, all data-record updates need to include the cas attribute present, and its value needs to be that of the most recent version number. Therefore, this setting is an additional fail-safe to prevent accidental modifications of the secretdelete-version-after=<duration>: Secrets can be configured as self-destructing with this setting. When the duration passes, the most recent version of the secret will be deleted. If version increments happen before the duration expires, older records remain readable.max-versions=<int>: Per default, each secret path can be updated for 10 iterations before older data is purged. This flag modifies this property.custom-metadata=<key=value>: The metadata section of each secret can include arbitrary, custom fields to augment information for system or human user access.
Here is an example of extending the metadata record with custom fields:
> vault kv metadata put -custom-metadata="comment=API credentials for AWS" -custom-metadata="public-key=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT" kv2/api-creds
# Log messages
Success! Data written to: kv2/metadata/api-creds
> vault kv get -format=JSON kv2/api-creds
# Log messages
{
"request_id": "18da20a1-b67b-f781-ae10-a699675f3b80",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"data": {
"api_key": "75ae33a4b907bc87796\n"
},
"metadata": {
"created_time": "2025-12-21T09:36:44.286357Z",
"custom_metadata": {
"comment": "API credentials for AWS",
"public-key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT"
},
"deletion_time": "",
"destroyed": false,
"version": 4
}
},
"warnings": null,
"mount_type": "kv"
}
The following experiment shows how the duration setting auto-deletes secrets. First, the duration is set to 1min. Second, a new version is created, and the return value shows a deletion timestamp in the future.
> vault kv metadata put -delete-version-after=1m kv2/api-creds
# Log messages
Success! Data written to: kv2/metadata/api-creds
> vault kv put kv2/api-creds api_key=@/tmp/aws.scr.txt
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-21T09:43:58.324523Z
custom_metadata map[comment:API credentials for AWS public-key:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT]
deletion_time 2025-12-21T09:44:58.324523Z
destroyed false
version 6
Once the time passed, the record is deleted. Executing a undelete command makes the secret available again.
vault kv get kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-21T09:43:58.324523Z
custom_metadata map[comment:API credentials for AWS public-key:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT]
deletion_time 2025-12-21T09:44:58.324523Z
destroyed false
version 6
> vault kv undelete -versions=7 kv2/api-creds
# Log messages
Success! Data written to: kv2/undelete/api-creds
> vault kv get kv2/api-creds
# Log messages
=== Secret Path ===
kv2/data/api-creds
======= Metadata =======
Key Value
--- -----
created_time 2025-12-21T09:43:58.324523Z
custom_metadata map[comment:API credentials for AWS public-key:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHwxMpyeMvv1THOjNfBubFwtifqWO6nSZj2AS6n0fFoT]
deletion_time 2025-12-21T09:48:18.574148Z
destroyed false
version 6
===== Data =====
Key Value
--- -----
api_key aws=75ae33a4b907bc87796
kv metadata delete
This command results in the immediate, non-recoverable destruction of all versions of a secret's data and metadata.
> vault kv metadata delete kv2/api-creds
# Log messages
Success! Data deleted (if it existed) at: kv2/metadata/api-creds
All following read attempts result in an error.
> vault kv metadata get kv2/api-creds
# Log messages
No value found at kv2/metadata/api-creds
Conclusion
The Hashicorp Vault CLI is a powerful tool for setup, configuration and maintenance of a Vault server or cluster. In an ongoing article series, all CLI commands are systematically explored. The focus for this article is two commands from the secret management group. With secrets, the available engines can be activated, their configuration read and modified, and finally disabled. Similarly, for the built-in key-value store engine, the kv subcommands cover all lifecycles. Here, you learned about the differences of kv-v1 and kv-v2 stores, and saw the intricacies of secret lifecycles with recoverable and non-recoverable deletion. Finally, you also saw how a secrets metadata can be accessed and manipulated, setting e.g. a property that automatically deletes secrets after a defined duration passed. Overall, this coverage should help you to use the key-value store with its full functionality.