Sägetstrasse 18, 3123 Belp, Switzerland +41 79 173 36 84 info@ict.technology

    HashiCorp Vault 深入解析 - 第 2a 部分:启用 Key/Value Secrets Engine

    在第一部分中,我们已经对整个 Secrets Engines 生态系统进行了深入概述,现在我们将深入每一个 Vault 集群的日常操作。Key/Value(KV)Secrets Engine 是在所有需要安全存储、版本控制以及后续有针对性读取秘密数据的场景中所依赖的主力工具。

    尽管 Vault 以其动态能力而广为人知,但在现实环境中,静态值的数量同样不容小觑——从外部 SaaS 服务的 API 密钥,到无过期日期的服务账户密码,再到为遗留系统配置的证书包。这些数据若没有一个可靠的 Key/Value 存储,将依然在文本文件、Git 仓库、CI 系统,甚至是 Excel 表格中以不受控的方式存在。而这正是 Key/Value Secrets Engine 所要杜绝的情况。

    在本小节中,我们将以实践为导向,探讨以下内容:

    • 如何启用一个 Key/Value Secrets Engine,
    • 在选择挂载路径时需要考虑哪些因素,
    • 以及如何设计一个即使在五年后依然清晰易懂的层级结构。

    在接下来的小节(从第 2b 部分开始)中,我们将深入探讨 Key/Value Secrets Engine 的不同方面——包括 KV 存储的两个版本之间的区别、如何从旧版本升级到新版本、如何写入与读取数据、如何处理元数据,以及如何对已存储的 Secrets 进行版本控制、删除与恢复等操作。

    什么是 Key/Value Secrets Engine?

    Key/Value Secrets Engine(简称 KV Engine)旨在解答一个简单却无处不在的问题:

    我们应如何存储不可变的秘密数据,使其既能防止外泄、满足审计要求,又能为集成流水线、人类用户和应用程序提供便捷的访问方式?

    从技术上讲,它是一个加密的、具备事务特性的 Key-Value 存储系统,所有数据在写入底层存储之前都会被透明地以 256 位 AES 加密。

    每一次读取或写入操作都会被 Vault 的审计子系统完整记录,从而可以准确追踪是谁在何时访问了哪个路径。

    对于那些此前依赖云端 KMS 解决方案或参数存储系统的团队而言,KV Engine 往往是他们接触 Vault 的第一个切入点,因为它提供了一个熟悉的数据模型,同时还具备显著增强的安全性与自动化功能。

    两个版本,两种理念

    有一个细节经常引发困惑:该 Engine 存在两种不同的变体。

    • 版本 1(kv):传统的、非版本化变体
    • 版本 2(kv-v2):现代的、具备版本控制的变体

    版本 1,在内部简称为「kv」,每个条目仅保留当前的一个版本。覆盖写操作将不可逆地删除旧数据。这一行为在早期 Vault 的使用场景下尚能满足需求,但如今已无法符合现代的合规性要求。

    版本 2,可以通过名称 kv-v2 或参数 ‑version=2 进行配置,为每一个条目维护完整的历史记录。每一次对 Key/Value 对的修改都会生成一个新的修订版本,用户可在后续对其进行精准检索或恢复。它还支持带有定义保留期限的软删除机制,在最终销毁前保留一定时间的恢复可能性。

    因此,在现代部署中应始终使用 v2 版本。v1 仅在遗留工作负载依赖稳定 API、短期内难以迁移的情况下继续使用。

    除了版本控制功能之外,v2 还引入了其他特性:例如每个条目可以使用可选的 Check‑and‑Set 索引进行保护,从而防止所谓的 Blind Writes(盲写)。此外,还可以在服务器端设置最大保留版本数,以便在可控范围内管理存储使用与副本同步的流量。

    对于认证考试而言,必须了解:从 v1 升级到 v2 并不会自动完成。迁移过程必须主动启动,并且需要配置相应的 ACL。如若涉及到多个应用,还可能需要一个多步骤的迁移方案。同时,API 路径也会发生变化,这意味着所有相关的脚本和应用在从 v1 迁移到 v2 时都需要进行相应的调整

    实践中应注意:在新的实现中应几乎全部使用版本 2。版本 1 已被视为过时,仅用于某些特定的遗留场景。

    访问方式与集成

    Vault 的所有组件一样,KV Engine 提供三种主要的交互方式。

    • UI:用于交互式操作与快速洞察,非常适合开发与演示环境中的快速临时工作。但由于其在生产环境中并不适用,因此本系列文章不对 UI 进行深入讲解。
    • CLI:面向管理员与脚本操作。CLI 是 UI 中点击操作在生产环境中的等效替代方式。我们的代码示例将以 CLI 为基础。
    • API:用于自动化及与流水线和应用程序的集成。

    在生产环境中,通过 Infrastructure-as-Code 使用 API 是标准做法。例如,一个 Build Job 可以在部署前直接请求一个新的 Token,并通过简单的 GET 请求,从 KV 路径 apps/payment/prod/api_key 获取所需的 Secret。

    对于应用程序等工作负载,推荐使用 Vault Agent,它可将数据提供为临时文件,并自动续签应用程序的访问 Token。借助这种灵活性,KV Engine 能适配几乎任何工作流,而无需妥协核心安全目标。

    安全性内建于设计之中

    KV Engine 中的所有数据在存储前都会加密。因此无论物理后端是本地文件系统、一个 Consul 集群(几年前的标准)还是当前的标准 - 内置的 Raft 后端,对于审计人员或运维团队来说都无关紧要。即使存储系统被攻破,也无法还原明文数据。

    Vault 通过其 Policy 系统控制访问权限。每个路径都可以基于如 readcreateupdatedelete 等权限规则进行保护。典型的生产环境中,开发人员可能仅拥有类似 apps/*/prod 的读取权限,而只有运维团队才被允许写入。

    通过分配特殊的 Capabilities,如 list,可以防止非授权用户知晓子路径的存在。

    作为额外的安全层,还可以使用 Token 等级(Tiers)或 Control Groups,例如要求多因素认证以执行特别敏感的操作。

    实际启用操作

    如在第一部分中所示,启用 KV Engine 的命令为 vault secrets enable <engine>

    一旦挂载点(Mount-Point)被定义,它就成为一个独立命名空间——与其他 Engine 完全隔离。请注意,挂载点之后只能通过 vault secrets move 命令重命名,并可能需要相应地大幅调整 Policy。

    启用版本 1


    # 默认启用 KV v1
    $ vault secrets enable kv
    Success! Enabled the kv secrets engine at: kv/
    # 在自定义路径启用
    $ vault secrets enable -path=legacy-secrets kv
    Success! Enabled the kv secrets engine at: legacy-secrets/

    启用版本 2


    # 方法一:显式指定 kv-v2
    $ vault secrets enable kv-v2
    Success! Enabled the kv-v2 secrets engine at: kv-v2/
    
    # 方法二:通过参数指定版本
    
    $ vault secrets enable -path=secrets -version=2 kv
    Success! Enabled the kv-v2 secrets engine at: secrets/

    实战技巧建议

    1. 每一个挂载点应立即附加说明文字,以便同事在六个月后仍能理解其用途。

    2. 一旦明确所需的修订数量,在生产环境的 v2 挂载中设置 max_versions 是非常值得的。在需要十年数据保存期的合规行业中,该数值应相应放宽。

    3. 建议将参数 cas_required 设置为 true,以启用 Check-and-Set 机制,防止意外的并发覆盖写操作。此时每个对 Secret 的写入操作(如 createupdatepatch)都必须带有 cas 参数。该参数作为版本检查工具:仅当 cas 的值与当前 Secret 的版本一致时,写入操作才会成功执行。借此可有效管理并发写入。

    识别版本

    在大型环境中,常常会有这样的疑问:哪些挂载点已经运行在 KV Engine v2 上?以下命令可在 »Options« 一栏中提供答案。


    $ vault secrets list --detailed
    Path          Type    Accessor              Description    Options
    ----          ----    --------              -----------    -------
    cubbyhole/    cubbyhole    cubbyhole_abc123    [...]         map[]
    kv/           kv      kv_def456            [...]         map[]
    secrets/      kv      kv_ghi789            [...]         map[version:2]

    是的,您说得没错:这些信息需要主动了解才能发现。

    若在 map[] 中缺少 version:2,则说明该实例仍为旧版 v1。

    在自动化脚本中,建议对该输出进行 version:2 的机器可读解析,而不仅仅依赖路径名称。原因在于 KV Engine 的路径名称(挂载点)是可自定义的,而且用户完全可能将一个 v1 挂载命名为 kv-v2

    路径概念与隔离

    每一个已启用的 KV Engine 都构成一个独立的命名空间。这意味着:

    • 区分大小写:路径 kv/ 与 KV/ 被视为不同且完全独立的命名空间,因为 Vault 区分大小写。
    • 无重叠:在 secrets/ 上挂载后,不能再对其子路径如 secrets/sub/ 进行挂载。这种机制可防止不同团队因命名空间分配理解不同而意外地向相同目录写入数据。
    • 完全隔离:在底层,每个 Engine 都获得一个随机生成的 UUID 路径,其运作方式类似于 chroot。即便某个挂载点被攻破,也无法访问其他 Engine 的数据。

    Secrets 的结构设计

    合理的命名约定有助于保持访问路径简洁、职责清晰,并最终减少支持请求。一个常见的结构模式是 »//<secret‑group>/«。实际应用中可能如下所示:


    apps/
      └── aws/
          ├── prod/
          │   ├── user: dbadmin
          │   ├── password: P@ssw0rd
          │   └── api_key: b93md83mdmapw
          └── dev/
              ├── cert: -----BEGIN CERTIFICATE-----
              └── key: -----BEGIN PRIVATE KEY-----

    上述目录 apps/aws/ 仅作为容器存在,其中并不包含 Secret。只有最底层节点才保存真正的键值对数据。

    该层级结构的优势在于权限委派简便。例如,开发团队可以被授予对路径名中包含 dev 的所有路径的读取权限,而运维团队则拥有其所属命名空间内生产路径的写入权限(命名空间是 Vault Enterprise 中彼此隔离的虚拟 Vault 实例)。

    权限模型

    使用 KV Engine 时涉及五种不同的权限类型,称为 Capabilities

    • create:创建新 Secret(在 v1 中会覆盖现有项)
    • update:修改已有 Secret(仅适用于 v2)
    • read:读取 Secret(返回明文值)
    • delete:删除 Secret,可为 softhard 删除
    • list:列出某路径下可用 Secret(仅列出键,不含值)

    这些 Capabilities 是通过 Policy 分配的——我们将在后续文章中深入探讨此话题。在此简要说明:Policy 将这些权限组合并绑定至 Token 角色。举例来说,您可以允许一个 CI 系统在构建阶段对路径 apps/build 进行写操作,而在发布阶段则仅限于对 apps//prod 的读取权限。

    展望

    本文中我们学习了 Key/Value Secrets Engine 的基础知识,并理解了版本 1 与版本 2 之间的重要差异。

    下一部分我们将深入实践应用:如何存储、读取与管理 Secrets?路径结构设计有哪些最佳实践?以及如何充分发挥 KV v2 的版本控制能力?

    敬请期待——干货即将登场!