hashi_vault 查找迁移

本指南旨在帮助您从 hashi_vault 查找插件 迁移到此集合中的较新内容。

要了解原因,请参阅本页描述插件的历史和未来

关于查找与模块的说明

由于 hashi_vault 插件是查找插件,因此最直接的方式通常是使用其他查找插件替换它。以前没有模块选项可用,但现在有了。

虽然可能涉及更多,但请考虑每个用例,以确定模块是否更合适。

有关详细信息,请参阅查找指南

一般更改

本节将介绍一些与特定场景无关的一般差异。

选项:直接 vs. 项字符串

很长一段时间以来,hashi_vault 查找将其所有选项作为项字符串内的 name=value 字符串,因此您将使用一个看起来像 secret/data/path auth_method=userpass username=my_user password=somepass 的单个字符串进行查找。

不鼓励这种传递选项的方式,并且 hashi_vault 已更新(在本集合存在之前)以支持将选项作为单独的关键字参数传递。保留了项字符串方法以实现向后兼容。

注意

此集合中的其他查找插件都不支持旧样式的项字符串语法,因此强烈建议更改为直接选项。

如果现有查找在项字符串中使用选项,您可能希望先更改为直接使用选项,然后再尝试更改插件,**尤其是在您打算继续使用查找而不是模块的情况下**。

项字符串样式示例

- name: Term string style
  vars:
    user: my_user
    pass: '{{ my_secret_password }}'
    mount: secret
    relpath: path
  ansible.builtin.debug:
    msg:
      - "Static: {{ lookup('community.hashi_vault.hashi_vault', 'secret/data/path auth_method=userpass username=my_user password=somepass') }}"
      - "Variables: {{ lookup('community.hashi_vault.hashi_vault', mount ~ '/data/' ~ path ~ ' auth_method=userpass username=' ~ user ~ ' password=' ~ pass) }}"
      #                                          note these necessary but easy to miss spaces ^                                          ^

以及转换为直接选项的相同查找

- name: Direct option style
  vars:
    user: my_user
    pass: '{{ my_secret_password }}'
    mount: secret
    relpath: path
  ansible.builtin.debug:
    msg:
      - "Static: {{ lookup('community.hashi_vault.hashi_vault', 'secret/data/path', auth_method='userpass', username='my_user', password='somepass') }}"
      - "Variables: {{ lookup('community.hashi_vault.hashi_vault', mount ~ '/data/' ~ path, auth_method='userpass', username=user, password=pass) }}"

键取消引用

在这些示例中,我们将假设我们的结果字典具有以下结构

key_1: value1
'key-2': 2
'key three': three

hashi_vault 还支持使用冒号 : 的字典取消引用语法,因此经常会看到这样的代码

- ansible.builtin.debug:
    msg:
      - "KV1 (key1): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret:key_1') }}"
      - "KV2 (key1): {{ lookup('community.hashi_vault.hashi_vault', 'kv2_mount/data/path/to/secret:key_1') }}"

使用上述语法,仅返回 key_1 的_值_。请注意,无法通过这种方式检索 key three,因为空格是项字符串选项的分隔符。

注意

集合中的任何其他查找都不支持冒号 : 语法,并且不鼓励使用该语法。

冒号 : 的使用不对应于任何服务器端过滤或其他优化,因此除了紧凑语法之外,使用它没有任何优势。

冒号 : 语法始终可以使用 Jinja2 模板中的直接取消引用来替换。可以直接使用 Jinja2 点 . 语法(对键名称有限制)或通过方括号 [] 进行直接取消引用,如下所示(KV 版本无关紧要)

- vars:
    k1: key_1
    k2: key-2
    k3: key three
  ansible.builtin.debug:
    msg:
      - "KV1 (key1, dot): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret').key_1 }}"
      - "KV1 (key1, [ ]): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret')['key_1'] }}"
      - "KV1 (var1, [ ]): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret')[k1] }}"
      - "KV1 (key2, [ ]): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret')['key-2'] }}"
      - "KV1 (var2, [ ]): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret')[k2] }}"
      - "KV1 (key3, [ ]): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret')['key three'] }}"
      - "KV1 (var3, [ ]): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret')[k3] }}"

请注意,只有 key_1 可以使用点 . 语法,因为允许用于该语法的字符仅限于 Python 符号允许的字符。变量也不能与点 . 访问一起使用。

此外,冒号 : 语法鼓励为了获取不同的键而多次查找同一个密钥,导致向 Vault 发送多个相同的请求。上面的示例也存在这个问题

一种更 DRY 的方法可能如下所示:

