17. 项目签名与验证

项目签名与验证提供了对项目目录中的文件进行签名,然后验证其内容是否以任何方式发生更改,或文件是否意外地添加到项目或从项目中删除的功能。为此,需要一个用于签名的私钥和一个用于验证的匹配公钥。

对于项目维护人员,执行内容签名的支持方式是使用名为 ansible-sign 的实用程序,通过附带的命令行界面 (CLI) 使用它。

CLI 的目标是简化使用诸如 GNU Privacy Guard (GPG) 之类的加密技术的应用,以验证项目中指定的文件是否以任何方式被篡改。目前,GPG 是唯一支持的签名和验证方式。

AWX 用于验证签名的内容。在将匹配的公钥与签名的项目关联后,AWX 将验证签名期间包含的文件是否已更改,以及是否意外添加或删除了文件。如果签名无效或文件已更改,则项目将无法更新,并且任何使用该项目的作业都将无法启动。项目的验证状态确保仅在作业中运行安全且未被篡改的内容。

假设存储库已配置为进行签名和验证(见下文),更改项目的通常工作流程如下

  1. 用户已经设置了一个项目存储库,并且想要对文件进行更改。

  2. 用户进行更改,运行 ansible-sign project gpg-sign /path/to/project,这将更新校验和清单并对其进行签名。

  3. 用户将更改、更新的校验和清单和签名提交到存储库。

  4. 当用户同步项目时,AWX(在此情况下已配置)会提取新的更改,检查 AWX 中与项目关联的公钥是否与用于对校验和清单进行签名的私钥匹配(这可以防止对校验和清单本身进行篡改),然后重新计算清单中每个文件的校验和以确保校验和匹配(从而确保没有文件更改)。它还将查看以确保所有文件都被考虑在内:这些文件必须包含在下面讨论的 MANIFEST.in 文件中或被排除在该文件中;如果文件意外添加或删除,则验证将失败。

Content signing process diagram

17.1. 先决条件

访问 GnuPG 文档 以了解有关 GPG 密钥的更多信息。

您可以使用以下命令验证您是否拥有有效的 GPG 密钥对,并且该密钥对位于您的默认 GnuPG 密钥环中

$ gpg --list-secret-keys

如果上述命令没有输出,或者只有一行输出,内容为 trustdb was created,那么您的默认密钥环中没有私钥。在这种情况下,请参阅 如何创建 GPG 密钥对 了解如何在继续操作之前创建新的密钥对。如果它产生了其他输出,那么您拥有有效的私钥,并且可以继续使用 ansible-sign

17.2. 将 GPG 密钥添加到 AWX

为了在 AWX 中使用 GPG 密钥进行内容签名和验证,您必须使用以下命令在 CLI 中添加它

$ gpg --list-keys
$ gpg --export --armour <key fingerprint> > my_public_key.asc
  1. 在 AWX 用户界面中,从左侧导航菜单中点击 **凭据**,然后点击 **添加** 按钮。

  2. 为新的凭据提供一个有意义的名称(例如,“基础设施团队公共 GPG 密钥”)。

  3. 在 **凭据类型** 字段中,选择 **GPG 公钥**。

  4. 点击 **浏览** 以查找并选择公钥文件(例如,my_public_key.asc)。

  5. 完成后,点击 **保存**。

Example GPG credential details

现在可以在 项目 中选择此凭据,并且将在未来的项目同步中自动进行内容验证。

Create project with example GPG credentials

注意

使用项目缓存 SCM 超时来控制您希望 AWX 多久重新验证一次签名的内容。当项目配置为在启动时更新(任何配置为使用该项目的作业模板的启动)时,您可以启用缓存超时设置,该设置会告诉它在上次更新后的 N 秒后更新。如果验证运行过于频繁,则可以通过在项目的选项详细信息窗格的 **缓存超时** 字段中指定时间来减慢项目更新的频率。

Checked Update Revision on Launch option with Cache Timeout value specified from the Create new project page

17.3. 访问 ansible-sign CLI 实用程序

ansible-sign 实用程序提供了用户签署和验证项目是否已签署的选项。

  1. 运行以下命令安装 ansible-sign

$ dnf install ansible-sign
  1. 验证 ansible-sign 是否已成功安装

$ ansible-sign --version

将显示类似于以下内容的输出(版本号可能不同)

ansible-sign 0.1

这表明您已成功安装 ansible-sign

17.4. 签名您的项目

顾名思义,对项目进行签名涉及 Ansible 项目目录。有关项目目录结构的更复杂示例,请参阅 Ansible 文档

以下示例项目具有非常简单的结构。一个清单文件和两个位于 playbooks 目录下的小型剧本

$ cd sample-project/
$ tree -a .
.
├── inventory
└── playbooks
    └── get_uptime.yml
    └── hello.yml

1 directory, 3 files

注意

本节中使用的命令假设您的工作目录是项目的根目录。作为一项规则,ansible-sign project 命令始终将项目根目录作为其最后一个参数,因此,我们使用 . 来表示当前工作目录。

