Nova 签名验证¶
https://blueprints.launchpad.net/nova/+spec/nova-support-image-signing
OpenStack 目前不支持对上传的已签名镜像进行签名验证。为 Nova 赋予验证镜像签名的能力将为最终用户提供对其用于创建服务器的镜像数据的完整性的更强保证。此更改将使用与 Glance 中配套功能相同的数据模型,该模型将允许最终用户对镜像进行签名并在上传时验证这些镜像签名 [1]。
问题描述¶
目前,OpenStack 对防止镜像意外修改的保护仅限于验证 MD5 校验和。虽然这可能足以防止意外修改,但 MD5 是一种哈希函数,而不是身份验证原语 [2],因此无法防止故意恶意修改镜像。镜像在传输过程中可能会被修改,例如在上传到 Glance 或传输到 Nova 时。被修改的镜像可能包含恶意代码。提供签名验证支持将允许 Nova 在启动前验证签名,并通过未来的 API 更改向用户发出签名验证成功的警报。此功能将保护 OpenStack 免受以下攻击场景的影响
中间人攻击 - 具有访问 Nova 和 Glance 之间网络的攻击者正在更改 Nova 从 Glance 下载数据时的镜像数据。攻击者可能会将恶意软件合并到镜像中和/或更改镜像元数据。
不受信任的 Glance - 在混合云部署中,Glance 托管在物理位置不安全的机器上,或由安全基础设施有限的公司托管。攻击者可能能够通过物理访问主机机器或通过托管 Glance 的公司网络安全措施不足,来破坏 Glance 和/或 Glance 存储的镜像的完整性。
请注意,我们的威胁模型仅考虑镜像在最终用户和 Glance 之间传输、在 Glance 中静态存储以及在 Glance 和 Nova 之间传输时的完整性威胁。此威胁模型不包括,因此此功能也不解决,对 Nova 的完整性、可用性或机密性的威胁。
用例¶
用户希望高度确信他们上传到 Glance 的自定义镜像在启动镜像之前没有被意外或恶意修改。
通过此提议的更改,Nova 将在下载镜像时验证已签名镜像的签名。如果无法验证镜像签名,则 Nova 将不会启动镜像,而是将实例置于错误状态。用户将通过 Glance API 的 image-create 方法将镜像和镜像签名元数据上传到 Glance来开始使用此功能。所需的镜像签名元数据属性如下
img_signature - 镜像数据签名的 base 64 编码的字符串表示形式。
img_signature_hash_method - 一个字符串,指定用于签名的哈希方法。目前,支持的值为 SHA-224、SHA-256、SHA-384 和 SHA-512。MD5 和其他加密强度较弱的哈希方法将不在此字段中支持。使用不受支持的哈希算法签名的任何镜像将无法通过验证。
img_signature_key_type - 一个字符串,指定用于生成签名的签名方案。
img_signature_certificate_uuid - 一个字符串,编码用于从密钥管理器检索证书的证书 uuid。
Glance 中的镜像验证功能使用 signature_utils 模块来验证签名元数据,然后再存储镜像。如果签名无效或元数据不完整,则此 API 方法将返回 400 错误状态并将镜像置于“killed”状态。请注意,如果签名元数据根本不存在,则镜像将像往常一样存储。
然后,用户将使用 Nova API 的 boot 方法从该镜像创建实例。如果 nova.conf 中的 verify_glance_signatures 标志设置为“True”,Nova 将调用 Glance 获取镜像的属性,其中包括用于镜像签名验证的属性。Nova 将将镜像数据和镜像属性传递给 signature_utils 模块,该模块将验证签名。如果签名验证失败,或者镜像签名元数据不完整或缺失,则启动实例将失败,并且 Nova 将记录异常。如果签名验证成功,Nova 将启动实例并记录消息,指示镜像签名验证成功,以及有关签名证书的详细信息。
提议的变更¶
此更改的第一个组件是创建一个独立模块,该模块负责大部分用于镜像签名验证的功能。该模块主要由三个面向公众的方法组成:一个初始化方法、一个更新方法和一个验证方法。初始化方法将采用签名证书 uuid 和指定的哈希方法作为输入。然后,此方法将通过 Castellan 与密钥管理器接口来获取签名证书,提取公钥,将公钥、证书和哈希方法存储为属性,并返回签名验证模块的实例。随着镜像数据的下载,签名验证模块将通过将镜像块传递给验证模块来更新。当所有镜像块都已传递给验证器时,希望进行验证的服务将调用验证方法,并将镜像签名传递给它。更具体地说,此模块将应用公钥到签名,并将此结果与将哈希算法应用于镜像数据的结果进行比较。此工作流程基本上是 pyca/cryptography 中签名验证发生的工作流程的包装版本。
然后,我们建议通过将此模块合并到 Nova 从镜像启动实例的控制流程中来实现初始实现。下载镜像后,Nova 将检查 nova.conf 中是否设置了 verify_glance_signatures 配置标志。如果是,该模块将使用 Glance 传递给 Nova 的镜像属性执行镜像签名验证。如果失败,或者镜像签名元数据不完整或缺失,Nova 将不会启动镜像。相反,Nova 将抛出异常并记录错误。如果签名验证成功,Nova 将继续启动实例。
下一个组件是向 pyca/cryptography 库添加功能,该功能将验证给定证书链是否符合已知受信任的给定根证书池。验证证书链与一组受信任的根证书的算法是一种标准,并在 RFC 5280 [3] 中进行了概述。
一旦将证书验证功能添加到 pyca/cryptography 库,我们将通过将证书验证合并到签名验证工作流程中来修改 signature_utils 模块。我们将实施 signature_utils 模块中的功能,该功能将使用 GET 请求动态获取给定证书的证书链。任何使用 signature_utils 模块的服务现在都将使用附加参数(表示受信任根证书池的列表)调用 signature_utils 模块的初始化方法。然后,该模块将使用其证书链获取功能构建签名证书的证书链,通过 Castellan 获取根证书,并使用 pyca/cryptography 库中的功能将此链与受信任的根证书进行验证。如果链验证失败,则将抛出异常,并且签名验证将失败。Nova 将通过从 nova.conf 中的 root_certificate_references 配置选项中读取引用来检索调用 signature_utils 模块的更新功能所需的根证书引用。
未来的 API 更改对于缓解在 Glance 不受信任时可能发生的攻击至关重要;例如 Glance 返回与请求的镜像不同的已签名镜像。可能的更改包括以下扩展
修改 REST API 以接受用于验证镜像完整性所需的特定签名。如果无法验证指定的签名,则 Nova 将拒绝启动镜像并向最终用户返回适当的错误消息。此更改基于允许在启动时覆盖镜像属性的规范 [4]。
修改 REST API 以向最终用户提供有关成功启动请求的元数据。此元数据将包括签名证书所有权信息和签名的 base64 编码。用户可以使用带外机制手动验证编码版本的签名是否与预期的签名匹配。
首选第一种方法,因为它可能是完全自动化的,而第二种方法需要最终用户手动验证。
证书引用将用于通过 Castellan 提供的接口从密钥管理器访问证书。
备选方案¶
一种替代方案是直接对镜像数据进行签名,而是支持通过对镜像数据的哈希进行签名来创建签名。这会给该功能引入不必要的复杂性,需要额外的哈希阶段和额外的元数据选项。由于 Glance 社区对哈希镜像数据相关的性能问题,我们最初追求了一种通过对 Glance 已经计算的 MD5 校验和进行签名来实现的方案。由于 MD5 的安全弱点以及两次执行哈希操作和维护有关两种哈希算法的信息的不必要复杂性,Nova 社区拒绝了这种方法。
一种替代方案是使用 PyCrypto 进行哈希和签名功能,而是使用 pyca/cryptography。我们选择使用 pyca/cryptography 是基于 OpenStack 要求从 PyCrypto 转向以及审查配套 Glance 规范的密码学家的建议 [5]。
一种替代方案是使用证书进行签名和签名验证,而是使用公钥。但是,这种方法存在一个重大弱点,即攻击者可以在密钥管理器中生成自己的公钥,使用该公钥对篡改后的镜像进行签名,并将对公钥的引用与他们的签名镜像一起传递给 Nova。或者,使用证书提供了一种将此类攻击归因于证书所有者的手段,并遵循常见的密码学标准,将信任根置于证书颁发机构。
一种替代方案是使用 verify_glance_signatures 配置标志来指定 Nova 是否应执行镜像签名验证,而是使用“受信任”flavor 来指定应从已签名镜像创建的单个实例。用户在使用 Nova CLI 启动实例时,将指定这些“受信任”flavor 以指示应将镜像签名验证作为实例启动控制流程的一部分。这可能会在后续更改中添加,但不会包含在初始实现中。如果添加,受信任的 flavor 选项将与配置选项方法一起工作。在这种情况下,如果设置了配置标志,或者用户指定启动“受信任”flavor 的实例,Nova 将执行镜像签名验证。
支持不受信任的 Glance 用例需要先前描述的 REST API 的未来修改。一种替代方案是使用“签名哈希”方法进行签名,而不是直接对镜像内容进行签名。在这种情况下,Nova 的 REST API 可以修改为允许用户指定哈希算法和预期的哈希值作为启动命令的一部分。如果实际哈希值不匹配,则 Nova 将不会启动镜像。直接对哈希进行签名而不是对镜像直接进行签名是有用的,因为通常可以从带外获得云镜像的哈希值。
数据模型影响¶
配套的 Glance 工作引入了用于镜像签名所需的其他 Glance 镜像属性。Nova 中的初始实现将引入一个配置标志,指示 Nova 是否应在启动镜像之前执行镜像签名验证。更新的实现,其中包括证书验证,将引入一个额外的配置标志,用于指定受信任的根证书。
REST API 影响¶
未来的更改将修改启动请求或响应。此更改支持不受信任的 Glance 用例,通过向用户提供与签名数据相关的其他保证,以确保已启动所需的镜像。
安全影响¶
Nova 目前缺乏在启动镜像之前验证镜像的机制。包含在镜像中的校验和可防止意外修改,但无法防止访问 Glance 或 Nova 和 Glance 之间通信网络的攻击者。此功能有助于在 Nova 和 Glance 之间创建逻辑信任边界;此信任边界允许最终用户高度确信 Nova 正在启动由受信任用户签名的镜像。
虽然 Nova 将使用证书来执行此任务,但这些证书将由密钥管理器存储并通过 Castellan 访问。
通知影响¶
无
其他最终用户影响¶
如果签名验证失败,则 Nova 将不会从镜像启动实例,并且将记录错误消息。然后,用户必须通过 Glance API、Nova API 或 Horizon 界面编辑镜像的元数据;或者重新启动将正确的签名元数据上传到 Glance 的镜像,以启动镜像。
性能影响¶
仅当设置了 verify_glance_signatures 配置标志时,才会使用此功能。
当发生签名验证时,由于通过 Castellan 接口从密钥管理器检索证书,因此会产生延迟。此外,对镜像数据进行哈希和使用公钥解密签名也会产生 CPU 开销。
其他部署者影响¶
为了使用此功能,必须部署和配置密钥管理器。此外,Nova 必须配置为使用具有能够响应最终用户证书签名请求的信任根的根证书。
开发人员影响¶
无
实现¶
负责人¶
- 主要负责人
dane-fichter
- 其他贡献者
brianna-poulos joel-coffman
评审人员¶
- 核心评审人
无
工作项¶
此功能将分以下阶段实现
创建独立的 signature_utils 模块,该模块处理通过 Castellan 与密钥管理器接口和验证签名。
将功能添加到 Nova,该功能在 Nova 上传 Glance 镜像并且设置了 verify_glance_signatures 配置标志时调用独立模块。
将证书验证功能添加到 pyca/cryptography 库。
将功能添加到 signature_utils 模块,该模块用于获取证书链。将此方法与 pyca/cryptography 库的证书验证功能合并到 signature_utils 模块的镜像签名验证功能中。
修改 Nova 中的初始实现,以通过允许 Nova 获取根证书引用并将其传递给镜像签名验证方法来利用此更改。
实施 REST API 更改,以响应成功的启动请求,并提供与签名数据相关的信息,或者实施 REST API 更改,以允许最终用户在启动时指定预期的签名。
依赖项¶
pyca/cryptography 库,已经是 Nova 的要求,将用于创建哈希和验证签名。此更改的证书验证部分取决于向 pyca/cryptography 库添加证书验证功能。
为了简化与密钥管理器的交互并允许使用多个密钥管理器后端,此功能将使用 Castellan 库 [6]。由于 Castellan 目前仅支持与 Barbican 集成,因此在此功能中使用 Castellan 间接需要 Barbican。未来,随着 Castellan 支持更多种类的密钥管理器,我们的功能只需要更新 Nova 和 Glance 的需求以使用最新的 Castellan 版本,即可支持这些密钥管理器;维护工作将降到最低。
测试¶
单元测试足以测试 Nova 中实现的功能。我们需要实现 Tempest 和功能测试,以测试此功能与 Glance 中配套功能的互操作性。
文档影响¶
需要记录如何使用此功能的说明。
参考资料¶
密码学 API:https://pypi.ac.cn/project/cryptography/0.2.2
[1] https://review.openstack.org/#/c/252462/ [2] https://en.wikipedia.org/wiki/MD5#Security [3] https://tools.ietf.org/html/rfc5280#section-6.1 [4] https://review.openstack.org/#/c/230382/ [5] https://review.openstack.org/#/c/177948/ [6] http://git.openstack.org/cgit/openstack/castellan