Merge pull request #168554 from helsinki-systems/feat/systemd-stage-1-luks

nixos/systemd-stage-1: Add basic LUKS support
This commit is contained in:
Bernardo Meurer 2022-04-22 12:37:25 -07:00 committed by GitHub
commit b23ec41f14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 175 additions and 6 deletions

View File

@ -1,10 +1,11 @@
{ config, lib, pkgs, ... }:
{ config, options, lib, pkgs, ... }:
with lib;
let
luks = config.boot.initrd.luks;
kernelPackages = config.boot.kernelPackages;
defaultPrio = (mkOptionDefault {}).priority;
commonFunctions = ''
die() {
@ -474,6 +475,16 @@ let
preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: let
opts = v.crypttabExtraOpts
++ optional v.allowDiscards "discard"
++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ]
++ optional (v.header != null) "header=${v.header}"
++ optional (v.keyFileOffset != null) "keyfile-offset=${v.keyFileOffset}"
++ optional (v.keyFileSize != null) "keyfile-size=${v.keyFileSize}"
;
in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices));
in
{
imports = [
@ -802,6 +813,18 @@ in
Commands that should be run right after we have mounted our LUKS device.
'';
};
crypttabExtraOpts = mkOption {
type = with types; listOf singleLineStr;
default = [];
example = [ "_netdev" ];
visible = false;
description = ''
Only used with systemd stage 1.
Extra options to append to the last column of the generated crypttab file.
'';
};
};
}));
};
@ -853,6 +876,31 @@ in
-> versionAtLeast kernelPackages.kernel.version "5.9";
message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
}
{ assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices);
message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1.";
}
{ assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preLVM) (attrValues luks.devices);
message = "boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1.";
}
{ assertion = config.boot.initrd.systemd.enable -> options.boot.initrd.luks.reusePassphrases.highestPrio == defaultPrio;
message = "boot.initrd.luks.reusePassphrases has no effect with systemd stage 1.";
}
{ assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preOpenCommands == "" && dev.postOpenCommands == "") (attrValues luks.devices);
message = "boot.initrd.luks.devices.<name>.preOpenCommands and postOpenCommands is not supported by systemd stage 1. Please bind a service to cryptsetup.target or cryptsetup-pre.target instead.";
}
# TODO
{ assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport;
message = "systemd stage 1 does not support GPG smartcards yet.";
}
# TODO
{ assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support;
message = "systemd stage 1 does not support FIDO2 yet.";
}
# TODO
{ assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport;
message = "systemd stage 1 does not support Yubikeys yet.";
}
];
# actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
@ -867,7 +915,7 @@ in
++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
# copy the cryptsetup binary and it's dependencies
boot.initrd.extraUtilsCommands = ''
boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
@ -915,7 +963,7 @@ in
''}
'';
boot.initrd.extraUtilsCommandsTest = ''
boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
$out/bin/cryptsetup --version
${optionalString luks.yubikeySupport ''
$out/bin/ykchalresp -V
@ -932,9 +980,27 @@ in
''}
'';
boot.initrd.preFailCommands = postCommands;
boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
boot.initrd.systemd = {
contents."/etc/crypttab".source = stage1Crypttab;
extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup";
additionalUpstreamUnits = [
"cryptsetup-pre.target"
"cryptsetup.target"
"remote-cryptsetup.target"
];
storePaths = [
"${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup"
];
};
# We do this because we need the udev rules from the package
boot.initrd.services.lvm.enable = true;
boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands;
boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands);
boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands);
environment.systemPackages = [ pkgs.cryptsetup ];
};

View File

@ -524,6 +524,8 @@ in
systemd-confinement = handleTest ./systemd-confinement.nix {};
systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
systemd-escaping = handleTest ./systemd-escaping.nix {};
systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {};
systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {};
systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; };
systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {};

View File

@ -0,0 +1,53 @@
import ./make-test-python.nix ({ lib, pkgs, ... }: let
keyfile = pkgs.writeText "luks-keyfile" ''
MIGHAoGBAJ4rGTSo/ldyjQypd0kuS7k2OSsmQYzMH6TNj3nQ/vIUjDn7fqa3slt2
gV6EK3TmTbGc4tzC1v4SWx2m+2Bjdtn4Fs4wiBwn1lbRdC6i5ZYCqasTWIntWn+6
FllUkMD5oqjOR/YcboxG8Z3B5sJuvTP9llsF+gnuveWih9dpbBr7AgEC
'';
in {
name = "systemd-initrd-luks-keyfile";
nodes.machine = { pkgs, ... }: {
# Use systemd-boot
virtualisation = {
emptyDiskImages = [ 512 ];
useBootLoader = true;
useEFIBoot = true;
};
boot.loader.systemd-boot.enable = true;
environment.systemPackages = with pkgs; [ cryptsetup ];
boot.initrd.systemd = {
enable = true;
emergencyAccess = true;
};
specialisation.boot-luks.configuration = {
boot.initrd.luks.devices = lib.mkVMOverride {
cryptroot = {
device = "/dev/vdc";
keyFile = "/etc/cryptroot.key";
};
};
virtualisation.bootDevice = "/dev/mapper/cryptroot";
boot.initrd.systemd.contents."/etc/cryptroot.key".source = keyfile;
};
};
testScript = ''
# Create encrypted volume
machine.wait_for_unit("multi-user.target")
machine.succeed("cryptsetup luksFormat -q --iter-time=1 -d ${keyfile} /dev/vdc")
# Boot from the encrypted disk
machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
machine.succeed("sync")
machine.crash()
# Boot and decrypt the disk
machine.wait_for_unit("multi-user.target")
assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
'';
})

View File

@ -0,0 +1,48 @@
import ./make-test-python.nix ({ lib, pkgs, ... }: {
name = "systemd-initrd-luks-password";
nodes.machine = { pkgs, ... }: {
# Use systemd-boot
virtualisation = {
emptyDiskImages = [ 512 512 ];
useBootLoader = true;
useEFIBoot = true;
};
boot.loader.systemd-boot.enable = true;
environment.systemPackages = with pkgs; [ cryptsetup ];
boot.initrd.systemd = {
enable = true;
emergencyAccess = true;
};
specialisation.boot-luks.configuration = {
boot.initrd.luks.devices = lib.mkVMOverride {
# We have two disks and only type one password - key reuse is in place
cryptroot.device = "/dev/vdc";
cryptroot2.device = "/dev/vdd";
};
virtualisation.bootDevice = "/dev/mapper/cryptroot";
};
};
testScript = ''
# Create encrypted volume
machine.wait_for_unit("multi-user.target")
machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdd -")
# Boot from the encrypted disk
machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
machine.succeed("sync")
machine.crash()
# Boot and decrypt the disk
machine.start()
machine.wait_for_console_text("Please enter passphrase for disk cryptroot")
machine.send_console("supersecret\n")
machine.wait_for_unit("multi-user.target")
assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
'';
})