本文记录一下我最近从 LUKS 迁移到 LUKS + LVM 的过程。整理是最好的复习!
背景
Device mapper 是 Linux 里将块设备映射成虚拟块设备的框架。
dm-crypt 是用 DM 进行透明加密的组件。例如:将 /dev/sda2
映射成 /dev/mapper/cryptsda2
,则往 /dev/mapper/cryptsda2
这个块设备写入的数据会被加密后实际写入下层的 /dev/sda2
块设备里。
LUKS 是以 dm-crypt 为基础,增加了密钥管理功能的加密实现。
我的笔记本电脑是这样的分区结构:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 476.9G 0 disk
├─nvme0n1p1 259:1 0 512M 0 part /boot
├─nvme0n1p2 259:2 0 450.0G 0 crypt /
└─nvme0n1p3 259:3 0 26.4G 0 crypt [SWAP]
一个引导分区(p1),一个主分区(p2),一个交换分区(p3)。其中主分区是用 LUKS 加密的,需要我每次开机时输入密码进行解锁;交换分区是以 plain dm-crypt 加密的,其密钥来自 /dev/urandom
提供的随机数据。交换分区以随机密钥进行加密是一种常见做法,密钥只存在于 RAM 里,关机之后交换分区就完全无法解密了,防止 RAM 里的敏感数据留在交换分区里被读取——尤其是在异常关机的时候。
休眠、但是醒不来
这样的加密交换分区有一个问题:无法休眠,确切地讲是能休眠,但是永远醒不来——因为断电之后密钥已经被丢弃了,所以重新开机的时候系统无法读取交换分区里的数据,所以无法恢复到之前的状态。
由于我的笔记本电脑几乎一直是插着电用,所以十几年来很少用到休眠的功能(更别提以前 Linux 休眠醒来之后网卡、扬声器等容易出 bug);偶尔要带出门的话,短时间我就睡眠,长时间我就直接关机。直到最近,由于一连串巧合,我的笔记本电脑没有插电,电池耗尽,系统自动尝试进入休眠状态——成功了,然后就再也醒不来了。
更糟糕的是,systemd 在系统启动时会等待交换分区出现,但因为永远等不到,所以会浪费两分钟等待直到超时。由于「从休眠中醒来」这个任务没有完成,所以下次重启电脑的时候,systemd 还会再等两分钟超时,周而复始。
我实在是不知道如何清除掉这个 flag 让它不要再等。最终我想了个解决方案:systemd 是根据 UUID 去找交换分区的,那我新建一个喂给它不就行了?于是我从日志里找到 systemd 苦等的 UUID,再用 mkswap -U that_uuid
建立一个 swap,再重启一次,果然它就不再等了。事成之后要记得 wipefs
擦掉分区签名,否则以后就一直是明文交换分区了。
结果这样的事情又发生了几次,每次我都要重启几次来修复。痛定思痛,我决定一劳永逸地解决这个问题,不然每次(不小心)触发休眠就会很麻烦。
思路
我调研了多种方法,在虚拟机里尝试了一番,发现最简单的方法是用 LUKS + LVM。
LVM 也是基于 DM 的组件,主要用于在块设备上映射出多个逻辑卷(LV),这些 LV 类似于分区,但是不受分区表的限制,可以灵活地调整,甚至可以提供快照等功能。由于我的某些执念,我一直不愿意将两个 DM 套娃使用,因此我笔记本电脑上只有 LUKS,而 NAS 上只有 LVM。实际在虚拟机里测试了一下,发现 DM 叠叠乐也没什么大不了的,因此也就接受了。至于是将 LVM 叠在 LUKS 上,还是将 LUKS 叠在 LVM 上——当然是前者,因为后者和我现在的处境没什么太大的区别,并不能解决加密交换分区的问题。
所以我的分区调整计划是(对照前文的分区结构):
- 将 450 GiB 的 p2 和 26.4 GiB 的 p3 两个分区删除,建立一个新的 476.4 GiB 的 p2 取而代之;
- 将新的 p2 作为 LUKS 容器,但不在里面直接创建文件系统,而是再叠一层 LVM,在里面创建两个 LV,一个作为主分区(root),一个作为交换分区(swap)。
根据我在虚拟机里的试验,这样调整过后,开机时系统会先解开 LUKS,然后按需求(全新启动还是从休眠中恢复)读取 root 和/或 swap。
实施
在折腾这些分区之前得先备份数据。我的主分区是 450 GiB,但只有 235 GiB 数据在里面。我有块 500 GB 的 SSD 移动硬盘可以暂存数据,这块移动硬盘里最大的一个分区是 377 GiB,倒是够存;但这分区没有加密,而且是 exFAT 文件系统,因此不适合把笔记本电脑里的数据直接复制进去。
那我把整个 LUKS 容器给 dd 进去?这就又太大了,而且由于加密数据块的信息熵极高,所以再怎么压缩也是塞不下的。要不在移动硬盘里创建一个 LUKS 容器然后把文件系统整个倒进去?但是我的笔记本电脑用的是 Ext4 文件系统,似乎并没有像 xfs_copy
那样只复制有用数据块的工具。我灵机一动,想到可以用 BorgBackup 直接直接读取 LUKS 解密后的明文块设备,由于 Borg repo 是加密的,所以可以存到不加密的移动硬盘里。
BorgBackup 甚至专门有个文档解释了这种用法:
- 首先用
zerofree
把 Ext4 文件系统里的没用的数据块归零——这工具比dd if=/dev/zero
更环保、高效 - 用
borg create --read-special repo::archive /dev/mapper/luks-xxxx
备份 Ext4 所在的块设备,那些被归零的数据块几乎不会占用存储空间 - 用
borg extract --stdout repo::archive | dd of=/dev/mapper/xxxx
恢复 Ext4 到新的块设备上
由于以下操作都是需要对系统分区进行操作,所以我是启动进 archiso 里操作的。在退出主系统之前,先把 /etc/fstab
和 /etc/crypttab
里即将没用的条目给注释掉,防止调整完后进系统时 systemd 又在那儿苦等。
第 2 步完成之后的分区调整命令(根据记忆默写;忘记屏摄了):
# 擦除 p2 上的 LUKS 签名防止被 cryptsetup 误读
wipefs -a /dev/nvme0n1p2
# 删除 p2 p3 并建立新的 p2
sgdisk -d 2 -d 3 -n 2 /dev/nvme0n1
# 在 p2 上创建 LUKS 容器
cryptsetup luksFormat /dev/nvme0n1p2
# 加载 LUKS 容器
cryptsetup open /dev/nvme0n1p2 lukslvm
# 初始化 LVM 并创建两个 LV
pvcreate /dev/mapper/lukslvm
vgcreate arch /dev/mapper/lukslvm
lvcreate -n root -l 450G lukslvm
lvcreate -n swap -L 100%FREE lukslvm
此时应该已经有 /dev/mapper/arch-root
和 /dev/mapper/arch-swap
两个 LV 了。它们的下层设备是 /dev/mapper/lukslvm
这个 LUKS 容器,而 LUKS 容器的下层设备是 /dev/nvme0n1p2
这个 SSD 的分区。这就是 DM 叠叠乐!
此时执行第 3 步,将之前备份的 Ext4 写入到 /dev/mapper/arch-root
上。写入成功后 file -sL /dev/mapper/arch-root
或 blkid
应该能观察到和原先一样的 UUID。
上述操作中,第 2 步耗时 53 分钟,450 GiB 的文件系统(已用 235 GiB)最终被去重和压缩成 162 GiB 的 Borg repo;第 3 步将压缩后的文件系统写回 450 GiB 的块设备里,耗时 143 分钟。
恢复完成之后,需要调整一下 boot loader 传递给内核的参数:
options rd.luks.uuid=5834fee0-d5f5-4985-aef1-c55d50bd069c rd.luks.options=discard root=UUID=e15bf41d-3922-49a8-abc2-7640f66e318c rw
其中 rd.luks.uuid=
是 LUKS 容器(上文的 /dev/mapper/lukslvm
)的 UUID,因为重建了容器,所以这个值需要更新;而 root=
是主分区的 UUID,因为我整个 Ext4 导出到导入,一个字节都没有动过,所以这个值是不变的。
除了更新内核参数,还要确保 initrd 里有 LVM 相关的组件。调整完成之后,退出 archiso 重启,成功进系统!
现在分区布局变成了这样:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nvme0n1 259:0 0 476.9G 0 disk
├─nvme0n1p1 259:1 0 512M 0 part /boot
└─nvme0n1p2 259:2 0 476.4G 0 part
└─luks-5834fee0-d5f5-4985-aef1-c55d50bd069c 254:0 0 476.4G 0 crypt
├─arch-root 254:1 0 450G 0 lvm /
└─arch-swap 254:2 0 26.4G 0 lvm [SWAP]
试试休眠……成功了!试试唤醒……也成功了!
再也不用小心翼翼害怕休眠了!