背景 之前得到了一台性能强劲的 EPYC 9654 服务器, 在上面非常愉快的跑了许多数据分析任务, 甚至是 LLM. 但最近想用它处理一些私密的数据的时候, 就遇到了困难…
我并不是这台服务器唯一的用户, 这台公交车服务器的权限管理混乱的十分典型 - 所有人的账号都共用一个密码, 所有人都在 sudo 组, 服务器的 root 密码在各个微信/QQ群被转发来转发去, 同时服务器的 SSH 还被内网穿透转发到了公网. 甚至连这台服务器的 IPMI 都和操作系统共用了同一个并不算强的用户名和密码对.
这样的环境可以说是和”安全”没有任何一丝关系, 任何一个用户或外来攻击者都可以轻易读取到我硬盘上或内存中的数据. 但这样的服务器在学校内网又大规模存在, 毕竟大家都只想追求便捷, 没人在意安全. 如果我加固服务器的安全措施, 会给其他用户带来不便.
于是乎, 我开始探索如何要在如此混沌的环境下安全的计算我的数据, 经过简单的搜索, 发现了 AMD Secure Encrypted Virtualization (SEV) 这一技术.
嗨呀, 这不可信计算嘛. 各大云厂商包括 Azure , AWS , GCP 都对此有支持, 看起来算是基本成熟的技术, 那在自己的服务器上配置应该也并非难事.(flag
尽管可信计算在民用领域口碑不佳, 常被用于搞 vendor lock-in 或是 DRM, 做各类限制用户自由和选择权的反用户功能, 通过垄断安全和可信的定义权以无底线的谋求商业利益. (说的就是你, Google 和 Play Integrity)
但至少在主流桌面端 x86 架构这边, Secure Boot 还可以自由 opt-out, 加密虚拟化还是服务器 CPU 的特权. 除了用户需要对 SEV 这种厂商专有功能有依赖和信任外, 整体可以算是一个良好的安全功能. 让我们来尝试一下它.
AMD 的 SEV 有以下几种:
SEV, 安全加密虚拟化 . 加密虚拟机内存. SEV-ES, 安全加密虚拟化-加密状态 . 加密虚拟机内存以及使用的寄存器. SEV-SNP, 安全加密虚拟化-安全嵌套分页 . 在前两者基础上加密内存映射, 保证内存完整性. SEV 和 SEV-ES 需要二代 EPYC 或以上, SEV-SNP 需要三代 EPYC 或以上. 作为 EPYC 9004 系列, 我们直接选择尝试最新的 SEV-SNP.
配环境 整个互联网上关于 SEV-SNP 的资料并不多, 只找到一份详细的 SEV-ES 的资料 (我尝试使用了这篇文章里和 QEMU WIki 的启动参数还启动失败了) , 故我记录下 SEV-SNP 配置过程, 以供参考.
Host Bios 首先, 需要修改主机 BIOS. 我这台服务器的主板是 H13SSL-N, SEV 相关配置默认没有开启. 直接重启服务器, 连接到 IPMI, 通过网页操作 BIOS 修改如下配置:
开启 SMEE(内存加密), 开启 SEV Control, 将 SEV-ES ASID Space Limit 调整为 > 1 的值以开启 SEV-ES, 开启 RMP Table.
BIOS CPU 设置页
开启 SEV-SNP Support
BIOS 南桥设置页
Host Linux 配置 CPU 和 BIOS 提供最基本的支持后, 还需要 Host 的 Hypervisor, QEMU 以及 QEMU 运行的 OVMF 固件支持.
目前 SEV-SNP 作为一种新技术, 仅仅在几天前才在最新的 Ubuntu 25.04 得到作为 Host 侧的支持 . 我使用的 Ubuntu 22.04 LTS 显然是无缘这类功能更新了, 只能自己编译需要的工具.
还好 GitHub 上有好心人已经整理好了相关的脚本 , 我们可以直接拉取使用. (理论上你应该在一个可信的环境下构建所需的工具, 特别是下面的 OVMF 固件, 不过我这里暂时偷懒了)
Ubuntu 22.04 LTS 在构建时遇到了一些依赖缺失, 比如 nasm iasl debhelper 之类的, 根据报错确认问题, 手动补安装即可.
1 2 3 4 5 6 7 8 9 git clone https://github.com/AMDESE/AMDSEV.git git checkout snp-latest ./build.sh qemu ./build.sh ovmf ./build.sh kernel sudo cp kvm.conf /etc/modprobe.d/
构建成功之后使用 apt 安装构建出的 Host kernel, 重启后在 GRUB 选择新 kernel 启动.
1 2 3 cd linuxapt install ./linux-headers-6.11.0-rc3-snp-host-85ef1ac03941_6.11.0-rc3-g85ef1ac03941-2_amd64.deb ./linux-libc-dev_6.11.0-rc3-g85ef1ac03941-2_amd64.deb ./linux-image-6.11.0-rc3-snp-host-85ef1ac03941_6.11.0-rc3-g85ef1ac03941-2_amd64.deb reboot
等待 Host 重启之后, 已经可以在 dmesg 中看到 SEV-SNP 正常加载的日志.
1 2 3 4 5 6 7 8 9 10 11 12 13 test @epyc:~$ uname -aLinux epyc 6.11.0-rc3-snp-host-85ef1ac03941 test @epyc:~$ sudo dmesg | grep SEV[ 0.000000] SEV-SNP: RMP table physical range [0x0000000015500000 - 0x0000000075afffff] [ 0.003519] SEV-SNP: Reserving start/end of RMP table on a 2MB boundary [0x0000000015400000] [ 0.003526] SEV-SNP: Reserving start/end of RMP table on a 2MB boundary [0x0000000075a00000] [ 13.101084] ccp 0000:03:00.5: SEV API:1.55 build:36 [ 13.101094] ccp 0000:03:00.5: SEV-SNP API:1.55 build:36 [ 13.127843] kvm_amd: SEV enabled (ASIDs 128 - 1006) [ 13.127844] kvm_amd: SEV-ES enabled (ASIDs 1 - 127) [ 13.127845] kvm_amd: SEV-SNP enabled (ASIDs 1 - 127) test @epyc:~$ cat /sys/module/kvm_amd/parameters/sev_snp Y
启动 Guest 准备好一个安装好 Linux 的 Guest 磁盘, 以 qcow2 格式存储并传输到 Host 上, 我这里选择 Debian 13. 相对新的发行版已经内置了 SEV-SNP 的 Guest 支持.
1 2 3 sudo ./launch-qemu.sh -hda ../debian13-secure.qcow2 -sev-snp -mem 16384 -smp 16
通过 SSH 或其他方法连接到运行的虚拟机, 通过 dmesg 可以发现客户端的 SEV-SNP 已经启动.
1 2 3 4 5 6 7 8 root@sevsnp:~# dmesg | grep SEV [ 2.350837] Memory Encryption Features active: AMD SEV SEV-ES SEV-SNP [ 2.350855] SEV: Status: SEV SEV-ES SEV-SNP [ 2.470226] SEV: APIC: wakeup_secondary_cpu() replaced with wakeup_cpu_via_vmgexit() [ 3.806622] SEV: Using SNP CPUID table, 29 entries present. [ 3.806628] SEV: SNP running at VMPL0. [ 4.118602] SEV: SNP guest platform device initialized. [ 15.268999] sev-guest sev-guest: Initialized SEV guest driver (using VMPCK0 communication key)
成功启动受 SEV-SNP 保护的 Guest 虚拟机
对 Guest 进行 Attestation 看起来 我们已经运行了受 SEV-SNP 保护的虚拟机, 这个 VM 的内存和寄存器状态受到 AMD Secure Processor 的保护, 无法被不受信任的 Hypervisor (在这里就是我们的 EPYC 9654 服务器) 读取或篡改.
但设想另一种情况: 假设有个足够高明的攻击者, 在我将上面的 debian13 qcow2 镜像传输到 EPYC 9654 上时, 立即对其 kernel 进行了篡改和替换, 使其明明没有受到 SEV-SNP 保护的情况下输出了 SEV-SNP 相关的日志, 也能得到上面的结果, 让我以为它受到保护了.
换句话说, 我们目前还无法证明 这台虚拟机受到了保护. 要进一步确定虚拟机受到 SEV-SNP 保护, 且运行的是受信任的软件, 我们需要对其进行远程 Attestation (度量).
准备 measurement 让我们转移阵地到一个受信任 的设备上, 比如我的 HomeLab, 进行一些准备工作.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 pip install sev-snp-measure git clone https://github.com/AMDESE/AMDSEV.git cd AMDSEVgit checkout snp-latest touch ovmf/OvmfPkg/AmdSev/Grub/grub.efi ./build.sh ovmf cd ..cp AMDSEV/ovmf/Build/AmdSev/DEBUG_GCC5/FV/OVMF.fd .sudo cp /boot/vmlinuz . echo -e '\nsev-guest' > /etc/initramfs-tools/modulessudo update-initramfs -u sudo cp /boot/initrd.img . sudo unmkinitramfs -v initrd.img . wget https://github.com/virtee/snpguest/releases/download/v0.9.1/snpguest sudo chown root:root snpguest sudo chmod 755 snpguest sudo cp snpguest main/usr/bin sudo cp /usr/bin/base64 main/usr/bin cd mainfind . | sudo cpio -o -H newc | gzip > ../myinitrd.cpio.gz cd ..sev-snp-measure --mode snp --vcpus 16 --vcpu-type EPYC-v4 --ovmf OVMF.fd --kernel vmlinuz --initrd myinitrd.cpio.gz --append "console=ttyS0"
启动 Guest 我们把刚刚得到的 OVMF.fd, vmlinuz, myinitrd.cpio.gz 这三个文件复制到 EPYC 9654 上.
1 2 3 sudo ./launch-qemu.sh -sev-snp -bios .. -cpu EPYC-v4 -smp 16 -mem 16384 -kernel ../vmlinuz -initrd ../myinitrd.cpio.gz -append "console=ttyS0" -hda ""
从日志可以看到, 最终执行的真正指令是
1 /mnt/ssd/qemu/AMDSEV/usr/local/bin/qemu-system-x86_64 -enable-kvm -cpu EPYC-v4 -machine q35 -netdev user,id =vmnic,hostfwd=tcp::8000-:22 -device virtio-net-pci,disable-legacy=on,iommu_platform=true ,netdev=vmnic,romfile= -vnc :1 -device virtio-vga -smp 16,maxcpus=255 -m 16384M,slots=5,maxmem=24576M -no-reboot -bios /mnt/ssd/qemu/OVMF.fd -machine confidential-guest-support=sev0,vmport=off -object memory-backend-memfd,id =ram1,size=16384M,share=true ,prealloc=false -machine memory-backend=ram1 -object sev-snp-guest,id =sev0,policy=0x30000,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on -kernel ../vmlinuz -append "console=ttyS0" -initrd ../myinitrd.cpio.gz -nographic -monitor pty -monitor unix:monitor,server,nowait
启动成功后, 由于我们目前没有挂载任何磁盘, initramfs 无法继续启动, 停留在了 busybox shell 中.
启动到了 initramfs 的虚拟机
进行 Attestation 并验证结果 回到我们受信任的机器, 生成一个现代安全领域非常常见的 nonce 用于防止重放攻击.
1 2 openssl rand -hex 64 > nonce.hex
再到我们的虚拟机, 把 nonce.hex 写入文件, 并生成 Attestation 报告:
1 2 3 4 5 6 echo '3502ae46269024fda5eea969d942f15b9cf9708602709d97b0de1ceaa0b712c7a6ea5170310fd6f10e9f1ad223cb4ffbc8fd036a70846cb7d50f3086c64c2da0' > nonce.hexsnpguest report report.bin nonce.hex cat report.bin | base64
再把这个 report.bin 写回受信任的 HomeLab 机器, 调用 snpguest 进行验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 snpguest fetch ca pem . genoa snpguest fetch vcek -p genoa pem . report.bin snpguest verify certs . snpguest verify attestation -p genoa . report.bin
此时, 我们得到了一份确认由 AMD 签名的 Attestation Report, 可以使用 snpguest display report report.bin 阅读其内容.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 Attestation Report: Version: 2 Guest SVN: 0 Guest Policy (0x30000): ABI Major: 0 ABI Minor: 0 SMT Allowed: true Migrate MA: false Debug Allowed: false Single Socket: false Family ID: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Image ID: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 VMPL: 1 Signature Algorithm: 1 Current TCB: TCB Version: Microcode: 72 SNP: 21 TEE: 0 Boot Loader: 9 FMC: None Platform Info (1): SMT Enabled: true TSME Enabled: false ECC Enabled: false RAPL Disabled: false Ciphertext Hiding Enabled: false Alias Check Complete: false Key Information: author key enabled: false mask chip key: false signing key: vcek Report Data: 33 35 30 32 61 65 34 36 32 36 39 30 32 34 66 64 61 35 65 65 61 39 36 39 64 39 34 32 66 31 35 62 39 63 66 39 37 30 38 36 30 32 37 30 39 64 39 37 62 30 64 65 31 63 65 61 61 30 62 37 31 32 63 37 Measurement: 49 36 D4 2A 8E 2C 2E 91 F1 F1 16 00 63 09 7B 43 B5 C0 C3 33 9F 5F 85 58 61 28 35 6E 16 D4 CF 36 EB E5 65 7F BB F9 C3 16 3D CF 60 6B DF 7E 54 2C Host Data: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ID Key Digest: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Author Key Digest: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Report ID: C5 13 43 3F A5 F7 D2 F9 24 91 AE AE DD B2 33 D3 5A 23 4A 81 90 90 F4 CA A6 75 8B 38 A0 F3 05 8C Report ID Migration Agent: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF Reported TCB: TCB Version: Microcode: 72 SNP: 21 TEE: 0 Boot Loader: 9 FMC: None CPUID Family ID: None CPUID Model ID: None CPUID Stepping: None Chip ID: 59 54 5A 6F D7 AF 46 35 51 28 B9 55 4B 1A 0C 86 6D 88 1A A5 E6 5F CB 02 24 49 51 78 F1 0B 57 7D C4 1D FC 64 82 57 E1 AC DB DB 2E 03 24 17 F2 02 A3 F1 D3 48 60 38 52 A3 10 62 7C 72 AA 27 68 BD Committed TCB: TCB Version: Microcode: 72 SNP: 21 TEE: 0 Boot Loader: 9 FMC: None Current Version: 1.55.36 Committed Version: 1.55.36 Launch TCB: TCB Version: Microcode: 72 SNP: 21 TEE: 0 Boot Loader: 9 FMC: None Signature: R: 23 48 89 73 E1 AE D6 B1 31 6C 8A 79 00 DF DA DB 84 CB C6 0A 1D EC AE 71 0D 7C E1 7A 33 C1 80 C1 86 1A A4 05 08 A0 A4 A9 C9 AD 5B 1B D8 03 A6 C2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 S: D8 AC 18 0F 3F 47 E8 F2 6A 8E AC 6C F2 A8 04 E3 1B 41 C0 4E F1 5A 51 30 79 8E C0 A0 69 61 73 EE B2 FC 6B 6C 72 84 F3 1B 0E 3A 22 B1 9B AB 2B D1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
可以看到其中包含我们生成的 Nonce (Report Data 区域), 且 Measurement 的值与我们在本地可信环境计算得到的一致.
至此, 我们只要信任 AMD, 我们就已经通过密码学手段确认: 这台虚拟机确实运行在一台真实的受信任的 AMD 系统上, 且其内存保密性和完整性受到 AMD SEV-SNP 技术保护, 且其正在运行我们在可信环境中取得的, 未经篡改的软件镜像 (包括 OVMF, Linux 内核, initramfs 以及启动时的 cmdline).
传入密钥并完成启动 经过 Attestation, 我们已经有理由相信我们在这台可疑的 EPYC 9654 服务器上成功启动了一个可信的虚拟机, 最困难的部分已经搞定了, 接下来只要简简单单启动一个完整的 Linux Distro 就可以开始计算了… 吗?
仔细想想会发现并非.
再回顾一下: 我们现在只是启动了指定的内核和 initramfs, 要启动一个完整的 Linux 环境进行计算任务, 我们还需要加载一个可信的 rootfs. 就算你头铁到不要 rootfs 直接把整个计算环境打包进 initramfs, 我们最终目的还是要把机密的数据传进这台 VM 的内存中进行计算.
直接将机密数据打包进 initramfs 是不可行的, 因为其本身是明文, 并未被加密. 同理, 直接打包任何形式的凭据, 密钥或私钥到 initramfs 都是不可行的.
通过网络或者终端将密钥或机密数据传输给 VM 同样不行, 因为这类通讯可以轻易被中间人 (Host) 的监听. 你可能马上会想到 SSH, 但 SSH 的安全性依赖 SSH Host Key 不被泄露, 可 Hypervisor 早已掌握存在于 initramfs 中的 SSH Host Key.
说了这么多, 其实根本的问题就是: Hypervisor 目前与我们加密的 VM 掌握的信息完全相同, 在 Hypervisor 成为天然中间人的情况下, 尽管我们知道存在这么一台加密的 VM, 我们却无法区分 VM 和 Hypervisor.
在曾经的 SEV-ES 中, 我们可以直接构建加密的 secret , 由 AMD CPU 解密后直接传递给 VM, 非常轻松的解决密钥传递的问题. 而新一代的 SEV-SNP 中, 这个功能看似被一个十分复杂的 SVSM 模块取代了. (相关讨论 )
这个地方确实让我卡了一段时间, 看来还是现代密码学学的不够好. 其实巧妙利用我们上面完成的 Attestation 过程, 我们已经可以区分出加密的 VM 和 Hypervisor.
具体思路是:
可信环境中, 我们生成一个 Nonce 提供 Nonce 给加密的 VM, VM 使用 Nonce 作为 Report Data 生成 Attestation Report 加密 VM 在内存中生成一对公私钥 加密 VM 使用公钥作为 Report Data 生成第二份 Attestation Report 可信环境中, 我们验证这两份 Attestation Report. 第一份证明加密 VM 确实可信, 第二份则携带了经由 AMD 签名的公钥. 这个公钥无法被恶意的 Hypervisor 篡改 可信环境中使用公钥加密我们想要传递的 Secret (例如加密磁盘的密钥) 加密 VM 使用私钥在内存中解密讯息, 得到 Secret 需要注意的是, 这里的 initramfs 不能信任 tty 的输入, 不能直接提供 shell, 必须只能按上述流程接受必要的信息. 由于 Attestation Report 中包含了 initramfs 的 Hash, 只要合理编写 init 脚本, 我们是可以完全控制 initramfs 本身的行为的.
这个流程是相对完整的, 其实偷懒一点的话, 可以只生成并验证第二份 Attestation Report, 相当于使用了一个来自 VM 内部的 Nonce.
理论可行实践开始 再次回到可信的 HomeLab, 先准备一个加密过后的 rootfs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 truncate -s 5G rootfs_enc.rawsudo losetup -f rootfs_enc.raw losetup -a sudo cryptsetup luksFormat --type luks2 /dev/loop28 --integrity hmac-sha256 sudo cryptsetup luksOpen /dev/loop28 guest_rootfs sudo mkfs.ext4 /dev/mapper/guest_rootfs mkdir rootfssudo mount /dev/mapper/guest_rootfs rootfs sudo debootstrap --arch =amd64 jammy rootfs http://mirror.nju.edu.cn/ubuntu/ sudo chroot rootfs passwd root cat << EOF > /etc/netplan/01-config.yaml network: version: 2 ethernets: enp0s1: dhcp4: true EOF apt update && apt install ssh exit sudo umount rootfs sudo cryptsetup luksClose guest_rootfs sudo losetup -d /dev/loop28
再准备下实现了上述”握手”过程的 initramfs. 内置的 /init 脚本过于复杂, 我对其研究甚浅, 我选择直接整个替换掉, 反正该用照用.
以及, 这里如果你之前没有安装过 cryptsetup, 是刚刚搓 rootfs 时候装的, 之前解包的 initrd.img 里也可能没有 cryptsetup 相关内容. 需要重新解包一次. 你可能还需要在 /etc/initramfs-tools/modules 添加 dm_integrity 并重新更新且解包 initrd.img.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #!/bin/sh LUKS_DEVICE="/dev/sda" MAPPER_NAME="encroot" FS_TYPE="ext4" NEW_ROOT_MP="/new_root" echo "==> [INITRAMFS] Starting LUKS init script..." echo "==> [INITRAMFS] Mounting essential filesystems..." mkdir /proc /sysmount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev echo "==> [INITRAMFS] Loading crypto modules..." modprobe sev-guest modprobe dm_crypt modprobe dm_integrity echo "==> [INITRAMFS] Starting remote attestation process..." read -r -p "Enter nonce:" NONCEecho " -> Generating attestation report with nonce..." echo -n $NONCE > /nonce.txtsnpguest report /report1.bin /nonce.txt if [ $? -ne 0 ]; then echo "!!! [INITRAMFS] FAILED to get attestation report." exit 1 fi echo " -> Generating ephemeral key pair..." age-keygen -o /vm_key.txt EPHEMERAL_PUBLIC_KEY=$(grep "public key: " /vm_key.txt | cut -d ' ' -f 4) echo " -> Public key generated." echo " -> Generating attestation report with public key..." echo -n "$EPHEMERAL_PUBLIC_KEY " > /pub.txt snpguest report /report2.bin /pub.txt if [ $? -ne 0 ]; then echo "!!! [INITRAMFS] FAILED to get attestation report." exit 1 fi echo " -> Attestation report generated." echo "----- REPORT 1 -----" cat /report1.bin | base64 echo "----- REPORT 2 -----" cat /report2.bin | base64 read -r -p "Enter encrypted LUKS key (age format, one-line base64):" ENCRYPTED_LUKS_KEY_B64echo $ENCRYPTED_LUKS_KEY_B64 | base64 -d > /luks.ageecho "==> [INITRAMFS] Decrypting LUKS key..." DECRYPTED_LUKS_KEY=$(age --decrypt -i /vm_key.txt /luks.age) if [ $? -ne 0 ] || [ -z "${DECRYPTED_LUKS_KEY} " ]; then echo "!!! [INITRAMFS] FAILED to decrypt LUKS key." exit 1 fi rm /vm_key.txt /luks.age /pub.txt /report1.bin /report2.binecho "==> [INITRAMFS] Attempting to unlock LUKS device: ${LUKS_DEVICE} " echo -n "${DECRYPTED_LUKS_KEY} " | cryptsetup luksOpen ${LUKS_DEVICE} ${MAPPER_NAME} --key-file -if [ $? -ne 0 ]; then echo "!!! [INITRAMFS] FAILED to unlock LUKS device with provided key." exit 1 fi echo "==> [INITRAMFS] LUKS device unlocked successfully as /dev/mapper/${MAPPER_NAME} " echo "==> [INITRAMFS] Creating mount point for the real root..." mkdir ${NEW_ROOT_MP} echo "==> [INITRAMFS] Mounting decrypted root filesystem..." mount -t ${FS_TYPE} /dev/mapper/${MAPPER_NAME} ${NEW_ROOT_MP} if [ $? -ne 0 ]; then echo "!!! [INITRAMFS] FAILED to mount decrypted root filesystem." exit 1 fi echo "==> [INITRAMFS] Real root filesystem mounted successfully." echo "==> [INITRAMFS] Unmounting temp filesystems..." umount /proc umount /sys umount /dev echo "==> [INITRAMFS] Switching to real root and executing init..." exec switch_root ${NEW_ROOT_MP} /sbin/initecho "!!! [INITRAMFS] FAILED to switch_root." exit 1
将如上的 init 和所有用到的工具打包到 initramfs 中. 在本地计算好 Measurement 值, 并将其发送到服务器, 准备工作就终于完成了!
我们启动虚拟机, 本地提供一个随机 nonce, 可以得到两份 report.bin 文件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [ 7.791052] Run /init as init process ==> [INITRAMFS] Starting LUKS init script... ==> [INITRAMFS] Mounting essential filesystems... ==> [INITRAMFS] Loading crypto modules... [ 7.829855] sev-guest sev-guest: Initialized SEV guest driver (using vmpck_id 0) [ 7.850841] xor: automatically using best checksumming function avx [ 7.857134] async_tx: api initialized (async) ==> [INITRAMFS] Starting remote attestation process... Enter nonce:f5c837b576373887112d46fcd20a77ac14ca5be663ca6d4913bd79e13e8d7c2e -> Generating attestation report with nonce... -> Generating ephemeral key pair... Public key: age15lz45488nzmk3q4kmfdg9489s7652h0h04vdekjhnptwa5wnff3snv45ly -> Public key generated. -> Generating attestation report with public key... -> Attestation report generated. ----- REPORT 1 ----- AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAJ <下略...> ----- REPORT 2 ----- AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAJ <下略...> Enter encrypted LUKS key (age format, one-line base64):
我们在本地验证两个 Report 的签名, 再确认两份的 Measurement 值与我们本地测算的一致.
随后确认第一份的 Report Data 与我们随机生成的 nonce 一致, 取第二份的 Report Data 解码成为 age 公钥, 加密我们的 LUKS 密钥传递给虚拟机.
1 2 3 4 5 6 7 8 9 snpguest verify attestation -p genoa . report1.bin snpguest verify attestation -p genoa . report2.bin snpguest display report report1.bin snpguest display report report2.bin echo -n "AMD,YES\!" | age -r age15lz45488nzmk3q4kmfdg9489s7652h0h04vdekjhnptwa5wnff3snv45ly | base64 -w 0
将得到的加密过后的密钥粘贴给虚拟机, 看到密钥正确加载, 启动流程继续, 我们成功进入了熟悉的 Ubuntu 22.04 系统~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Enter encrypted LUKS key (age format, one-line base64):YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBITW5Bclp1ZmNiU01yZmlicmhpWmJaMG1KUExRTW5DNnI4Nmo3K3FZNTBBCkZwQU5EelNtNjN2SDlQeUtBVEVGZSs0dk5WWldMTUhLSTlCblY3c3k2bE0KLS0tIFhWR3IvY0NUSkZuemplS0FRa0ozbGRvQ0Y4WS81NDQrdGJQUkVyMXBiRWsKR3IVz1uPSJtD/Uc9ojYZ2fChdRumQk7YxAKv5JUbffWvFevi/Lhyww== ==> [INITRAMFS] Decrypting LUKS key... ==> [INITRAMFS] Attempting to unlock LUKS device: /dev/sda ==> [INITRAMFS] LUKS device unlocked successfully as /dev/mapper/encroot ==> [INITRAMFS] Creating mount point for the real root... ==> [INITRAMFS] Mounting decrypted root filesystem... [493.983329] EXT4-fs (dm-1): mounted filesystem b155f911-e042-4949-b6bb-3a01f66445bf r/w with ordered data mode. Quota mode: none. ==> [INITRAMFS] Real root filesystem mounted successfully. ==> [INITRAMFS] Unmounting temp filesystems... ==> [INITRAMFS] Switching to real root and executing init... [494.535955] systemd[1]: Failed to look up module alias 'autofs4': Function not implemented [494.571956] systemd[1]: systemd 249.11-0ubuntu3 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT +GNUTLS -OPENSSL +ACL +BLKID +CURL +ELFUTILS -FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP -LIBFDISK +PCRE2 -PWQUALITY -P11KIT -QRENCODE +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified) [494.578432] systemd[1]: Detected virtualization kvm. [494.579548] systemd[1]: Detected architecture x86-64. Welcome to Ubuntu 22.04 LTS! <随后就进入正常的启动流程了>
很快启动成功, 我们就能看到 tty 上的登录 prompt. 但我们不要直接在 QEMU 终端中登录, 这是不安全的. 我们已经配置好 SSH, 并且预先记下了 Host Key 的公钥, 直接通过 SSH 即可安全地连接到加密的 VM.
启动成功后看起来平平无奇的 VM
尾声 第一次尝试自己使用可信计算技术, 除了要手动构建 QEMU 和内核外, 整体的流程和工具还不算复杂, 相比纯 SEV/SEV-ES 的 attestation 过程也简化了很多. 顺便回顾了 Linux 启动的流程, 构建了从 UEFI 固件 -> Kernel -> initramfs -> Ubuntu 的信任链. 动手之前也没想到, 一个最小的 initramfs 能够这么简单.
这篇博客也是来自我的突发奇想, 最终能在硬件和软件都完全不受信任的机器上构建出安全的计算环境, 这还是挺有趣的. 搞了好几天, 谁还记得开始我其实只是想校验一下我在服务器上的 restic 备份… 其实我这点文件直接明文放着都没人感兴趣.