缓解 OSSN-0075

https://blueprints.launchpad.net/glance/+spec/mitigate-ossn-0075

OpenStack 安全说明 OSSN-0075,“已删除的 Glance 镜像 ID 可能会被重新分配”,于 2016 年 9 月 13 日公开发布。目前的情况是,由于缺乏修复方案的共识,我们让运营商处于一个糟糕的状态:我们的建议是 Glance 数据库中 ‘images’ 表中的软删除行应该从数据库中清除,但与此同时,glance-manage 工具会在没有警告的情况下删除这些行。

问题描述

简而言之,问题在于 Glance 一直允许具有创建镜像权限的用户指定 image_id。如果指定的 image_id 与现有的 image_id 冲突,镜像创建操作将失败;否则,指定的 image_id 将应用于新镜像。一致性由数据库 ‘images’ 表中 ‘id’ 列上的唯一性约束强制执行。由于 Glance 数据库条目是软删除的,因此建议的 image_id 将与自上次清除 ‘images’ 表以来分配的所有 image_id 进行检查。

OSSN-0075 中所述,当 (a) 一个流行的公共或社区镜像被删除,(b) 数据库被清除,以及 (c) 用户使用相同的 image_id 创建新镜像时,此问题会变成安全漏洞。通过 image_id 消费镜像的用户(Nova 和 Cinder 通过 image_id 消费镜像的方式)可能会最终使用与他们打算使用的镜像不同的镜像来启动虚拟机。

请注意,新镜像将拥有与其自身的数据和校验和,这些数据和校验和与原始数据和校验和不同,但 Nova 例如,将无法知道这些数据发生了变化。如果有人使用 image_id 启动服务器,Nova 将接收镜像数据,然后将校验和与 Glance 记录为与镜像关联的校验和(即新的校验和)进行验证。

一旦镜像进入 ‘active’ 状态,(image_id, 镜像数据, 校验和) 将不会更改的想法被称为镜像不可变性。重要的是要注意,镜像不可变性是 Glance 所必需的,否则它就无法作为镜像目录运行。如果每个消费者都必须跟踪 image_id 校验和以及其他基本属性才能验证下载的数据,那么就没有必要让 Glance 维护这些信息了。

注意

允许最终用户在镜像创建时指定 image_id 的主要用例是,方便在云的不同区域中找到“相同”镜像数据(即,数据逐位相同,尽管存储在不同的位置)。重要的是要注意,Glance 不保证不同区域的镜像“相同”。(Glance 安装可以保证其自身区域内镜像的不可变性,但无法知道其他区域正在发生什么。)因此,在当前情况下,当最终用户依赖 image_id 作为保证在不同云区域获取“相同”数据的保证时,最终用户实际上依赖于镜像所有者的可信度。

这与 OSSN-0075 是一个独立的问题,并且与 Glance 数据库是否被清除无关。我们指出这一点,供运营商注意。为了明确这个问题,这里有一个例子。假设云运营商将具有 image_id A 的镜像放在 R、S、T 区域,但由于某种原因,运营商没有将该镜像放在 U 区域。U 区域的任何云用户都可以在 U 区域创建具有 image_id A 的镜像。然后可以通过镜像共享或通过赋予其 ‘community’ 可见性将该镜像提供给目标用户或整个云。

运营商可以通过在 U 区域创建一个具有 image_id A 的镜像记录而不上传任何数据来避免这种情况。该镜像将保持在 ‘queued’ 状态,如果未将可见性更改为 ‘public’ 或 ‘community’,则该镜像不会出现在任何最终用户的镜像列表响应中。

这里也有一些最终用户教育的空间,即镜像消费者应仅依赖 image_id 来保证在跨区域场景中接收相同的镜像数据。

通过与运营商的讨论,很明显,在现场使用的镜像创建时,设置 image_id 的能力正在被使用,因此我们不能简单地阻止此能力。与此同时,我们必须允许偶尔清除数据库,因为有证据表明,对于大型部署,‘images’ 表中大量的软删除行会影响镜像列表 API 调用的响应时间。

提议的变更

修改当前 glance-manage db purge 命令,使其不会清除 images 表。

引入一个新的命令,glance-manage db purge-images-table 来清除 images 表。新命令将采用与当前 purge 相同的选项,即 --age-in-days--max-rows。将此作为新命令(而不是当前命令的 --force 选项)的原因有两个:(1)用于 images 表的 age-in-days 可能会有所不同,以及 (2) 鉴于清除 images 表具有安全影响,将其作为完全独立的命令强调了这一点。

