为加密模拟虚拟 TPM 添加支持¶
https://blueprints.launchpad.net/nova/+spec/add-emulated-virtual-tpm
有一类应用程序期望使用 TPM 设备来存储密钥。为了在虚拟机中运行这些应用程序,在 guest 中暴露一个虚拟 TPM 设备将很有用。因此,建议添加 flavor/image 属性,a) 转换为调度时的 placement traits,b) 导致相关 virt 驱动程序将此类设备添加到 VM 中。
问题描述¶
目前,nova 中创建虚拟机时,无法向客户机提供虚拟 TPM 设备。
用例¶
支持虚拟化现有应用程序和操作系统,这些应用程序和操作系统期望使用物理 TPM 设备。至少有一个 hypervisor (libvirt/qemu) 当前支持创建与主机上的 per-VM swtpm 进程关联的模拟 TPM 设备,但没有办法告诉 nova 启用它。
提议的变更¶
最近的 libvirt 和 qemu (以及可能其他 hypervisor) 支持模拟的 vTPM 设备。我们建议修改 nova 以利用此功能。
此 spec 仅描述 libvirt 实现。
XML¶
所需的 libvirt XML 参数如下所示 (来源)
...
<devices>
<tpm model='tpm-tis'>
<backend type='emulator' version='2.0'>
<encryption secret='6dd3e4a5-1d76-44ce-961f-f119f5aad935'/>
</backend>
</tpm>
</devices>
...
先决条件¶
支持加密模拟 TPM 至少需要
libvirt 版本 5.6.0 或更高版本。
qemu 2.11 作为最低版本,但推荐 qemu 2.12。virt 驱动程序代码应添加合适的版本检查(对于 LibvirtDriver,这将包括对 libvirt 和 qemu 的检查)。目前,模拟 TPM 仅支持 x86,但这是一种实现细节,而不是架构限制。
主机上的
swtpm二进制文件和库。访问与 castellan 兼容的密钥管理器,例如 barbican,用于存储用于加密虚拟设备数据的密码短语。(密钥管理器实现的公共方法必须能够从
context参数(这是接口的一部分)消耗用户的 auth token。)访问对象存储服务,例如 swift,用于存储主机在 shelve 等操作期间用于虚拟设备数据的文件。
配置¶
以下所有内容均适用于 compute(而非 conductor/scheduler/API)配置
将引入一个新的配置选项,作为启用 vTPM 的“主开关”。此配置选项也适用于未来驱动程序的实现,但由于此 spec 和当前实现特定于 libvirt,因此它位于
libvirt而不是compute组中[libvirt] vtpm_enabled = $bool (default False)
为了启用移动操作(涉及在新的主机上重建 vTPM 的任何操作),nova 必须能够使用正确的 ownership(即 libvirt 将创建的
swtpm进程的 ownership)来放置 vTPM 数据,但我们无法检测到该 ownership 将是什么。因此,我们需要在 compute 上指示应拥有主机上 vTPM 数据的用户和组的两个配置选项[libvirt] swtpm_user = $str (default 'tss') swtpm_group = $str (default 'tss')
(现有,已知)选项用于
[key_manager]。将引入新的标准 keystoneauth1 auth/session/adapter 选项用于
[swift]。
Traits、Extra Specs、Image Meta¶
为了支持此功能,我们建议
使用现有的
COMPUTE_SECURITY_TPM_1_2和COMPUTE_SECURITY_TPM_2_0traits。这些代表当前支持的 TPM spec 的两个不同版本。(请注意,2.0 与 1.2 不向后兼容,因此我们不能简单地忽略 1.2。两种版本之间的差异摘要目前可在 此处获得。)当满足所有 先决条件并且 配置开关打开时,libvirt compute 驱动程序将在 compute 节点资源提供程序上设置这两个 traits。支持以下新的 flavor extra_specs 及其对应的 image metadata 属性(这些属性只是下面内容的
s/:/_/)hw:tpm_version={1.2|2.0}。这将是转换为 allocation candidate request 中的相应
required=COMPUTE_SECURITY_TPM_{1_2|2_0},以确保实例落在能够以请求版本使用 vTPM 的主机上由 libvirt compute 驱动程序用于注入适当的 guest XML。
注意
虽然可以直接在 flavor extra_specs 或 image metadata 中指定
trait:COMPUTE_SECURITY_TPM_{1_2|2_0}=required,但这只会将实例放置在能够的主机上;它不会触发 libvirt 驱动程序创建虚拟 TPM 设备。因此,为了避免混淆,将不会将此记录为可能性。hw:tpm_model={TIS|CRB}。指示要使用的模拟模型。如果省略,则默认值为TIS(这对应于 libvirt 默认值)。CRB仅与 TPM 版本 2.0 兼容;如果请求版本 1.2 时使用CRB,API 将引发错误。
总而言之,所有且仅支持以下组合,并且它们是互斥的(不能相互兼容)
版本 1.2,模型 TIS
版本 2.0,模型 TIS
版本 2.0,模型 CRB
请注意,由于 TPM 是模拟的(主机上的进程/文件),因此“库存”实际上是无限的。因此,此功能没有相关的资源类。
如果 flavor 和 image 都指定了 TPM trait 或设备模型,并且这两个值不匹配,API 将从 flavor/image validator 引发异常。
实例生命周期操作¶
以下描述是 libvirt 驱动程序特定的。但是,实现哪些部分由 compute manager 执行,哪些部分由 libvirt ComputeDriver 本身执行,由实现决定。
注意
在决定是否支持给定的操作时,我们以“裸机上它是如何工作的”作为起点。如果我们在不引入过度的复杂性或用户面临的奇怪情况的情况下支持 VM 操作,我们就这样做。
生成¶
即使 swift 对于生成不是必需的,也要确保服务目录中存在 swift 端点(并且可访问?版本发现?实现细节),以便将来的 unshelve 不会破坏实例。
Nova 生成一个随机密码短语并将其存储在配置的密钥管理器中,产生一个 UUID,以下称为
$secret_uuid。Nova 将
$secret_uuid保存到实例的system_metadata下键tpm_secret_uuid。Nova 使用
virSecretDefineXMLAPI 定义一个私有(无法列出值)、短暂(状态仅存储在内存中,从不存储在磁盘上)secret,其name是实例 UUID,其 UUID 是$secret_uuid。然后使用virSecretSetValueAPI 将其值设置为生成的密码短语。我们已经在nova.virt.libvirt.host.Host.create_secret处为加密卷提供了对此 API 的包装,并将扩展此功能以涵盖 vTPM。Nova 将 XML 注入到实例的 domain 中。从 flavor/image 属性中获取
model和version,并且secret是$secret_uuid。一旦 libvirt 创建了 guest,nova 使用
virSecretUndefineAPI 删除 secret。实例的模拟 TPM 继续运行。
注意
从通过快照创建具有 vTPM 的 VM 创建的镜像生成将导致新的、空的 vTPM,即使该快照是由 shelve 创建的。相反,生成期间 unshelve 将恢复此类 vTPM 数据。
冷启动¶
……以及任何其他从头开始启动 guest 的操作。(根据 密钥管理器 安全模型,这些操作可能仅限于实例所有者。)
从实例的
system_metadata的tpm_secret_uuid中提取$secret_uuid。通过配置的密钥管理器 API 检索与
$secret_uuid关联的密码短语。
然后执行 生成 下的步骤 4-6。
迁移及其类似操作¶
对于 libvirt 实现,模拟 TPM 数据存储在 /var/lib/libvirt/swtpm/<instance> 中。某些生命周期操作要求将该目录逐字复制到“目标”。对于(冷/实时)迁移,只有 nova-compute 运行的用户才能保证已设置 SSH 密钥以进行无密码访问,并且它只能保证能够将文件复制到目标节点上的实例目录。因此,我们建议对相关生命周期操作执行以下过程
将目录复制到本地实例目录,更改 ownership 以匹配它。
执行移动,这将自动携带数据。
更改 ownership 并将目录移到目标上的
/var/lib/libvirt/swtpm/<instance>。在确认/撤销时,分别从源/目标删除目录。(这在 libvirt 拆除 guest 时自动完成。)
在撤销时,必须恢复数据目录(具有适当的权限)到源。
由于目标上的预期 ownership 可能与源不同,并且(我们认为)无法检测到,管理员必须通过新的 [libvirt]swtpm_user 和 [libvirt]swtpm_group 配置 选项告知我们,如果与默认值 tss 不同。
这应该允许支持冷/实时迁移和不更改设备的调整大小。
待办事项
确认上述“手动”复制操作实际上对于迁移是必要的。从阅读 https://github.com/qemu/qemu/blob/6a5d22083d50c76a3fdc0bffc6658f42b3b37981/docs/specs/tpm.txt#L324-L383 来看,尚不清楚。
调整大小有可能将 vTPM 添加到没有 vTPM 的实例,或者从具有 vTPM 的实例中删除 vTPM,这些操作应该“正常工作”。当从一个版本/模型调整为另一个版本时,数据无法携带(对于同主机调整大小,我们必须删除旧的备份文件)。如果旧的和新的 flavor 具有相同的模型/版本,我们必须确保我们像上面描述的那样传递虚拟设备数据(对于同主机调整大小,我们必须保留现有的备份文件)。
Shelve(卸载)和 Unshelve¶
恢复 shelve 卸载的服务器上的 vTPM 数据需要将 vTPM 数据持久化到某个地方。我们无法将其与实例磁盘本身一起放置,因为它与实例磁盘外部的数据。因此,我们建议将其放入对象存储(swift)并在实例的 system_metadata 中维护对 swift 对象的引用。
shelve 操作需要
将 vTPM 数据目录保存到 swift。
将 swift 对象 ID 和目录的数字签名(sha256)保存到实例的
system_metadata下键tpm_object_id和tpm_object_sha256。在 image 上创建适当的
hw_tpm_version和/或hw_tpm_modelmetadata 属性。(这是为了弥补原始 VM 上 vTPM 是应 image 属性而非 flavor 属性的要求下创建的差距。它确保了 unshelve 上的正确调度,并且目标上创建了正确的版本/模型。)
对已 shelve(但未卸载)的实例执行 unshelve 操作应该“正常工作”(不包括删除 swift 对象;参见下文)。用于卸载实例的代码路径需要
确保我们落在能够满足必要 vTPM 版本和模型的 host 上(我们通过通用的调度代码路径免费获得此信息,因为我们在 shelve 期间执行了步骤 3)。
查找实例的
system_metadata中的tpm_object_{id|sha256}和tpm_secret_uuid。下载 swift 对象。验证其校验和,如果校验和不匹配则失败。
根据 host 上的
[libvirt]swtpm_{user|group}分配数据目录的 ownership。从密钥管理器中检索 secret 并将其提供给 libvirt;并生成适当的 domain XML(我们通过
spawn()免费获得此信息)。从 swift 中删除对象,并从实例
system_metadata中删除tpm_object_{id|sha256}。必须从两个代码路径(即,无论已 shelve 的实例是否已卸载)执行此步骤。
注意
用户仍然可以通过一些方式“智胜”我们的检查并在 unshelve 上发生可怕的事情。例如
flavor 没有指定 vTPM 属性。
原始 image 指定了版本 2.0。
在 shelve 和 unshelve 之间,编辑快照以指定版本 1.2。
我们将很高兴创建一个 v1.2 vTPM 并将 (v2.0) 数据恢复到其中。VM 可能可以正常启动,但是访问 vTPM 时会发生不可预测的事情。
我们无法阻止所有愚蠢行为。
注意
如安全影响中所述,如果shelve操作由管理员执行,则只有管理员才能执行相应的unshelve操作。并且,根据密钥管理器的安全模型,如果shelve操作由用户执行,管理员可能无法执行相应的unshelve操作。
由于底层设备数据是virt驱动特定的,因此必须由virt驱动进行管理;但我们希望对象存储的交互由计算管理器完成。因此,我们提出计算管理器和virt驱动之间的以下交互方式
当前的ComputeDriver.snapshot()契约不指定返回值。它将被更改为允许返回一个类文件对象,其中包含(预打包的)底层设备数据。libvirt驱动程序实现将打开一个tar管道并返回该句柄。计算管理器负责从该句柄读取内容并将其推送到swift对象存储。(实现细节:我们仅对shelve期间的快照执行swift操作,因此a) virt驱动程序不应在VM处于SHELVE[_OFFLOADED]状态之外时生成句柄;和/或计算管理器应显式关闭snapshot()的其他调用的句柄。)
卸载实例的计算驱动程序触点是spawn()。此方法将获得一个新的关键字参数,该参数是一个类文件对象。如果不是None,virt驱动程序实现负责从该句柄流式传输数据并反转snapshot()期间所做的事情(在本例中为解压tar文件)。对于卸载实例的unshelve路径,计算管理器将从swift对象存储中拉取对象并将其流式传输到spawn()通过此关键字参数。
createImage和createBackup¶
由于vTPM数据与实例相关,而不是与镜像相关,因此createImage和createBackup流程不会被更改。特别是,它们不会尝试将vTPM底层设备保存到swift对象存储。
这与新的Spawn不会尝试恢复vTPM数据(即使给定通过shelve创建的镜像)的事实一起,也防止了“克隆”vTPM。
这类似于裸机情况,从“干净”系统上的镜像/备份生成会得到一个“干净”(或没有)TPM。
重建¶
由于实例停留在同一主机上,因此我们可以选择保留现有的vTPM底层文件。这类似于裸机行为,在现有系统上恢复备份不会触碰TPM(或任何其他设备),因此您将获得已经存在的内容。但是,通过使用不同的镜像和/或具有不同元数据的镜像重建,也可能使您的实例无法访问其vTPM。用户有责任避免使用TPM创建主密钥而不将该主密钥保存(在您的重建镜像中,或其他地方)的情况。
也就是说,重建将涵盖以下场景
如果没有现有的vTPM底层数据,并且重建镜像请求vTPM,则创建一个新的vTPM,就像Spawn一样。
如果存在vTPM,并且flavor或镜像都不请求vTPM,则删除它。
如果存在vTPM,并且flavor或镜像请求vTPM,则保留底层文件。但是,如果旧镜像和新镜像与flavor组合请求的版本/型号不同,我们将使重建失败。
撤离¶
由于vTPM数据属于libvirt而不是存储在实例磁盘中,因此在撤离时会丢失vTPM,即使实例是volume-backed。这类似于裸机行为,即使通过共享存储将状态复活到另一个系统,硬件TPM也会被遗留。
(可以通过将/var/lib/libvirt/swtpm/挂载到共享存储来缓解这种情况,但libvirt在创建/拆除客户机时对该目录的管理可能会阻碍此类尝试。无论如何,这将是一项管理员的工作;nova不会执行任何操作来支持或阻止它。)
销毁¶
删除与
system_metadata['tpm_secret_uuid']关联的密钥管理器secret。libvirt会在客户机拆除过程中删除vTPM数据目录。
如果存在
system_metadata['tpm_object_id'],则API端将删除它标识的swift对象存储。由于此元数据仅在实例shelved时存在,因此这仅适用于诸如在shelve和offload之间执行
destroy()的情况。清理处于
ERROR状态的VM,该VM来自shelve、offload或unshelve失败(在恰当的时间)。清理主机关闭时被删除的VM。
限制¶
这是此设计导致的奇怪或意外行为的总结。
备选方案¶
与其使用trait,我们可以使用任意大的
1_2/2_0资源类清单。除非可以证明存在我们可以发现的实际限制,否则这并不是我们做事的方式。与其使用专门的
hw:tpm*extra_spec/image_meta属性,不如隐式地基于placement-ese语法(resources:COMPUTE_SECURITY_TPM_*)进行配置。被拒绝,因为我们正在尝试摆脱这种做事方式,而是倾向于支持特定于该功能的语法,而不是要求管理员了解该功能如何映射到placement语法。此外,在某些情况下映射可能很简单,但在其他情况下需要在virt驱动程序级别进行额外的配置,而无法从placement语法中推断出来,这将需要混合和匹配placement和非placement语法。因此,禁止使用
resources[$S]:COMPUTE_SECURITY_TPM_*placement-ese语法。被拒绝,主要是由于(不必要的)额外复杂性,以及因为我们不想陷入假设没有使用“让我登陆到具有vTPM(不)能力的宿主机,但不要设置一个(现在)”用例的情况。使用物理直通(
<backend type='passthrough'>)一个真实的(硬件)TPM设备。这在当前的TPM硬件上不可行,因为(在其他事情中),更改secret的所有权需要主机重启。阻止需要对象存储的操作。这被认为不可行,特别是由于跨单元格调整大小在底层使用shelve。
使用glance或密钥管理器而不是swift来存储那些操作的vTPM数据。被否决,因为这些服务实际上不适合该目的,并且(至少glance)可能会在未来阻止此类用法。
在任何快照操作(包括
createImage和createBackup)上保存vTPM数据。这增加了复杂性以及一些意想不到的行为,例如“克隆”vTPM的能力。用户在这些情况下,当他们的vTPM表现得像一个(硬件)TPM时,会感到不那么惊讶。与其在spawn时检查swift,不如添加一个额外的spec / image prop,例如
vtpm_I_promise_I_will_never_shelve_offload=True或vtpm_is_totally_ephemeral=True,这将分别在shelve-offload时出错或简单地不备份vTPM。
数据模型影响¶
需要添加新的版本的ImageMetaProps和ImageMetaPropsPayload对象
hw_tpm_versionhw_tpm_modeltpm_object_idtpm_object_sha256
REST API 影响¶
镜像/flavor验证器将获得新的属性一致性检查。不需要新的微版本。
安全影响¶
客户机将能够使用模拟的TPM来增强所有安全性功能,从而保护自身免受来自客户机内部的攻击。
假设密钥管理器和对象存储服务已充分加固,以防止外部攻击。但是,部署必须考虑对这些服务的授权访问问题,如下所述。
数据盗窃¶
vTPM数据文件在磁盘上加密,因此在加密范围内是“安全的”,可以防止简单的数据盗窃。
我们将使用384字节的密码短语,这是SSH密钥的默认大小,由/dev/urandom生成。将来可能需要使此大小可配置。
受损root¶
假设计算节点上的root用户能够通过检查内存等方式获取vTPM的内容和/或密码短语。除了在libvirt中使用私有+临时secret之外,没有采取进一步措施来防止受损的root用户。
对象存储¶
对象存储服务允许管理员用户完全访问对象,无论谁创建了该对象。目前没有限制管理员只能删除对象等设施。因此,如果执行了shelve,vTPM设备的内容将可供管理员使用。它们是加密的,因此如果没有访问密钥,我们仍然信任加密的强度来保护数据。但是,如果对象存储管理员与访问计算主机上原始文件的人员不同,这将增加攻击面。
同样(呵呵),如果shelve由管理员执行,vTPM数据对象将由管理员创建和拥有,因此只有管理员才能unshelve该实例。
密钥管理器¶
存储在密钥管理器中的secret更加敏感,因为它可用于解密vTPM设备的内容。barbican实现对项目级别的secret进行范围划分,因此部署必须小心限制项目到所有应该信任一组通用secret的用户。此外,请注意,项目范围的管理员默认允许访问和解密任何项目拥有的secret;如果管理员不可信,则应通过策略限制此权限。
但是,castellan后端负责自己的身份验证机制。因此,部署可能希望使用仅对创建secret的个人用户进行解密范围划分的后端。(无论如何,重要的是允许管理员删除secret,以便可以由管理员执行VM删除等操作,而不会留下secret。)
请注意,如果管理员被限制解密密钥,则管理员执行的生命周期操作将无法产生正在运行的虚拟机。这包括重启宿主机:即使设置了 resume_guests_state_on_host_boot,带有 vTPM 的实例也不会自动启动,而是必须由其所有者手动启动。其他默认情况下仅限管理员操作的操作,只有由虚拟机所有者执行时才有效,这意味着必须授予所有者适当的策略角色才能执行;否则,这些操作将实际上被禁用。
…除了实时迁移,因为 vTPM 的(已解密)运行状态会随之传递到目标位置。(为了澄清:由于上述原因,如果由管理员执行,实时迁移与其他操作不同,实际上会起作用。)
通知影响¶
无
其他最终用户影响¶
无
性能影响¶
在创建(spawn)期间(用于注册密码短语)、冷启动(用于检索密码短语)和销毁(用于删除密码短语)期间,需要向密钥管理器进行额外的 API 调用。
在创建和其他类似启动的操作期间,需要向 libvirt 进行额外的 API 调用,以定义、设置值和取消定义 libvirt 中的 vTPM 密钥。
需要向对象存储(swift)进行额外的 API 调用,以创建(在 shelve 期间)、检索(unshelve)和删除(unshelve/destroy)vTPM 设备数据对象。
其他部署者影响¶
无
开发人员影响¶
各个 virt 驱动程序将能够根据需要实现模拟 vTPM。
升级影响¶
无
实现¶
负责人¶
- 主要负责人
stephenfin
- 其他贡献者
cfriesen efried
功能联络人¶
stephenfin
工作项¶
依赖项¶
无
测试¶
将添加单元测试和功能测试。可能需要为对象存储和密钥管理器服务添加新的 fixtures。
由于 a) 访问加密密钥的用户身份验证的特殊性,以及 b) 一些操作的虚拟设备文件管理,将为以下内容添加 CI 覆盖:
实时迁移
冷迁移
宿主机重启(如何?)
Shelve(卸载)和 unshelve
备份和重建
文档影响¶
操作指南和最终用户指南将相应更新。功能支持矩阵将更新。
参考资料¶
维基百科上的 TPM:https://en.wikipedia.org/wiki/Trusted_Platform_Module
Qemu 关于 tpm 的文档:https://github.com/qemu/qemu/blob/master/docs/specs/tpm.txt
请求模拟 TPM 设备的 Libvirt XML:https://libvirt.org/formatdomain.html#elementsTpm
历史¶
发布名称 |
描述 |
|---|---|
Stein |
引入 |
Train |
重新提出 |
Ussuri |
重新提出,并包含加密部分进行改进 |
Victoria |
重新提出 |