Add LUKS disk encryption support for Linux OS morphing#436
Conversation
49260d6 to
33a3ef6
Compare
The bulk of the work lives in the new `LinuxLUKSMixin` class (`osmount/luks_mixin.py`), which is then included in `BaseLinuxOSMountTools`: - `mount_os()`: check `osmorphing_info["disk_luks_passphrases"]` for each block device. confirmed LUKS containers are opened via `cryptsetup luksOpen` and the resulting `/dev/mapper/<name>` path is used in place of the raw device. `dismount_os()` closes them again after all filesystems have been unmounted. - `remove_encryption_artifacts`: after OS morphing, stale TPM2 LUKS tokens and their keyslots are killed and the corresponding `tpm2-*` options are stripped from `/etc/crypttab`. The source TPM does not exist on the destination, so leaving these in place would cause the initramfs to hang or fail on first boot. - `install_encryption_firstboot_setup`: a temporary migration keyfile is injected into the guest, `/etc/crypttab` is updated to reference it, the initramfs is rebuilt so the migrated VM can boot, GRUB is patched to use the crypttab mapper names instead of the osmount-time names, and a systemd one-shot service is installed to re-enroll TPM2 and remove the migration keyfile on the first boot of the destination VM. The firstboot shell script itself lives in `coriolis/osmorphing/osmount/resources/luks_firstboot_initramfs_tools.sh` and targets `update-initramfs`-based systems (Debian / Ubuntu).
Extends `LinuxLuksMixin` with dracut support for RHEL / Fedora / SUSE guests: - `_configure_dracut_keyfiles`: writes a `dracut.conf.d/99-coriolis-luks.conf` snippet that adds the migration keyfiles to `install_items`, ensuring dracut embeds them in the initramfs. It also probes for the `libcryptsetup-token-systemd-tpm2.so` plugin (checked against a list of known paths) and adds it explicitly, because cryptsetup loads TPM2 token plugins via `dlopen` and dracut's `ldd` analysis would otherwise miss it along with its `libtss2` dependencies. - `_build_dracut_include_args`: returns `--include` args that force-embed `/etc/crypttab` and all `coriolis_*.key` keyfiles into the initramfs image. Without an explicit crypttab embed, dracut names the mapper `luks-<UUID>` rather than the crypttab name and cannot find the keyfile at boot. - `luks_firstboot_dracut.sh`: the firstboot shell script for dracut-based systems. Runs once on first boot to re-enroll TPM2, remove the migration keyslots, and rebuild the initramfs so the embedded keyfile no longer ships in future initramfs images.
Adds `cryptsetup` to the `data-minion` Dockerfile. Required by osmount LUKS unlock / lock; `cryptsetup` `luksOpen` / `luksClose` are called over SSH on the morphing container. Adds `make_luks_device` to `test_utils.py`: formats the device with LUKS, opens it, writes a minimal Linux OS tree inside via make_os_device(), then closes the mapper. Adds integration test in which the source disk is LUKS-encrypted. The test runs a full transfer + deployment with skip_os_morphing=False, and asserts that it completed.
33a3ef6 to
1e84389
Compare
| # Runs once on first boot to re-enroll TPM2, remove migration keyslots, and | ||
| # rebuild the initramfs so the embedded keyfile is gone. | ||
|
|
||
| set -e |
There was a problem hiding this comment.
Would it make sense to use set -x, making this script easier to debug?
|
|
||
| def _configure_dracut_keyfiles(self, os_root_dir, uuid_to_keyfile): | ||
| """Write a dracut.conf.d snippet to embed keyfiles in the initramfs.""" | ||
| for dracut_bin in ["usr/bin/dracut", "usr/sbin/dracut", "sbin/dracut"]: |
There was a problem hiding this comment.
I'd just update the caller to use _detect_initramfs_tool and remove this check. The caller would then use either _configure_dracut_keyfiles or _configure_initramfs_tools_keyfiles.
|
|
||
| return "sysvinit" | ||
|
|
||
| def _register_firstboot_script_systemd(self, os_root_dir): |
There was a problem hiding this comment.
We'll also need this for user provided "firstboot" scripts. I guess we'll end up with a separate public method as part of BaseOSMorphingTools.
| disk_id = os.path.basename(self._src_device) | ||
|
|
||
| # Write a minimal Linux OS on the device, encrypted with LUKS. | ||
| test_utils.make_luks_device( |
There was a problem hiding this comment.
Later on, it might be useful to test the scenario in which there are multiple disk attachments. We'd have to ensure that Coriolis doesn't error out if the user didn't specify the keys for secondary encrypted disks.
| Reads osmorphing_info["disk_luks_passphrases"], containing mappings | ||
| {"device_path": "passphrase"}. | ||
|
|
||
| For each device that has a passphrase entry and is confirmed LUKS by |
There was a problem hiding this comment.
So the user will have to specify the luks passphrase for each disk path, right?
Do we expect those to be udev links based on the disk WWN or serial ID? For example:
/dev/disk/by-uuid/4306c488-13c8-447a-825d-3e9216876081
/dev/disk/by-id/wwn-0x6d094660793802002afcbbe61cfbcd38
/dev/disk/by-id/scsi-36d094660793802002afcbbe61cfbcd38
Do we need coriolis-web changes to accommodate this?
There was a problem hiding this comment.
As discussed during the call, the user can't know these paths beforehand since the original WWN / SCSI ID won't be preserved.
The consensus was that we're going to use the same key for all LUKS encrypted disks.
|
|
||
| _SYSTEMD_UNIT = """\ | ||
| [Unit] | ||
| Description=Coriolis LUKS migration firstboot cleanup |
There was a problem hiding this comment.
Won't it run at every boot, not just the first one?
There was a problem hiding this comment.
Nevermind, the invoked scripts are supposed to disable the service.
The bulk of the work lives in the new
LinuxLuksMixinclass(
osmount/luks_mixin.py), which is then included inBaseLinuxOSMountTools:mount_os(): checkosmorphing_info["disk_luks_passphrases"]for each block device. confirmed LUKS containers are opened viacryptsetup luksOpenand the resulting/dev/mapper/<name>path is used in place of the raw device.dismount_os()closes them again after all filesystems have been unmounted.remove_encryption_artifacts: after OS morphing, stale TPM2 LUKS tokens and their keyslots are killed and the correspondingtpm2-*options are stripped from/etc/crypttab. The source TPM does not exist on the destination, so leaving these in place would cause the initramfs to hang or fail on first boot.install_encryption_firstboot_setup: a temporary migration keyfile is injected into the guest,/etc/crypttabis updated to reference it, the initramfs is rebuilt so the migrated VM can boot, GRUB is patched to use the crypttab mapper names instead of the osmount-time names, and a systemd one-shot service is installed to re-enroll TPM2 and remove the migration keyfile on the first boot of the destination VM.The firstboot shell scripts live in
coriolis/osmorphing/osmount/resources:luks_firstboot_initramfs_tools.shand targetsupdate-initramfs-based systems (Debian / Ubuntu).luks_firstboot_dracut.sh: targets dracut-based systems.