diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl index 205f1513fd9..aea426c7fdf 100644 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -450,8 +450,9 @@ sub addEntry { # Include second initrd with secrets if (-e -x "$path/append-initrd-secrets") { - my $initrdName = basename($initrd); - my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets"; + # Name the initrd secrets after the system from which they're derived. + my $systemName = basename(Cwd::abs_path("$path")); + my $initrdSecretsPath = "$bootPath/kernels/$systemName-secrets"; mkpath(dirname($initrdSecretsPath), 0, 0755); my $oldUmask = umask; @@ -470,7 +471,7 @@ sub addEntry { if (-e $initrdSecretsPathTemp && ! -z _) { rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n"; $copied{$initrdSecretsPath} = 1; - $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets"; + $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$systemName-secrets"; } else { unlink $initrdSecretsPathTemp; rmdir dirname($initrdSecretsPathTemp); diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index f7e82963fba..ce2ee9b4c5d 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -152,9 +152,11 @@ let ${lib.optionalString cfg.useBootLoader '' - # Create a writable copy/snapshot of the boot disk. - # A writable boot disk can be booted from automatically. - ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img" + if ${if !cfg.persistBootDevice then "true" else "! test -e $TMPDIR/disk.img"}; then + # Create a writable copy/snapshot of the boot disk. + # A writable boot disk can be booted from automatically. + ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img" + fi NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}") @@ -370,6 +372,17 @@ in ''; }; + virtualisation.persistBootDevice = + mkOption { + type = types.bool; + default = false; + description = + lib.mdDoc '' + If useBootLoader is specified, whether to recreate the boot device + on each instantiaton or allow it to persist. + ''; + }; + virtualisation.emptyDiskImages = mkOption { type = types.listOf types.ints.positive; @@ -853,6 +866,8 @@ in # * The disks are attached in `virtualisation.qemu.drives`. # Their order makes them appear as devices `a`, `b`, etc. # * `fileSystems."/boot"` is adjusted to be on device `b`. + # * The disk.img is recreated each time the VM is booted unless + # virtualisation.persistBootDevice is set. # If `useBootLoader`, GRUB goes to the second disk, see # note [Disk layout with `useBootLoader`]. diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 0c3310cabe4..c1059f8c984 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -311,6 +311,7 @@ in { initrd-network-ssh = handleTest ./initrd-network-ssh {}; initrdNetwork = handleTest ./initrd-network.nix {}; initrd-secrets = handleTest ./initrd-secrets.nix {}; + initrd-secrets-changing = handleTest ./initrd-secrets-changing.nix {}; input-remapper = handleTest ./input-remapper.nix {}; inspircd = handleTest ./inspircd.nix {}; installer = handleTest ./installer.nix {}; diff --git a/nixos/tests/initrd-secrets-changing.nix b/nixos/tests/initrd-secrets-changing.nix new file mode 100644 index 00000000000..775c69d0142 --- /dev/null +++ b/nixos/tests/initrd-secrets-changing.nix @@ -0,0 +1,58 @@ +{ system ? builtins.currentSystem +, config ? {} +, pkgs ? import ../.. { inherit system config; } +, lib ? pkgs.lib +, testing ? import ../lib/testing-python.nix { inherit system pkgs; } +}: + +let + secret1InStore = pkgs.writeText "topsecret" "iamasecret1"; + secret2InStore = pkgs.writeText "topsecret" "iamasecret2"; +in + +testing.makeTest { + name = "initrd-secrets-changing"; + + nodes.machine = { ... }: { + virtualisation.useBootLoader = true; + virtualisation.persistBootDevice = true; + + boot.loader.grub.device = "/dev/vda"; + + boot.initrd.secrets = { + "/test" = secret1InStore; + "/run/keys/test" = secret1InStore; + }; + boot.initrd.postMountCommands = "cp /test /mnt-root/secret-from-initramfs"; + + specialisation.secrets2System.configuration = { + boot.initrd.secrets = lib.mkForce { + "/test" = secret2InStore; + "/run/keys/test" = secret2InStore; + }; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("multi-user.target") + print(machine.succeed("cat /run/keys/test")) + machine.succeed( + "cmp ${secret1InStore} /secret-from-initramfs", + "cmp ${secret1InStore} /run/keys/test", + ) + # Select the second boot entry corresponding to the specialisation secrets2System. + machine.succeed("grub-reboot 1") + machine.shutdown() + + with subtest("Check that the specialisation's secrets are distinct despite identical kernels"): + machine.wait_for_unit("multi-user.target") + print(machine.succeed("cat /run/keys/test")) + machine.succeed( + "cmp ${secret2InStore} /secret-from-initramfs", + "cmp ${secret2InStore} /run/keys/test", + ) + machine.shutdown() + ''; +}