diff --git a/snapcraft.yaml b/snapcraft.yaml index c5ca2f22..e84cebb8 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -70,7 +70,7 @@ parts: source: https://git.launchpad.net/curtin source-type: git - source-commit: a349105809af6188cb394e300a99846df3d117f0 + source-commit: 1997a1614e774cbd152fa33059d53417a641dbd6 override-pull: | craftctl default diff --git a/subiquity/common/filesystem/manipulator.py b/subiquity/common/filesystem/manipulator.py index b5b4ec26..37e8dcb7 100644 --- a/subiquity/common/filesystem/manipulator.py +++ b/subiquity/common/filesystem/manipulator.py @@ -145,7 +145,7 @@ class FilesystemManipulator: if key: device = self.model.add_dm_crypt( device, - key, + key=key, recovery_key=spec.get("recovery-key"), ) devices.add(device) @@ -177,6 +177,15 @@ class FilesystemManipulator: delete_lvm_partition = delete_logical_volume + def create_cryptoswap(self, device): + dmc = self.model.add_dm_crypt( + device, + keyfile="/dev/urandom", + options=["swap", "initramfs"], + ) + self.create_filesystem(dmc, dict(fstype="swap")) + return dmc + def create_zpool(self, device, pool, mountpoint, boot=False, canmount="on"): fs_properties = dict( atime=None, @@ -341,7 +350,7 @@ class FilesystemManipulator: if key: d = self.model.add_dm_crypt( d, - key, + key=key, recovery_key=spec.get("recovery-key"), ) d._constructed_device = existing diff --git a/subiquity/common/types.py b/subiquity/common/types.py index 712f2699..320d1af8 100644 --- a/subiquity/common/types.py +++ b/subiquity/common/types.py @@ -347,6 +347,7 @@ class GuidedCapability(enum.Enum): LVM = enum.auto() LVM_LUKS = enum.auto() ZFS = enum.auto() + ZFS_LUKS = enum.auto() CORE_BOOT_ENCRYPTED = enum.auto() CORE_BOOT_UNENCRYPTED = enum.auto() @@ -381,7 +382,10 @@ class GuidedCapability(enum.Enum): ] def is_zfs(self) -> bool: - return self in [GuidedCapability.ZFS] + return self in [ + GuidedCapability.ZFS, + GuidedCapability.ZFS_LUKS, + ] class GuidedDisallowedCapabilityReason(enum.Enum): diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py index 6e532edd..970c092f 100644 --- a/subiquity/models/filesystem.py +++ b/subiquity/models/filesystem.py @@ -1068,10 +1068,11 @@ LUKS_OVERHEAD = 16 * (2**20) @fsobj("dm_crypt") -class DM_Crypt: +class DM_Crypt(_Formattable): volume: _Formattable = attributes.ref(backlink="_constructed_device") key: Optional[str] = attr.ib(metadata={"redact": True}, default=None) keyfile: Optional[str] = None + options: Optional[List[str]] = None recovery_key: Optional[RecoveryKeyHandler] = None _recovery_keyfile: Optional[str] = None _recovery_live_location: Optional[str] = None @@ -1142,6 +1143,29 @@ class DM_Crypt: def size(self): return self.volume.size - LUKS_OVERHEAD + def available(self): + if self._is_in_use: + return False + if self._constructed_device is not None: + return False + if self._fs is None: + return True + return self._fs._available() + + @property + def ok_for_raid(self): + if self._fs is not None: + if self._fs.preserve: + return self._fs._mount is None + return False + if self._constructed_device is not None: + return False + return True + + @property + def ok_for_lvm_vg(self): + return self.ok_for_raid and self.size > LVM_OVERHEAD + @fsobj("device") class ArbitraryDevice(_Device): @@ -2081,9 +2105,11 @@ class FilesystemModel: def add_dm_crypt( self, volume, - key, *, - recovery_key: Optional[RecoveryKeyHandler], + key: Optional[str] = None, + keyfile: Optional[str] = None, + options: Optional[List[str]] = None, + recovery_key: Optional[RecoveryKeyHandler] = None, root: Optional[pathlib.Path] = None, ): if not volume.available: @@ -2093,6 +2119,8 @@ class FilesystemModel: m=self, volume=volume, key=key, + keyfile=keyfile, + options=options, recovery_key=recovery_key, ) self._actions.append(dm_crypt) diff --git a/subiquity/server/controllers/filesystem.py b/subiquity/server/controllers/filesystem.py index fe87487e..f87ed533 100644 --- a/subiquity/server/controllers/filesystem.py +++ b/subiquity/server/controllers/filesystem.py @@ -216,6 +216,7 @@ class VariationInfo: GuidedCapability.LVM, GuidedCapability.LVM_LUKS, GuidedCapability.ZFS, + GuidedCapability.ZFS_LUKS, ] ), ) @@ -563,13 +564,17 @@ class FilesystemController(SubiquityController, FilesystemManipulator): bootfs_size = align_up(sizes.get_bootfs_size(gap.size), part_align) gap_boot, gap_rest = gap.split(bootfs_size) bpart = self.create_partition(device, gap_boot, dict(fstype=None)) + encrypted = choice.password is not None avail = gap_rest.size - self._info.min_size swap_size = align_down(swap.suggested_swapsize(avail=avail), part_align) if swap_size > 0: - gap_swap, gap_rootfs = gap_rest.split(swap_size) - self.create_partition(device, gap_swap, dict(fstype="swap")) - gap = gap_rootfs + gap_swap, gap = gap_rest.split(swap_size) + if encrypted: + part = self.create_partition(device, gap_swap, {}) + self.create_cryptoswap(part) + else: + self.create_partition(device, gap_swap, dict(fstype="swap")) else: gap = gap_rest rpart = self.create_partition(device, gap, dict(fstype=None)) @@ -1386,7 +1391,10 @@ class FilesystemController(SubiquityController, FilesystemManipulator): capability = GuidedCapability.DD assert mode == "reformat_disk" elif name == "zfs": - capability = GuidedCapability.ZFS + if password is not None: + capability = GuidedCapability.ZFS_LUKS + else: + capability = GuidedCapability.ZFS else: capability = GuidedCapability.DIRECT diff --git a/subiquity/server/controllers/tests/test_filesystem.py b/subiquity/server/controllers/tests/test_filesystem.py index 8e1bd683..202e361b 100644 --- a/subiquity/server/controllers/tests/test_filesystem.py +++ b/subiquity/server/controllers/tests/test_filesystem.py @@ -75,6 +75,7 @@ default_capabilities = [ GuidedCapability.LVM, GuidedCapability.LVM_LUKS, GuidedCapability.ZFS, + GuidedCapability.ZFS_LUKS, ] @@ -567,6 +568,43 @@ class TestGuided(IsolatedAsyncioTestCase): zfs_boot = self.model._mount_for_path("/boot") self.assertEqual("zfs", zfs_boot.type) + @parameterized.expand(boot_expectations) + async def test_guided_zfs_luks(self, bootloader, ptable, p1mnt): + await self._guided_setup(bootloader, ptable) + target = GuidedStorageTargetReformat( + disk_id=self.d1.id, allowed=default_capabilities + ) + await self.controller.guided( + GuidedChoiceV2( + target=target, + capability=GuidedCapability.ZFS_LUKS, + password="passw0rd", + ) + ) + [firmware, boot, swap, root] = self.d1.partitions() + self.assertEqual(p1mnt, firmware.mount) + self.assertIsNone(boot.mount) + self.assertIsNone(root.mount) + self.assertFalse(firmware.preserve) + self.assertFalse(boot.preserve) + self.assertFalse(swap.preserve) + self.assertFalse(root.preserve) + self.assertIsNone(swap.fs()) + [dmc] = self.model.all_dm_crypts() + self.assertEqual("/dev/urandom", dmc.keyfile) + self.assertEqual(["swap", "initramfs"], dmc.options) + self.assertEqual("swap", dmc.fs().fstype) + [rpool] = self.model._all(type="zpool", pool="rpool") + self.assertIsNone(rpool.path) + self.assertEqual([root], rpool.vdevs) + [bpool] = self.model._all(type="zpool", pool="bpool") + self.assertIsNone(bpool.path) + self.assertEqual([boot], bpool.vdevs) + zfs_rootfs = self.model._mount_for_path("/") + self.assertEqual("zfs", zfs_rootfs.type) + zfs_boot = self.model._mount_for_path("/boot") + self.assertEqual("zfs", zfs_boot.type) + async def test_guided_zfs_BIOS_MSDOS(self): await self._guided_setup(Bootloader.BIOS, "msdos") target = GuidedStorageTargetReformat(