在 AWS Nitro Enclave 上使用 Red Hat Enterprise Linux 进行机密计算
Source: Red Hat Blog
为什么在机密计算环境中运行工作负载
在传统环境中,正在使用的数据可能会受到特权内部人员、受损的 hypervisor 或能够读取服务器内存的高级恶意软件等威胁的暴露。机密计算通过将工作负载放置在硬件保护的受信执行环境(TEE)中来解决此问题,该环境会加密内存并严格控制访问,帮助确保即使是云提供商、系统管理员和系统级软件也无法查看或修改正在使用的数据。
RHEL 通过提供经过加固的企业级操作系统,在现代机密计算技术中发挥关键作用,并且与这些技术完全兼容。RHEL 与 CPU 级特性(如 AMD SEV‑SNP、Intel TDX 和 IBM Secure Execution)集成,以实现加密虚拟机和安全enclaves。RHEL 抽象了配置这些硬件保护所涉及的大部分复杂性,提供安全配置文件、attestation工具以及生命周期管理,使机密计算更易于在生产环境中部署和运行。
通过在机密计算环境中使用 RHEL 运行工作负载,组织可以满足日益严格的监管要求,并在混合或云托管架构中实现高水平的数据保证。RHEL 的长期支持生命周期、可预测的更新以及安全认证帮助确保机密计算部署随时间保持稳定和安全。这种硬件级隔离与 RHEL 企业安全能力的结合,为处理最敏感的工作负载(如金融数据、医疗记录和 AI 模型)提供了可信的基础。
在您信任的环境中部署工作负载
本指南的目的是展示如何使用机密计算技术在 TEE 中部署工作负载。机密计算的根本特性是能够证明您正在安全的环境中运行。为实现此目标,您必须:
- 以能够使用密码学测量证明其身份的方式构建工作负载。
- 部署能够验证工作负载身份以及其运行的 TEE 有效性的服务。
- 以一种方式部署工作负载,使其在未在受保护的 TEE 中执行时无法运行。
AWS EC2 支持实例上 enclave 工作负载可用的资源
操作系统和容器编排系统不对您可以运行的应用 enclave 数量设限,但必须考虑系统资源约束。具体而言,分配给每个 enclave 的 vCPU 和内存会影响单台主机上可部署的 enclave 总数。
例如,在一台拥有 8 vCPU 和 32 GB 内存的实例上,如果需要多线程,vCPU 分配是限制因素。保留 2 vCPU 给父主机后,剩余的 6 vCPU 最多只能支持三个每个占用 2 vCPU 和 8 GB 内存的 enclave。此外,在 AWS Nitro Enclave 中,每个父实例的 enclave 绝对最大数量为四个。有关多 enclave 部署的系统考虑的更多细节,请参阅官方AWS 文档。
在 AWS Nitro Enclaves 上实现机密计算
在下面的示例中,应用代码在 AWS Nitro Enclave 上的 Podman 容器中运行,父主机使用 RHEL。示例展示了如何:
- 构建并打包应用到 enclave 中。
- 通过 Linux 虚拟套接字(Vsock)进行通信,这是一种为 hypervisor 与其客体 VM 之间的高效、隔离通信而设计的机制。
- 使用 OpenSSL 验证 attestation 证书链。
- 将 enclave 测量值(PCR)与 attestation 文档中报告的值进行比对。
准备 EC2 实例
从支持 AWS Nitro Enclaves 的 EC2 实例开始(请参阅AWS 文档中最新的受支持父实例列表)。在 EC2 控制台中:
- 前往实例的高级详情。
- 启用 Nitro Enclave 选项。
- 选择RHEL 9作为操作系统。
在本示例中,使用了 m5.2xlarge 实例(8 vCPU,32 GB RAM),并配备了 30 GB 的启动卷,运行 RHEL 9.7。
实例创建完成后,通过调整实例元数据设置以及 enclave 环境的内存和 CPU 分配策略来启用 Nitro Enclave 支持。详细步骤请参见AWS 入门指南。
安装 Nitro Enclave CLI 和 SDK
对于 RHEL,从AWS Git 仓库构建 CLI 和 SDK 工具。拉取源码并遵循仓库中的构建说明。确保实例存储至少有 20 GB 可用于构建过程。
编译完成后,启动 Nitro Enclave 分配器服务:
sudo systemctl start nitro-enclaves-allocator.service
创建示例服务器和客户端
该示例使用一个简单的 Python 应用作为 enclave 服务器,通过 Vsock 与父主机上的 Python 客户端通信。
Enclave 服务器 (server.py)
import socket
VSOCK_PORT = 5000
ANY_CID = socket.VMADDR_CID_ANY
def main():
server = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
server.bind((ANY_CID, VSOCK_PORT))
server.listen(5)
print(f"[Enclave] vsock server listening on port {VSOCK_PORT}...")
while True