ansible-sign 通过获取项目中所有受保护文件的校验和 (SHA256),将这些校验和编译成校验和清单文件,最后对该清单文件进行签名来保护内容免遭篡改。

对内容进行签名的第一步是创建一个文件,告诉 ansible-sign 哪些文件需要保护。该文件应命名为 MANIFEST.in 并且位于项目根目录中。

在内部,ansible-sign 使用 Python distlib 库的 distlib.manifest 模块,因此 MANIFEST.in 必须遵循该库指定的语法。有关 MANIFEST.in 文件指令的说明,请参见 Python 打包用户指南

在示例项目中,包含两个指令,导致 MANIFEST.in 文件如下所示

include inventory
recursive-include playbooks *.yml

有了这个文件,就可以生成校验和清单文件并对其进行签名。这两步都在一个 ansible-sign 命令中完成

$ ansible-sign project gpg-sign .
[OK   ] GPG signing successful!
[NOTE ] Checksum manifest: ./.ansible-sign/sha256sum.txt
[NOTE ] GPG summary: signature created

现在,项目已签名。

请注意,gpg-sign 子命令位于 project 子命令下。对于对项目内容进行签名,每个命令都将以 ansible-sign project 开头。如上所述,作为一项规则,每个 ansible-sign project 命令都将项目根目录作为其最后一个参数。

如前所述,ansible-sign 默认使用您的默认密钥环并查找它可以找到的第一个可用私钥来对您的项目进行签名。您可以使用 --fingerprint 选项指定一个特定的私钥,甚至可以使用 --gnupg-home 选项指定一个完全独立的 GPG 主目录。

注意

如果您正在使用桌面环境,GnuPG 将自动提示您输入私钥的密码。如果此功能无法正常工作,或者您在没有桌面环境的情况下工作(例如,通过 SSH),您可以在上述命令中 gpg-sign 后使用 -p/--prompt-passphrase 标志,这将导致 ansible-sign 提示输入密码。

查看项目目录的结构后,请注意,创建了一个新的 .ansible-sign 目录。此目录包含校验和清单及其分离的 GPG 签名。

$ tree -a .
.
├── .ansible-sign
│   ├── sha256sum.txt
│   └── sha256sum.txt.sig
├── inventory
├── MANIFEST.in
└── playbooks
    ├── get_uptime.yml
    └── hello.yml

17.5. 验证您的项目

如果您想验证签名的 Ansible 项目是否被修改过,可以使用 ansible-sign 检查签名是否有效,以及文件的校验和是否与校验和清单中的预期一致。特别是,ansible-sign project gpg-verify 命令可用于自动验证这两个条件。

$ ansible-sign project gpg-verify .
[OK   ] GPG signature verification succeeded.
[OK   ] Checksum validation succeeded.

注意

默认情况下,ansible-sign 会使用您的默认 GPG 密钥环查找匹配的公钥。您可以使用 --keyring 选项指定密钥环文件,或使用 --gnugpg-home 选项指定不同的 GPG 主目录。

如果验证因任何原因失败,将显示信息以帮助您调试原因。可以通过在命令中 ansible-sign 后面立即传递全局 --debug 标志来启用更多详细程度。

注意

当 GPG 凭据在项目中使用时,内容验证将在将来的项目同步中自动进行。

17.6. 自动化签名

在高度可信的 CI 环境(例如 OpenShift、Jenkins 等)中,可以自动化签名过程。例如,您可以将您的 GPG 私钥存储在选择的 CI 平台中作为秘密,并将该私钥导入 CI 环境中的 GnuPG。然后,您可以在正常的 CI 工作流程/容器/环境中执行上述签名工作流程。

使用 GPG 签名项目时,可以将环境变量 ANSIBLE_SIGN_GPG_PASSPHRASE 设置为签名密钥的密码。这可以在 CI 管道中注入(并屏蔽/保护)。

根据具体情况,ansible-sign 在签名和验证期间将返回不同的退出代码。这在 CI 和自动化环境中也很有用,因为 CI 环境可以根据失败情况采取不同的行动(例如,对某些错误发送警报,但对其他错误则静默失败)。

以下是目前 ansible-sign 中使用的退出代码,这些代码可以被认为是稳定的

退出代码

近似含义

示例场景

0

成功

  • 签名成功

  • 验证成功

1

一般故障

  • 校验和清单文件在验证期间包含语法错误

  • 签名文件在验证期间不存在

  • MANIFEST.in 在签名期间不存在

2

校验和验证失败

  • 验证期间计算的校验和哈希值与签名校验和清单中的哈希值不同(例如,项目文件已更改,但签名过程未重新完成)

3

签名验证失败

  • 签名者的公钥不在用户的 GPG 密钥环中

  • 指定了错误的 GnuPG 主目录或密钥环文件

  • 签名的校验和清单文件以某种方式被修改

4

签名过程失败

  • 签名者的私钥未在 GPG 密钥环中找到

  • 指定了错误的 GnuPG 主目录或密钥环文件