Key/Value Secrets Engine 是几乎所有 Vault 实施中的核心组件。它为静态 secrets 的安全存储提供基础,并且在实际应用中,其使用频率远高于许多动态引擎。
在第 2a 部分的理论介绍之后,本文将聚焦于 KV Engine 的实际操作。我们将演示如何写入、读取、更新和删除 secrets,并结合实际案例分析 KV 版本 1 与版本 2 之间的差异。本文重点在于与生产环境相关的命令、常见陷阱以及日常运维中的具体建议,因此我将这些知识以教程与速查表相结合的形式呈现给您。
vault kv 命令:您日常工作的工具箱
与 KV Engine 的交互是通过 vault kv 命令完成的。该命令在内部对不同版本进行了抽象,但在输出结果中会清楚地显示使用的是 v1 还是 v2 引擎。基本的子命令可以分为两个类别:
基本操作(适用于 KV v1 和 v2):
- put:写入或更新 secrets
- get:读取 secrets
- delete:删除 secrets(不一定是永久删除)
- list:列出所有可用路径
扩展操作(仅适用于 KV v2):
- patch:更新单个键而不覆盖其他键
- rollback:回滚到特定版本
- undelete:恢复已删除的版本
- destroy:永久销毁指定版本
这些扩展命令仅在 KV v2 中可用,因为它们依赖于内部存储的元数据。而旧的 v1 版本尚不支持元数据功能。
使用 put 写入 Secrets:简单但暗藏陷阱
vault kv put 写入命令是向 KV Engine 写入 secrets 的核心方法:
vault kv put <路径> <键>=<值> [<键>=<值>...]
一个简单示例,我们首先启用 Secrets Engines:
$ vault secrets enable kv
Success! Enabled the kv secrets engine at: kv/
$
$ vault secrets enable -path=kvv2 kv-v2
Success! Enabled the kv-v2 secrets engine at: kvv2/
接下来的命令将在路径 kv/app/db 下存储一个 key 为 pass、值为 123 的 secret。根据所用版本的不同,输出结果也有所差异:
KV 版本 1:
$ vault kv put kv/app/db pass=123 Success! Data written to: kv/app/db $
KV 版本 2:
$ vault kv put kvv2/app/db pass=123 == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T10:52:07.551872783Z custom_metadata <nil> deletion_time n/a destroyed false version 1 $
版本 2 会返回元数据,例如创建时间和当前版本号。
重要提示:在 KV v2 中,数据是存储在一个内部路径下,并带有额外前缀 /data/。这在使用 API 进行操作时尤其需要注意,因为这意味着您无法简单地从 v1 迁移到 v2,而不对脚本和应用程序进行必要的调整!
同时写入多个 Key/Value 对
单个 put 命令可以写入任意数量的键:
$ vault kv put kv/app/db pass=123 user=admin api=myapisecret Success! Data written to: kv/app/db $ vault kv put kvv2/app/db pass=123 user=admin api=myapisecret == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T10:56:04.624145825Z custom_metadata <nil> deletion_time n/a destroyed false version 2 [rramge@ol9 terraform-vault-kv]$
在实际操作中,这种方式可以减少命令数量,但请不要忘记:路径下的所有内容都会被替换。为避免出错并提高脚本的可读性,在不确定的情况下,建议分步骤操作 - 或者使用下一节介绍的文件方式。
从 JSON 文件导入 Secrets
尤其在自动化环境或复杂的 secret 结构中,推荐使用文件导入多个 Key/Value 对:
$ vault kv put kv/app/db @secrets.json
secrets.json 文件示例内容:
{ "pass": "123", "user": "admin", "api": "myapisecret" }
put 与 patch 的重要区别
许多 Vault 用户在早期都会犯一个严重错误:他们使用 put 来更新单个值。
这在 KV v1 和 v2 中都会导致已存在的数据被删除,只保留 put 命令中提供的字段。
示例:
# 初始状态
$ vault kv put kv/app/db pass=123 user=admin api=myoldapisecret
Success! Data written to: kv/app/db
$
# 更新 API 密钥(错误地使用了 "put") $ vault kv put kv/app/db pass=123 user=admin api=myoldapisecret
Success! Data written to: kv/app/db
$
# 结果:只剩下 "api" $ vault kv get kv/app/db
=== Data ===
Key Value
--- -----
api mynewapisecret
$
user 和 pass 两个键因此被删除。
在希望保留现有 user 和 pass 键的场景下,vault kv put 显然不是正确的命令。只有 patch 命令可以避免删除未变更的键。我们将在下文进一步介绍 patch。现在请记住,put 是具有破坏性的:
重要: vault kv put 命令始终会替换指定路径下的全部数据。它不是合并操作!
使用 get 读取 Secrets:窥视存储内容
通过 vault kv get 命令可以读取 secrets。默认情况下总是显示最新版本。
例如,以下命令:
vault kv get kv/app/db
对于 KV v1:
$ vault kv get kv/app/db
==== Data ====
Key Value
--- -----
api myapisecret
pass 123
user admin
$
对于 KV v2:
$ vault kv get kvv2/app/db
== Secret Path ==
kvv2/data/app/db
======= Metadata =======
Key Value
--- -----
created_time 2025-07-01T10:56:04.624145825Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2
==== Data ====
Key Value
--- -----
api myapisecret
pass 123
user admin
$
提示:在 KV v2 中,路径中同样会出现 /data/。如果您打算在脚本或应用中解析该输出,请务必考虑到这一点。
用于脚本和自动化的 JSON 输出
使用 -
format=json 可以获取机器可读的数据格式:
$ vault kv get -format=json kv/app/db { "request_id": "7de43863-d7c4-837f-b59c-4b5e546a1d65", "lease_id": "", "lease_duration": 2764800, "renewable": false, "data": { "api": "myapisecret", "pass": "123", "user": "admin" }, "warnings": null, "mount_type": "kv" } $
您可以将其与 jq 工具结合使用:
$ vault kv get -format=json kv/app/db | jq -r '.data.api' myapisecret $
专业提示:可以通过设置环境变量 VAULT_FORMAT 将默认输出格式永久设置为 JSON:
export VAULT_FORMAT=json
使用 -version 读取指定版本的 Secrets
get 的读取行为相对简单,但前提是理解其版本处理机制。规则如下:
- 普通的 get 命令总是返回最新版本。
- 对于被删除的 secrets(KV v2),您只能获取元数据,无法获取数据内容。
- KV v2 支持通过 --version=X 参数获取指定版本的 secrets。
示例:读取某个 Secret 的特定版本:
$ vault kv get --version=1 kvv2/app/db == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T10:52:07.551872783Z custom_metadata <nil> deletion_time n/a destroyed false version 1 ==== Data ==== Key Value --- ----- pass 123 $ vault kv get --version=2 kvv2/app/db == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T10:56:04.624145825Z custom_metadata <nil> deletion_time n/a destroyed false version 2 ==== Data ==== Key Value --- ----- api myapisecret pass 123 user admin $
我已经多次强调,但这个事实再怎么强调也不为过:Secrets 的版本控制仅在 KV v2 中可用。 这也意味着,只有 KV v2 Secrets Engine 才支持查看单个 Secret 的历史记录。这在后续审计中可能非常关键,例如用于确认 secrets 是否确实进行了轮换,以及轮换的频率。
Secrets 的精确更新与恢复
patch:精确更新指定键
本文前文已明确指出,put 命令会完全覆盖已有 secrets,并可能导致原有数据被删除。请务必使用 patch 来单独修改某个键的值,而不会影响其他数据。
由于 KV v1 缺乏元数据支持,因此无法使用 patch:
$ vault kv patch kv/app/db user=dbadmin
KV engine mount must be version 2 for patch support
$
而 KV v2 则完全支持:
$ vault kv patch kvv2/app/db user=dbadmin == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T11:07:50.365256138Z custom_metadata <nil> deletion_time n/a destroyed false version 3 $
结果:仅 user 键被更新,其他 secret 数据保持不变。
rollback:安全地回滚版本
不小心保存了错误的 secret?误用了 vault kv put?在 KV v2 中没关系。如果您误操作了数据,可以使用 rollback 回滚至旧版本:
$ vault kv get kvv2/app/db == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T11:07:50.365256138Z custom_metadata <nil> deletion_time n/a destroyed false version 3 ==== Data ==== Key Value --- ----- api myapisecret pass 123 user dbadmin $
$ vault kv rollback -version=2 kvv2/app/db
Key Value --- ----- created_time 2025-07-01T11:10:44.341592593Z custom_metadata <nil> deletion_time n/a destroyed false version 4 $
此操作会根据指定旧版本的数据创建一个新版本。在本示例中:
- 版本 2 包含最初的数据内容
- 版本 3 是错误的覆盖操作
- 版本 4 是根据版本 2 的内容生成的回滚版本
专业提示:这并不会将 Secret 回滚至版本 2 创建时的系统状态,而是基于版本 2 的数据生成一个新版本。历史记录将完整保留。
删除数据:delete 与 destroy
请务必牢记:delete 命令在 KV v1 和 v2 中的行为截然不同。
KV v1:彻底的 delete
在使用 KV v1 Secrets Engine 的情况下,delete 命令会永久删除数据:
$ vault kv delete kv/app/db
Success! Data deleted (if it existed) at: kv/app/db
$
$ vault kv get kv/app/db
No value found at kv/app/db
$
数据已被彻底删除,无法恢复。若需恢复,只能通过恢复 Vault 快照来实现。
KV v2:软 delete
在 KV v2 中,delete 只是软删除:
$ vault kv delete kvv2/app/db
Success! Data deleted (if it existed) at: kvv2/data/app/db
$
$ vault kv get kvv2/app/db
== Secret Path ==
kvv2/data/app/db
======= Metadata =======
Key Value
--- -----
created_time 2025-07-01T11:10:44.341592593Z
custom_metadata <nil>
deletion_time 2025-07-01T11:13:33.705332433Z
destroyed false
version 4
$
虽然数据本身不再可见,但仍然保存在系统中。元数据依然存在,特别注意新增的 deletion_time 字段,以前该字段为 n/a。
已软删除的 Secrets 可通过 undelete 恢复,或通过 destroy 永久删除。
undelete:恢复已删除的版本
如果误删了某个 Secret:
$ vault kv undelete -versions=4 kvv2/app/db Success! Data written to: kvv2/undelete/app/db $ $ vault kv get kvv2/app/db == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T11:10:44.341592593Z custom_metadata <nil> deletion_time n/a destroyed false version 4 ==== Data ==== Key Value --- ----- api myapisecret pass 123 user admin $
数据再次变得可见,前提是尚未执行过 destroy。
destroy:永久删除指定版本
重要提示: destroy 是不可逆操作 - undelete 和 rollback 都无法再使用。因此仅在完全确认的情况下执行,并且绝不可与 delete 混淆!
一旦执行 destroy,将无法恢复数据:
$ vault kv destroy -versions=2 kvv2/app/db Success! Data written to: kvv2/destroy/app/db $
您也可以同时销毁多个版本:
$ vault kv destroy -versions=1,3,4 kvv2/app/db Success! Data written to: kvv2/destroy/app/db $
执行 destroy 后,destroyed 字段将显示为 true:
$ vault kv get kvv2/app/db == Secret Path == kvv2/data/app/db ======= Metadata ======= Key Value --- ----- created_time 2025-07-01T11:10:44.341592593Z custom_metadata <nil> deletion_time n/a destroyed true version 4 $
该字段主要用于审计目的。此时无法再执行 rollback 或 undelete,它是 destroy 操作已执行的明确标志。
日常实践建议与 KV 策略
1. 使用版本控制
定期检查您所使用的 KV 版本:
$ vault secrets list --detailed | grep kv kv/ kv kv_11a18e23 system system false replicated false false map[] n/a 4a479e06-6b44-e721-bbff-5ab3df0b7134 n/a v0.21.0+builtin n/a supported kvv2/ kv kv_be4ef9a0 system system false replicated false false map[version:2] n/a 390c2b5f-45cd-6774-ae17-3d643d951401 n/a v0.21.0+builtin n/a supported $
在“Options”列(倒数第二列)中查找 version:2。
2. 使用结构化路径
在 Secret Engine 路径中制定一致的命名规范,例如:
apps/ └── payment-service/ ├── prod/ │ ├── db-credentials │ └── api-keys └── dev/ ├── db-credentials └── api-keys
3. 始终使用 patch 替代 put
当需要更新单个键值时,请始终使用 patch 而非 put,以避免不必要的数据丢失。
4. 在自动化流程中使用 JSON 输出
在脚本中统一采用如下方式:
vault kv get -format=json kv/app/db | jq -r '.data.password'
5. 制定回滚策略
记录关键版本并准备好回滚流程。例如:
# 记录当前版本 $ CURRENT_VERSION=$(vault kv get -format=json kvv2/app/db | jq -r '.data.metadata.version') $ echo $CURRENT_VERSION 5 # 若出现问题:执行回滚 $ vault kv rollback --version=$((CURRENT_VERSION-1)) kvv2/app/db
避免常见错误来源
1. “write is not merge” 错误
最常见的问题:不小心使用 put 覆盖数据,而不是使用 patch。
2. KV v2 中的路径混淆
请时刻牢记:
- KV v2 中路径区分大小写,容易造成混淆
- KV v2 在内部路径中自动插入 /data/。虽然 CLI 会做抽象处理,但在直接调用 API 时必须自行处理该前缀。
3. 混淆 delete 与 destroy
- delete = KV v2 中的软删除(可恢复),KV v1 中为硬删除
- destroy = KV v2 中的硬删除(不可恢复)
4. delete 后版本号不会重置
KV v2 中的版本号是递增的,即使执行了 delete 也不会重置或回滚。
结语
Key/Value Secrets Engine 原理简洁,但在实际使用中充满细节。对版本机制、各类命令及其副作用的深刻理解,是实现稳定安全运行的关键。
在实际操作中,谁能谨慎使用 put,精确使用 patch 进行维护,并在必要时掌握 rollback,谁就能真正驾驭 KV Engine 这项工具。而能在非必要时避免使用 destroy 的人,将在误操作发生时保留恢复的余地,避免陷入万劫不复之境。
实践口诀:在 KV v2 中几乎所有内容都可以恢复 - 除了执行了 destroy 之后。
充分利用这一安全机制,同时也应在流程设计上做到未雨绸缪。