- vars:
    secret: "{{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret') }}"
    k1: key_1
    k2: key-2
    k3: key three
  ansible.builtin.debug:
    msg:
      - "KV1 (key1, dot): {{ secret.key_1 }}"
      - "KV1 (key1, [ ]): {{ secret['key_1'] }}"
      - "KV1 (var1, [ ]): {{ secret[k1] }}"
      - "KV1 (key2, [ ]): {{ secret['key-2'] }}"
      - "KV1 (var2, [ ]): {{ secret[k2] }}"
      - "KV1 (key3, [ ]): {{ secret['key three'] }}"
      - "KV1 (var3, [ ]): {{ secret[k3] }}"

这看起来好多了,并且从可读性的角度来看是这样,但实际上它的操作方式完全相同,每次引用 secret 时都会发出新的请求。这是由于 Ansible 中的惰性模板评估造成的,在 查找指南 中有更详细的讨论。可以通过使用 ansible.builtin.set_fact 来设置 secret 变量,或者使用模块执行读取来解决此问题。

如果您大量使用冒号 : 语法,建议在继续使用其他插件之前更新它。

返回格式

注意

return_format 选项在其他插件中不受支持。如果当前正在使用它,建议将其替换为 Jinja2。

hashi_vault 查找采用一个 return_format 选项,其默认值为 dict。查找始终查找 data 字段(有关详细信息,请参阅KV 响应详细信息),默认情况下返回该字段。

return_formatraw 值会给出请求的原始 API 响应。例如,这可以用于从 KV2 请求中获取通常会被剥离的元数据,或者可以用于从其响应恰好类似于 KV 响应(具有一个或多个 data 结构)的非 KV 路径读取,并因此被解释为 KV 响应。

对于读取非 KV 路径,还有其他选项可用

有关访问 KV2 元数据的信息,请参阅关于 KV 替换 的部分。

return_format 选项也可以设置为 values 以返回字典值的列表。

这可以用 Jinja2 替换。我们将再次使用我们的示例密钥

key_1: value1
'key-2': 2
'key three': three

并查看与 return_format 的用法

# show a list of values, ['value1', 2, 'three']
- ansible.builtin.debug:
    msg:
      - "KV1: {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret', return_format='values') }}"

# run debug once for each value
- ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ query('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret', return_format='values') }}"

我们可以使用 Jinja2 做同样的事情

# show a list of values
- ansible.builtin.debug:
    msg:
      - "KV1: {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret').values() | list }}"

# run debug once for each value
- ansible.builtin.debug:
    msg: "{{ item }}"
  loop: "{{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret').values() | list }}"

Vault KV 读取

hashi_vault 查找最常见的用途是从 KV 密钥存储中读取密钥。

- ansible.builtin.debug:
    msg:
      - "KV1: {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret') }}"
      - "KV2: {{ lookup('community.hashi_vault.hashi_vault', 'kv2_mount/data/path/to/secret') }}"

两者的返回值都是密钥中键/值对的字典,不包含 API 响应中的其他信息,也不包含元数据(对于 KV2)。

KV1 和 KV2 响应结构

在底层,KV 存储的版本 1 和版本 2 的返回格式不同。

这是一个示例 KV1 响应

{
    "auth": null,
    "data": {
        "Key1": "val1",
        "Key2": "val2"
    },
    "lease_duration": 2764800,
    "lease_id": "",
    "renewable": false,
    "request_id": "e26a7521-e512-82f1-3998-7cc494f14e86",
    "warnings": null,
    "wrap_info": null
}

这是一个示例 KV2 响应

{
    "auth": null,
    "data": {
        "data": {
            "Key1": "val1",
            "Key2": "val2"
        },
        "metadata": {
            "created_time": "2022-04-21T15:56:58.8525402Z",
            "custom_metadata": null,
            "deletion_time": "",
            "destroyed": false,
            "version": 2
        }
    },
    "lease_duration": 0,
    "lease_id": "",
    "renewable": false,
    "request_id": "15538d55-0ad9-1c39-2f4b-dcbb982f13cc",
    "warnings": null,
    "wrap_info": null
}

hashi_vault 查找传统上返回它正在读取的任何内容的 data 字段,然后插件更新到其当前行为,其中它查找嵌套的 data.data 结构,如果找到,它仅返回内部的 data。这旨在始终以一致的格式从 KV1 和 KV2 返回密钥数据,但这意味着无法访问 KV2 元数据中的任何其他信息。

KV1 和 KV2 API 路径