备选方案

  1. 引入一项策略,以管理用户是否允许在镜像创建时指定 image_id。此建议的缺点有两个

    • 它破坏了向后兼容性,因为在 Image API 的 v1 和 v2 版本中一直允许此能力

    • 它破坏了互操作性,因为最终用户在某些云中可以使用此能力,而在其他云中则不能

    此建议的另一个问题是,如果拒绝最终用户跨区域使用特定 image_id,他们将不得不使用其他镜像元数据来实现此目的。由于 cinder 和 nova 在请求服务时都使用 image_id,因此用户工作流程必须更改,以在将传递给 cinder 或 nova 的 image_id 之前,先向镜像服务发出额外的调用以查找镜像记录。

  2. 不要在 images 表中引入新列,而是在具有唯一性约束的新单列表中引入一个用于记录“已用”UUID 的新表。镜像创建操作将尝试将建议的 UUID 插入到此表中而不是 ‘images’ 表中,如果违反了唯一性约束,则像现在一样失败。这个“已用”UUID 表永远不会被清除,但是 glance-manage 工具可以继续清除所有其他表。

    此替代方案的优点是不影响镜像列表调用。它最终会在镜像创建操作中引入很小的延迟,但这可能可以接受。

    缺点是此建议引入了一个无法清除的表,该表的大小不受限制。

  3. 对替代方案 #2 的变体:不要使用单列表,而是在 image_id 之外至少有一个 deleted_at 列。此表不会受到“正常”glance-manage 数据库清除操作的影响。相反,可以为该表引入额外的清除操作,该操作将清除从表中删除 5 年的行。

    此建议的一个问题是,一个有决心的攻击者仍然可以淹没“已用”image_id 表。这是可能的,因为虽然限制用户拥有的现有镜像数量可能是有意义的,但限制用户拥有的已删除镜像数量没有意义。例如,每天创建一个重要服务器镜像的最终用户,但只保留一周的镜像,将积累许多已删除的镜像(乘以正在执行此操作的服务器数量),但这是完全合法的行为。因此,我不确定如何防止淹没“已用”image_id 表,除非使用速率限制,但必须以不会影响合法用例的方式进行设置。

  4. 在 images 表中引入一个新的字段,preserve_id,仅供 Glance 内部使用,不会通过 API 公开。该字段默认情况下为空,每当镜像的 ‘visibility’ 字段设置为 ‘public’ 或 ‘community’ 时,该字段将设置为 true。无法重置该字段的值。除此之外,修改 glance-manage 工具,使其永远不会删除 images 表中 preserve_id == True 的条目。

    与替代方案 2 和 3 一样,数据库表将继续增长,但这种增长受到仅保留与 OSSN-0075 漏洞相关的行的限制。另一方面,攻击者所要做的就是阅读此规范,以意识到通过创建具有社区可见性的镜像记录,images 表仍然可以被填充虚假的镜像记录。因此,这种策略很容易被击败,不值得实施,特别是它可能会给运营商一种虚假的安全性。

数据模型影响

REST API 影响

安全影响

此更改将通过提供运营商缓解 OSSN-0075 中描述的漏洞的手段来增强安全性。

通知影响

其他最终用户影响

性能影响

images 表将无限期地增长,尽管关联表(image_properties、image_tags、image_members、image_locations)可以由 glance-manage 工具清除。

images 表可以在适当的间隔部分清除。

其他部署者影响

运营商必须监视 Glance 是否存在异常使用模式,并采取适当的措施。

此外,应让运营商了解 OSSN-0075 漏洞的跨区域版本(如问题描述部分“注意”中所讨论的)。

开发人员影响

实现

负责人

主要负责人

  • brian-rosmaita

其他贡献者

  • 未确定

工作项

  1. 修改 glance-manage 工具

    • 当前行为是清除所有表中的软删除行。更改行为,以便默认情况下不清除 images 表。

    • 添加一个新的命令来清除 images 表。它应该采用与当前 purge 命令相同的 --age-in-days--max-rows 选项。

  2. 更新运营商文档

  3. 发布说明

依赖项

没有新的依赖项。

测试

适当的单元测试,以确保对 glance 和 glance-manage 工具的更改正常工作。

文档影响

Glance 管理员指南需要更新。

参考资料

OSSN-0075已删除的 Glance 镜像 ID 可能会被重新分配