From 56d038e17dfb518937eb132b94b39bc25a1e513a Mon Sep 17 00:00:00 2001 From: WilliButz Date: Wed, 18 Sep 2024 19:26:31 +0200 Subject: [PATCH] nixos/tests/appliance-repart-image-verity-store: init This test should illustrate how to build a verity-protected NixOS image with systemd-repart, using the opinionated image.repart.verityStore module. --- nixos/tests/all-tests.nix | 1 + .../appliance-repart-image-verity-store.nix | 130 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 nixos/tests/appliance-repart-image-verity-store.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 27d5b79c95c4..0aff8978cb62 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -128,6 +128,7 @@ in { apcupsd = handleTest ./apcupsd.nix {}; apfs = runTest ./apfs.nix; appliance-repart-image = runTest ./appliance-repart-image.nix; + appliance-repart-image-verity-store = runTest ./appliance-repart-image-verity-store.nix; apparmor = handleTest ./apparmor.nix {}; archi = handleTest ./archi.nix {}; aria2 = handleTest ./aria2.nix {}; diff --git a/nixos/tests/appliance-repart-image-verity-store.nix b/nixos/tests/appliance-repart-image-verity-store.nix new file mode 100644 index 000000000000..3834d0a468ab --- /dev/null +++ b/nixos/tests/appliance-repart-image-verity-store.nix @@ -0,0 +1,130 @@ +# similar to the appliance-repart-image test but with a dm-verity +# protected nix store and tmpfs as rootfs +{ lib, ... }: + +{ + name = "appliance-repart-image-verity-store"; + + meta.maintainers = with lib.maintainers; [ + nikstur + willibutz + ]; + + nodes.machine = + { + config, + lib, + pkgs, + ... + }: + let + inherit (config.image.repart.verityStore) partitionIds; + in + { + imports = [ ../modules/image/repart.nix ]; + + virtualisation.fileSystems = lib.mkVMOverride { + "/" = { + fsType = "tmpfs"; + options = [ "mode=0755" ]; + }; + + "/usr" = { + device = "/dev/mapper/usr"; + # explicitly mount it read-only otherwise systemd-remount-fs will fail + options = [ "ro" ]; + fsType = config.image.repart.partitions.${partitionIds.store}.repartConfig.Format; + }; + + # bind-mount the store + "/nix/store" = { + device = "/usr/nix/store"; + options = [ "bind" ]; + }; + }; + + image.repart = { + verityStore = { + enable = true; + # by default the module works with systemd-boot, for simplicity this test directly boots the UKI + ukiPath = "/EFI/BOOT/BOOT${lib.toUpper config.nixpkgs.hostPlatform.efiArch}.EFI"; + }; + + name = "appliance-verity-store-image"; + + partitions = { + ${partitionIds.esp} = { + # the UKI is injected into this partition by the verityStore module + repartConfig = { + Type = "esp"; + Format = "vfat"; + SizeMinBytes = if config.nixpkgs.hostPlatform.isx86_64 then "64M" else "96M"; + }; + }; + ${partitionIds.store-verity}.repartConfig = { + Minimize = "best"; + }; + ${partitionIds.store}.repartConfig = { + Minimize = "best"; + }; + }; + }; + + virtualisation = { + directBoot.enable = false; + mountHostNixStore = false; + useEFIBoot = true; + }; + + boot = { + loader.grub.enable = false; + initrd.systemd.enable = true; + }; + + system.image = { + id = "nixos-appliance"; + version = "1"; + }; + + # don't create /usr/bin/env + # this would require some extra work on read-only /usr + # and it is not a strict necessity + system.activationScripts.usrbinenv = lib.mkForce ""; + }; + + testScript = + { nodes, ... }: # python + '' + import os + import subprocess + import tempfile + + tmp_disk_image = tempfile.NamedTemporaryFile() + + subprocess.run([ + "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", + "create", + "-f", + "qcow2", + "-b", + "${nodes.machine.system.build.finalImage}/${nodes.machine.image.repart.imageFile}", + "-F", + "raw", + tmp_disk_image.name, + ]) + + os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name + + machine.wait_for_unit("default.target") + + with subtest("Running with volatile root"): + machine.succeed("findmnt --kernel --type tmpfs /") + + with subtest("/nix/store is backed by dm-verity protected fs"): + verity_info = machine.succeed("dmsetup info --target verity usr") + assert "ACTIVE" in verity_info,f"unexpected verity info: {verity_info}" + + backing_device = machine.succeed("df --output=source /nix/store | tail -n1").strip() + assert "/dev/mapper/usr" == backing_device,"unexpected backing device: {backing_device}" + ''; +}