diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh index e7cf52f5e32..7247451a85d 100644 --- a/nixos/modules/installer/tools/nixos-install.sh +++ b/nixos/modules/installer/tools/nixos-install.sh @@ -188,6 +188,15 @@ nix-env --store "$mountPoint" "${extraBuildFlags[@]}" \ mkdir -m 0755 -p "$mountPoint/etc" touch "$mountPoint/etc/NIXOS" +# Create a bind mount for each of the mount points inside the target file +# system. This preserves the validity of their absolute paths after changing +# the root with `nixos-enter`. +# Without this the bootloader installation may fail due to options that +# contain paths referenced during evaluation, like initrd.secrets. +mount --rbind --mkdir "$mountPoint" "$mountPoint$mountPoint" +mount --make-rslave "$mountPoint$mountPoint" +trap 'umount -R "$mountPoint$mountPoint" && rmdir "$mountPoint$mountPoint"' EXIT + # Switch to the new system configuration. This will install Grub with # a menu default pointing at the kernel/initrd/etc of the new # configuration. diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl index 20d48cde4ca..205f1513fd9 100644 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ b/nixos/modules/system/boot/loader/grub/install-grub.pl @@ -442,7 +442,7 @@ sub copyToKernelsDir { } sub addEntry { - my ($name, $path, $options) = @_; + my ($name, $path, $options, $current) = @_; return unless -e "$path/kernel" && -e "$path/initrd"; my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel")); @@ -458,7 +458,14 @@ sub addEntry { # Make sure initrd is not world readable (won't work if /boot is FAT) umask 0137; my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX"); - system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n"; + if (system("$path/append-initrd-secrets", $initrdSecretsPathTemp) != 0) { + if ($current) { + die "failed to create initrd secrets $!\n"; + } else { + say STDERR "warning: failed to create initrd secrets for \"$name\", an older generation"; + say STDERR "note: this is normal after having removed or renamed a file in `boot.initrd.secrets`"; + } + } # Check whether any secrets were actually added if (-e $initrdSecretsPathTemp && ! -z _) { rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n"; @@ -491,7 +498,7 @@ sub addEntry { } $conf .= "\n"; } else { - $conf .= "menuentry \"$name\" " . ($options||"") . " {\n"; + $conf .= "menuentry \"$name\" " . $options . " {\n"; if ($saveDefault) { $conf .= " savedefault\n"; } @@ -511,7 +518,7 @@ sub addEntry { # Add default entries. $conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS; -addEntry("@distroName@ - Default", $defaultConfig, $entryOptions); +addEntry("@distroName@ - Default", $defaultConfig, $entryOptions, 1); $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; @@ -536,7 +543,7 @@ foreach my $link (@links) { my $linkname = basename($link); $entryName = "($linkname - $date - $version)"; } - addEntry("@distroName@ - $entryName", $link); + addEntry("@distroName@ - $entryName", $link, "", 1); } my $grubBootPath = $grubBoot->path; @@ -568,7 +575,7 @@ sub addProfile { -e "$link/nixos-version" ? readFile("$link/nixos-version") : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]); - addEntry("@distroName@ - Configuration " . nrFromGen($link) . " ($date - $version)", $link, $subEntryOptions); + addEntry("@distroName@ - Configuration " . nrFromGen($link) . " ($date - $version)", $link, $subEntryOptions, 0); } $conf .= "}\n" if $grubVersion == 2; diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index 0884b0436f8..f5455267b28 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -51,7 +51,7 @@ let boot.loader.systemd-boot.enable = true; ''} - boot.initrd.secrets."/etc/secret" = /etc/nixos/secret; + boot.initrd.secrets."/etc/secret" = ./secret; users.users.alice = { isNormalUser = true; @@ -150,8 +150,7 @@ let ) with subtest("Shutdown system after installation"): - machine.succeed("umount /mnt/boot || true") - machine.succeed("umount /mnt") + machine.succeed("umount -R /mnt") machine.succeed("sync") machine.shutdown() @@ -672,6 +671,55 @@ in { ''; }; + # Full disk encryption (root, kernel and initrd encrypted) using GRUB, GPT/UEFI, + # LVM-on-LUKS and a keyfile in initrd.secrets to enter the passphrase once + fullDiskEncryption = makeInstallerTest "fullDiskEncryption" { + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel gpt" + + " mkpart ESP fat32 1M 100MiB" # /boot/efi + + " set 1 boot on" + + " mkpart primary ext2 1024MiB -1MiB", # LUKS + "udevadm settle", + "modprobe dm_mod dm_crypt", + "dd if=/dev/random of=luks.key bs=256 count=1", + "echo -n supersecret | cryptsetup luksFormat -q --pbkdf-force-iterations 1000 --type luks1 /dev/vda2 -", + "echo -n supersecret | cryptsetup luksAddKey -q --pbkdf-force-iterations 1000 --key-file - /dev/vda2 luks.key", + "echo -n supersecret | cryptsetup luksOpen --key-file - /dev/vda2 crypt", + "pvcreate /dev/mapper/crypt", + "vgcreate crypt /dev/mapper/crypt", + "lvcreate -L 100M -n swap crypt", + "lvcreate -l '100%FREE' -n nixos crypt", + "mkfs.vfat -n efi /dev/vda1", + "mkfs.ext4 -L nixos /dev/crypt/nixos", + "mkswap -L swap /dev/crypt/swap", + "mount LABEL=nixos /mnt", + "mkdir -p /mnt/{etc/nixos,boot/efi}", + "mount LABEL=efi /mnt/boot/efi", + "swapon -L swap", + "mv luks.key /mnt/etc/nixos/" + ) + ''; + bootLoader = "grub"; + grubUseEfi = true; + extraConfig = '' + boot.loader.grub.enableCryptodisk = true; + boot.loader.efi.efiSysMountPoint = "/boot/efi"; + + boot.initrd.secrets."/luks.key" = ./luks.key; + boot.initrd.luks.devices.crypt = + { device = "/dev/vda2"; + keyFile = "/luks.key"; + }; + ''; + enableOCR = true; + preBootCommands = '' + machine.start() + machine.wait_for_text("Enter passphrase for") + machine.send_chars("supersecret\n") + ''; + }; + swraid = makeInstallerTest "swraid" { createPartitions = '' machine.succeed(