KV1 的 API 路径直接将密钥路径连接到挂载点。因此,例如,如果一个 KV1 引擎挂载在 kv/v/1(挂载路径可以包含 /),并且在该存储中创建了一个位于 app/deploy_key 的密钥,则该路径将为 kv/v/1/app/deploy_key

在 KV2 中,有单独的路径来处理密钥的数据和元数据,因此需要在挂载点和路径之间插入额外的 /data//metadata/ 组件。

例如,如果一个 KV2 存储挂载在 kv/v/2,并且一个密钥位于 app/deploy_key,则读取密钥数据的路径为 kv/v/2/data/app/deploy_key。对于元数据操作,路径为 kv/v/2/metadata/app/deploy_key

由于 hashi_vault 对 API 路径执行通用读取,因此任何使用它的人都必须知道将它们插入路径中,这会导致很多混乱。

KV2 密钥版本

由于 KV2 是一个版本化的密钥存储,因此通常存在同一密钥的多个版本。使用 hashi_vault 查找没有专门的方法来获取除最新密钥(默认)之外的任何密钥,但是文档建议可以将 ?version=2 添加到路径以获取密钥版本 2。这确实有效,但它直接修改了 API 路径,因此不被视为稳定选项。集合中的专用 KV2 内容将此作为首要选项支持。

KV 获取替换

从集合版本 2.5.0 开始,添加了 vault_kv1_getvault_kv2_get 查找和模块

这些专用插件清楚地分离了 KV1 和 KV2 操作。这确保了它们的行为清晰且可预测。

就 API 路径而言,这些插件采用了大多数 Vault 客户端库的方法,并由 HashiCorp 推荐,即接受挂载点作为选项 (engine_mount_point),与要读取的路径分开。这确保了将在内部构建正确的路径,并且不需要调用方在 KV2 上插入 /data/

对于返回值,KV 插件不再返回直接密钥。相反,KV1 和 KV2 的返回值以及模块和查找形式都已统一,以便可以轻松访问密钥、完整的 API 响应以及响应的其他部分。

返回值直接在每个插件的文档中的返回和示例部分中介绍。

示例

以下是一些 KV 前后示例。

我们将回到我们的示例密钥

key_1: value1
'key-2': 2
'key three': three

和一些用法

- name: Reading secrets with hashi_vault and colon dereferencing
  ansible.builtin.debug:
    msg:
      - "KV1 (key1): {{ lookup('community.hashi_vault.hashi_vault', 'kv1_mount/path/to/secret:key_1') }}"
      - "KV2 (key1): {{ lookup('community.hashi_vault.hashi_vault', 'kv2_mount/data/path/to/secret:key_1') }}"

- name: Replacing the above
  ansible.builtin.debug:
    msg:
      - "KV1 (key1): {{ lookup('community.hashi_vault.vault_kv1_get', 'path/to/secret', engine_mount_point='kv1_mount').secret.key_1 }}"
      - "KV2 (key1): {{ lookup('community.hashi_vault.vault_kv2_get', 'path/to/secret', engine_mount_point='kv2_mount').secret.key_1 }}"

- name: Reading secret version 7 (old)
  ansible.builtin.debug:
    msg:
      - "KV2 (v7): {{ lookup('community.hashi_vault.hashi_vault', 'kv2_mount/data/path/to/secret?version=7') }}"

- name: Reading secret version 7 (new)
  ansible.builtin.debug:
    msg:
      - "KV2 (v7): {{ lookup('community.hashi_vault.vault_kv2_get', 'path/to/secret', engine_mount_point='kv2_mount', version=7).secret }}"

- name: Reading KV2 metadata (old)
  ansible.builtin.debug:
    msg:
      - "KV2 (metadata): {{ lookup('community.hashi_vault.hashi_vault', 'kv2_mount/data/path/to/secret', return_format='raw').data.metadata }}"

- name: Reading KV2 metadata (new)
  ansible.builtin.debug:
    msg:
      - "KV2 (metadata): {{ lookup('community.hashi_vault.vault_kv2_get', 'path/to/secret', engine_mount_point='kv2_mount').metadata }}"

常规读取(非 KV)

由于 hashi_vault 查找在内部执行通用读取,因此它可以用于读取其他非 KV 特定的路径,例如从 cubbyhole 读取或检索 AppRole 的角色 ID。

预计将来会有更多特定用途的内容,例如用于检索角色 ID 的插件,但是对于目前尚未涵盖的任何内容,我们有 vault_read 查找和模块

它们始终执行直接读取,并返回原始结果,而无需尝试对响应进行任何其他解释。有关示例,请参阅其文档。