diff options
author | Ryan Lahfa <masterancpp@gmail.com> | 2023-12-02 12:58:01 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-02 12:58:01 +0100 |
commit | 8626b5c06d264b4d034cb160578b56ea0e09575f (patch) | |
tree | aef768ebfd0d3f9c4702b1ca3aa177becf2947b6 /nixos | |
parent | 598129ea00d7144b5c36a1312454958e1949f224 (diff) | |
parent | 3aa4ed098504c16c7a6fc801dc9612253b4f72c6 (diff) | |
download | nixlib-8626b5c06d264b4d034cb160578b56ea0e09575f.tar nixlib-8626b5c06d264b4d034cb160578b56ea0e09575f.tar.gz nixlib-8626b5c06d264b4d034cb160578b56ea0e09575f.tar.bz2 nixlib-8626b5c06d264b4d034cb160578b56ea0e09575f.tar.lz nixlib-8626b5c06d264b4d034cb160578b56ea0e09575f.tar.xz nixlib-8626b5c06d264b4d034cb160578b56ea0e09575f.tar.zst nixlib-8626b5c06d264b4d034cb160578b56ea0e09575f.zip |
Merge pull request #257525 from SaumonNet/clevis
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2405.section.md | 2 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/system/boot/clevis.md | 51 | ||||
-rw-r--r-- | nixos/modules/system/boot/clevis.nix | 107 | ||||
-rw-r--r-- | nixos/modules/system/boot/luksroot.nix | 48 | ||||
-rw-r--r-- | nixos/modules/tasks/filesystems/bcachefs.nix | 10 | ||||
-rw-r--r-- | nixos/modules/tasks/filesystems/zfs.nix | 13 | ||||
-rw-r--r-- | nixos/tests/installer-systemd-stage-1.nix | 4 | ||||
-rw-r--r-- | nixos/tests/installer.nix | 248 |
9 files changed, 464 insertions, 20 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index ba4d26518db8..729b52e09a16 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -18,6 +18,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable). +- [Clevis](https://github.com/latchset/clevis), a pluggable framework for automated decryption, used to unlock encrypted devices in initrd. Available as [boot.initrd.clevis.enable](#opt-boot.initrd.clevis.enable). + ## Backward Incompatibilities {#sec-release-24.05-incompatibilities} <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 67ba5d18433a..b505a294c8b9 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1424,6 +1424,7 @@ ./system/activation/bootspec.nix ./system/activation/top-level.nix ./system/boot/binfmt.nix + ./system/boot/clevis.nix ./system/boot/emergency-mode.nix ./system/boot/grow-partition.nix ./system/boot/initrd-network.nix diff --git a/nixos/modules/system/boot/clevis.md b/nixos/modules/system/boot/clevis.md new file mode 100644 index 000000000000..91eb728a919e --- /dev/null +++ b/nixos/modules/system/boot/clevis.md @@ -0,0 +1,51 @@ +# Clevis {#module-boot-clevis} + +[Clevis](https://github.com/latchset/clevis) +is a framework for automated decryption of resources. +Clevis allows for secure unattended disk decryption during boot, using decryption policies that must be satisfied for the data to decrypt. + + +## Create a JWE file containing your secret {#module-boot-clevis-create-secret} + +The first step is to embed your secret in a [JWE](https://en.wikipedia.org/wiki/JSON_Web_Encryption) file. +JWE files have to be created through the clevis command line. 3 types of policies are supported: + +1) TPM policies + +Secrets are pinned against the presence of a TPM2 device, for example: +``` +echo hi | clevis encrypt tpm2 '{}' > hi.jwe +``` +2) Tang policies + +Secrets are pinned against the presence of a Tang server, for example: +``` +echo hi | clevis encrypt tang '{"url": "http://tang.local"}' > hi.jwe +``` + +3) Shamir Secret Sharing + +Using Shamir's Secret Sharing ([sss](https://en.wikipedia.org/wiki/Shamir%27s_secret_sharing)), secrets are pinned using a combination of the two preceding policies. For example: +``` +echo hi | clevis encrypt sss \ +'{"t": 2, "pins": {"tpm2": {"pcr_ids": "0"}, "tang": {"url": "http://tang.local"}}}' \ +> hi.jwe +``` + +For more complete documentation on how to generate a secret with clevis, see the [clevis documentation](https://github.com/latchset/clevis). + + +## Activate unattended decryption of a resource at boot {#module-boot-clevis-activate} + +In order to activate unattended decryption of a resource at boot, enable the `clevis` module: + +``` +boot.initrd.clevis.enable = true; +``` + +Then, specify the device you want to decrypt using a given clevis secret. Clevis will automatically try to decrypt the device at boot and will fallback to interactive unlocking if the decryption policy is not fulfilled. +``` +boot.initrd.clevis.devices."/dev/nvme0n1p1".secretFile = ./nvme0n1p1.jwe; +``` + +Only `bcachefs`, `zfs` and `luks` encrypted devices are supported at this time. diff --git a/nixos/modules/system/boot/clevis.nix b/nixos/modules/system/boot/clevis.nix new file mode 100644 index 000000000000..0c72590f9385 --- /dev/null +++ b/nixos/modules/system/boot/clevis.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.boot.initrd.clevis; + systemd = config.boot.initrd.systemd; + supportedFs = [ "zfs" "bcachefs" ]; +in +{ + meta.maintainers = with maintainers; [ julienmalka camillemndn ]; + meta.doc = ./clevis.md; + + options = { + boot.initrd.clevis.enable = mkEnableOption (lib.mdDoc "Clevis in initrd"); + + + boot.initrd.clevis.package = mkOption { + type = types.package; + default = pkgs.clevis; + defaultText = "pkgs.clevis"; + description = lib.mdDoc "Clevis package"; + }; + + boot.initrd.clevis.devices = mkOption { + description = "Encrypted devices that need to be unlocked at boot using Clevis"; + default = { }; + type = types.attrsOf (types.submodule ({ + options.secretFile = mkOption { + description = lib.mdDoc "Clevis JWE file used to decrypt the device at boot, in concert with the chosen pin (one of TPM2, Tang server, or SSS)."; + type = types.path; + }; + })); + }; + + boot.initrd.clevis.useTang = mkOption { + description = "Whether the Clevis JWE file used to decrypt the devices uses a Tang server as a pin."; + default = false; + type = types.bool; + }; + + }; + + config = mkIf cfg.enable { + + # Implementation of clevis unlocking for the supported filesystems are located directly in the respective modules. + + + assertions = (attrValues (mapAttrs + (device: _: { + assertion = (any (fs: fs.device == device && (elem fs.fsType supportedFs)) config.system.build.fileSystems) || (hasAttr device config.boot.initrd.luks.devices); + message = '' + No filesystem or LUKS device with the name ${device} is declared in your configuration.''; + }) + cfg.devices)); + + + warnings = + if cfg.useTang && !config.boot.initrd.network.enable && !config.boot.initrd.systemd.network.enable + then [ "In order to use a Tang pinned secret you must configure networking in initrd" ] + else [ ]; + + boot.initrd = { + extraUtilsCommands = mkIf (!systemd.enable) '' + copy_bin_and_libs ${pkgs.jose}/bin/jose + copy_bin_and_libs ${pkgs.curl}/bin/curl + copy_bin_and_libs ${pkgs.bash}/bin/bash + + copy_bin_and_libs ${pkgs.tpm2-tools}/bin/.tpm2-wrapped + mv $out/bin/{.tpm2-wrapped,tpm2} + cp {${pkgs.tpm2-tss},$out}/lib/libtss2-tcti-device.so.0 + + copy_bin_and_libs ${cfg.package}/bin/.clevis-wrapped + mv $out/bin/{.clevis-wrapped,clevis} + + for BIN in ${cfg.package}/bin/clevis-decrypt*; do + copy_bin_and_libs $BIN + done + + for BIN in $out/bin/clevis{,-decrypt{,-null,-tang,-tpm2}}; do + sed -i $BIN -e 's,${pkgs.bash},,' -e 's,${pkgs.coreutils},,' + done + + sed -i $out/bin/clevis-decrypt-tpm2 -e 's,tpm2_,tpm2 ,' + ''; + + secrets = lib.mapAttrs' (name: value: nameValuePair "/etc/clevis/${name}.jwe" value.secretFile) cfg.devices; + + systemd = { + extraBin = mkIf systemd.enable { + clevis = "${cfg.package}/bin/clevis"; + curl = "${pkgs.curl}/bin/curl"; + }; + + storePaths = mkIf systemd.enable [ + cfg.package + "${pkgs.jose}/bin/jose" + "${pkgs.curl}/bin/curl" + "${pkgs.tpm2-tools}/bin/tpm2_createprimary" + "${pkgs.tpm2-tools}/bin/tpm2_flushcontext" + "${pkgs.tpm2-tools}/bin/tpm2_load" + "${pkgs.tpm2-tools}/bin/tpm2_unseal" + ]; + }; + }; + }; +} diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index ca560d63f3bd..8bd9e71cb3a9 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -1,9 +1,11 @@ -{ config, options, lib, pkgs, ... }: +{ config, options, lib, utils, pkgs, ... }: with lib; let luks = config.boot.initrd.luks; + clevis = config.boot.initrd.clevis; + systemd = config.boot.initrd.systemd; kernelPackages = config.boot.kernelPackages; defaultPrio = (mkOptionDefault {}).priority; @@ -594,7 +596,7 @@ in ''; type = with types; attrsOf (submodule ( - { name, ... }: { options = { + { config, name, ... }: { options = { name = mkOption { visible = false; @@ -894,6 +896,19 @@ in ''; }; }; + + config = mkIf (clevis.enable && (hasAttr name clevis.devices)) { + preOpenCommands = mkIf (!systemd.enable) '' + mkdir -p /clevis-${name} + mount -t ramfs none /clevis-${name} + clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted + ''; + keyFile = "/clevis-${name}/decrypted"; + fallbackToPassword = !systemd.enable; + postOpenCommands = mkIf (!systemd.enable) '' + umount /clevis-${name} + ''; + }; })); }; @@ -1081,6 +1096,35 @@ in 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); + boot.initrd.systemd.services = let devicesWithClevis = filterAttrs (device: _: (hasAttr device clevis.devices)) luks.devices; in + mkIf (clevis.enable && systemd.enable) ( + (mapAttrs' + (name: _: nameValuePair "cryptsetup-clevis-${name}" { + wantedBy = [ "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" ]; + before = [ + "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" + "initrd-switch-root.target" + "shutdown.target" + ]; + wants = [ "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; + after = [ "systemd-modules-load.service" "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; + script = '' + mkdir -p /clevis-${name} + mount -t ramfs none /clevis-${name} + umask 277 + clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted + ''; + conflicts = [ "initrd-switch-root.target" "shutdown.target" ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "${config.boot.initrd.systemd.package.util-linux}/bin/umount /clevis-${name}"; + }; + }) + devicesWithClevis) + ); + environment.systemPackages = [ pkgs.cryptsetup ]; }; } diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix index f28fd5cde9c1..639ff87841b6 100644 --- a/nixos/modules/tasks/filesystems/bcachefs.nix +++ b/nixos/modules/tasks/filesystems/bcachefs.nix @@ -57,7 +57,15 @@ let # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671) firstDevice = fs: lib.head (lib.splitString ":" fs.device); - openCommand = name: fs: '' + openCommand = name: fs: if config.boot.initrd.clevis.enable && (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices) then '' + if clevis decrypt < /etc/clevis/${firstDevice fs}.jwe | bcachefs unlock ${firstDevice fs} + then + printf "unlocked ${name} using clevis\n" + else + printf "falling back to interactive unlocking...\n" + tryUnlock ${name} ${firstDevice fs} + fi + '' else '' tryUnlock ${name} ${firstDevice fs} ''; diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 72bc79f31b68..fd92a0014002 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -17,6 +17,9 @@ let cfgZED = config.services.zfs.zed; selectModulePackage = package: config.boot.kernelPackages.${package.kernelModuleAttribute}; + clevisDatasets = map (e: e.device) (filter (e: (hasAttr e.device config.boot.initrd.clevis.devices) && e.fsType == "zfs" && (fsNeededForBoot e)) config.system.build.fileSystems); + + inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems; inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems; @@ -120,12 +123,12 @@ let # but don't *require* it, because mounts shouldn't be killed if it's stopped. # In the future, hopefully someone will complete this: # https://github.com/zfsonlinux/zfs/pull/4943 - wants = [ "systemd-udev-settle.service" ]; + wants = [ "systemd-udev-settle.service" ] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target"; after = [ "systemd-udev-settle.service" "systemd-modules-load.service" "systemd-ask-password-console.service" - ]; + ] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target"; requiredBy = getPoolMounts prefix pool ++ [ "zfs-import.target" ]; before = getPoolMounts prefix pool ++ [ "zfs-import.target" ]; unitConfig = { @@ -154,6 +157,9 @@ let poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool. fi if poolImported "${pool}"; then + ${concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem} || true ") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets)} + + ${optionalString keyLocations.hasKeys '' ${keyLocations.command} | while IFS=$'\t' read ds kl ks; do { @@ -623,6 +629,9 @@ in fi poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool. fi + + ${concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem}") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets)} + ${if isBool cfgZfs.requestEncryptionCredentials then optionalString cfgZfs.requestEncryptionCredentials '' zfs load-key -a diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix index 1b4c92b584b9..d0c01a779ef1 100644 --- a/nixos/tests/installer-systemd-stage-1.nix +++ b/nixos/tests/installer-systemd-stage-1.nix @@ -32,6 +32,10 @@ stratisRoot swraid zfsroot + clevisLuks + clevisLuksFallback + clevisZfs + clevisZfsFallback ; } diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index e9ec28749850..f7fc168eba8c 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -12,6 +12,7 @@ let # The configuration to install. makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi , extraConfig, forceGrubReinstallCount ? 0, flake ? false + , clevisTest }: pkgs.writeText "configuration.nix" '' { config, lib, pkgs, modulesPath, ... }: @@ -52,6 +53,15 @@ let boot.initrd.secrets."/etc/secret" = ./secret; + ${optionalString clevisTest '' + boot.kernelParams = [ "console=tty0" "ip=192.168.1.1:::255.255.255.0::eth1:none" ]; + boot.initrd = { + availableKernelModules = [ "tpm_tis" ]; + clevis = { enable = true; useTang = true; }; + network.enable = true; + }; + ''} + users.users.alice = { isNormalUser = true; home = "/home/alice"; @@ -71,7 +81,7 @@ let # partitions and filesystems. testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi, grubIdentifier , postInstallCommands, preBootCommands, postBootCommands, extraConfig - , testSpecialisationConfig, testFlakeSwitch + , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest }: let iface = "virtio"; isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); @@ -79,12 +89,16 @@ let in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then '' machine.succeed("true") '' else '' + import subprocess + tpm_folder = os.environ['NIX_BUILD_TOP'] def assemble_qemu_flags(): flags = "-cpu max" ${if (system == "x86_64-linux" || system == "i686-linux") then ''flags += " -m 1024"'' else ''flags += " -m 768 -enable-kvm -machine virt,gic-version=host"'' } + ${optionalString clevisTest ''flags += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"''} + ${optionalString clevisTest ''flags += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""''} return flags @@ -110,8 +124,45 @@ let def create_machine_named(name): return create_machine({**default_flags, "name": name}) + class Tpm: + def __init__(self): + self.start() + + def start(self): + self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm", + "socket", + "--tpmstate", f"dir={tpm_folder}/swtpm", + "--ctrl", f"type=unixio,path={tpm_folder}/swtpm-sock", + "--tpm2" + ]) + + # Check whether starting swtpm failed + try: + exit_code = self.proc.wait(timeout=0.2) + if exit_code is not None and exit_code != 0: + raise Exception("failed to start swtpm") + except subprocess.TimeoutExpired: + pass + + """Check whether the swtpm process exited due to an error""" + def check(self): + exit_code = self.proc.poll() + if exit_code is not None and exit_code != 0: + raise Exception("swtpm process died") + + + os.mkdir(f"{tpm_folder}/swtpm") + tpm = Tpm() + tpm.check() + + start_all() + ${optionalString clevisTest '' + tang.wait_for_unit("sockets.target") + tang.wait_for_unit("network-online.target") + machine.wait_for_unit("network-online.target") + ''} + machine.wait_for_unit("multi-user.target") - machine.start() with subtest("Assert readiness of login prompt"): machine.succeed("echo hello") @@ -127,13 +178,23 @@ let machine.copy_from_host( "${ makeConfig { inherit bootLoader grubDevice grubIdentifier - grubUseEfi extraConfig; + grubUseEfi extraConfig clevisTest; } }", "/mnt/etc/nixos/configuration.nix", ) machine.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret") + ${optionalString clevisTest '' + with subtest("Create the Clevis secret with Tang"): + machine.wait_for_unit("network-online.target") + machine.succeed('echo -n password | clevis encrypt sss \'{"t": 2, "pins": {"tpm2": {}, "tang": {"url": "http://192.168.1.2"}}}\' -y > /mnt/etc/nixos/clevis-secret.jwe')''} + + ${optionalString clevisFallbackTest '' + with subtest("Shutdown Tang to check fallback to interactive prompt"): + tang.shutdown() + ''} + with subtest("Perform the installation"): machine.succeed("nixos-install < /dev/null >&2") @@ -200,7 +261,7 @@ let machine.copy_from_host_via_shell( "${ makeConfig { inherit bootLoader grubDevice grubIdentifier - grubUseEfi extraConfig; + grubUseEfi extraConfig clevisTest; forceGrubReinstallCount = 1; } }", @@ -229,7 +290,7 @@ let machine.copy_from_host_via_shell( "${ makeConfig { inherit bootLoader grubDevice grubIdentifier - grubUseEfi extraConfig; + grubUseEfi extraConfig clevisTest; forceGrubReinstallCount = 2; } }", @@ -303,7 +364,7 @@ let """) machine.copy_from_host_via_shell( "${makeConfig { - inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig; + inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest; forceGrubReinstallCount = 1; flake = true; }}", @@ -379,6 +440,8 @@ let , enableOCR ? false, meta ? {} , testSpecialisationConfig ? false , testFlakeSwitch ? false + , clevisTest ? false + , clevisFallbackTest ? false }: makeTest { inherit enableOCR; @@ -416,13 +479,13 @@ let virtualisation.rootDevice = "/dev/vdb"; virtualisation.bootLoaderDevice = "/dev/vda"; virtualisation.qemu.diskInterface = "virtio"; - - # We don't want to have any networking in the guest whatsoever. - # Also, if any vlans are enabled, the guest will reboot - # (with a different configuration for legacy reasons), - # and spend 5 minutes waiting for the vlan interface to show up - # (which will never happen). - virtualisation.vlans = []; + virtualisation.qemu.options = mkIf (clevisTest) [ + "-chardev socket,id=chrtpm,path=$NIX_BUILD_TOP/swtpm-sock" + "-tpmdev emulator,id=tpm0,chardev=chrtpm" + "-device tpm-tis,tpmdev=tpm0" + ]; + # We don't want to have any networking in the guest apart from the clevis tests. + virtualisation.vlans = mkIf (!clevisTest) []; boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true; @@ -471,7 +534,7 @@ let in [ (pkgs.grub2.override { inherit zfsSupport; }) (pkgs.grub2_efi.override { inherit zfsSupport; }) - ]); + ]) ++ optionals clevisTest [ pkgs.klibc ]; nix.settings = { substituters = mkForce []; @@ -480,12 +543,21 @@ let }; }; + } // optionalAttrs clevisTest { + tang = { + services.tang = { + enable = true; + listenStream = [ "80" ]; + ipAddressAllow = [ "192.168.1.0/24" ]; + }; + networking.firewall.allowedTCPPorts = [ 80 ]; + }; }; testScript = testScriptFun { inherit bootLoader createPartitions postInstallCommands preBootCommands postBootCommands grubDevice grubIdentifier grubUseEfi extraConfig - testSpecialisationConfig testFlakeSwitch; + testSpecialisationConfig testFlakeSwitch clevisTest clevisFallbackTest; }; }; @@ -586,6 +658,145 @@ let zfs = super.zfs.overrideAttrs(_: {meta.platforms = [];});} )]; }; + + mkClevisBcachefsTest = { fallback ? false }: makeInstallerTest "clevis-bcachefs${optionalString fallback "-fallback"}" { + clevisTest = true; + clevisFallbackTest = fallback; + enableOCR = fallback; + extraInstallerConfig = { + imports = [ no-zfs-module ]; + boot.supportedFilesystems = [ "bcachefs" ]; + environment.systemPackages = with pkgs; [ keyutils clevis ]; + }; + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary ext2 1M 100MB" + + " mkpart primary linux-swap 100M 1024M" + + " mkpart primary 1024M -1s", + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "keyctl link @u @s", + "echo -n password | mkfs.bcachefs -L root --encrypted /dev/vda3", + "echo -n password | bcachefs unlock /dev/vda3", + "echo -n password | mount -t bcachefs /dev/vda3 /mnt", + "mkfs.ext3 -L boot /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=boot /mnt/boot", + "udevadm settle") + ''; + extraConfig = '' + boot.initrd.clevis.devices."/dev/vda3".secretFile = "/etc/nixos/clevis-secret.jwe"; + + # We override what nixos-generate-config has generated because we do + # not know the UUID in advance. + fileSystems."/" = lib.mkForce { device = "/dev/vda3"; fsType = "bcachefs"; }; + ''; + preBootCommands = '' + tpm = Tpm() + tpm.check() + '' + optionalString fallback '' + machine.start() + machine.wait_for_text("enter passphrase for") + machine.send_chars("password\n") + ''; + }; + + mkClevisLuksTest = { fallback ? false }: makeInstallerTest "clevis-luks${optionalString fallback "-fallback"}" { + clevisTest = true; + clevisFallbackTest = fallback; + enableOCR = fallback; + extraInstallerConfig = { + environment.systemPackages = with pkgs; [ clevis ]; + }; + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary ext2 1M 100MB" + + " mkpart primary linux-swap 100M 1024M" + + " mkpart primary 1024M -1s", + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "modprobe dm_mod dm_crypt", + "echo -n password | cryptsetup luksFormat -q /dev/vda3 -", + "echo -n password | cryptsetup luksOpen --key-file - /dev/vda3 crypt-root", + "mkfs.ext3 -L nixos /dev/mapper/crypt-root", + "mount LABEL=nixos /mnt", + "mkfs.ext3 -L boot /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=boot /mnt/boot", + "udevadm settle") + ''; + extraConfig = '' + boot.initrd.clevis.devices."crypt-root".secretFile = "/etc/nixos/clevis-secret.jwe"; + ''; + preBootCommands = '' + tpm = Tpm() + tpm.check() + '' + optionalString fallback '' + machine.start() + ${if systemdStage1 then '' + machine.wait_for_text("Please enter") + '' else '' + machine.wait_for_text("Passphrase for") + ''} + machine.send_chars("password\n") + ''; + }; + + mkClevisZfsTest = { fallback ? false }: makeInstallerTest "clevis-zfs${optionalString fallback "-fallback"}" { + clevisTest = true; + clevisFallbackTest = fallback; + enableOCR = fallback; + extraInstallerConfig = { + boot.supportedFilesystems = [ "zfs" ]; + environment.systemPackages = with pkgs; [ clevis ]; + }; + createPartitions = '' + machine.succeed( + "flock /dev/vda parted --script /dev/vda -- mklabel msdos" + + " mkpart primary ext2 1M 100MB" + + " mkpart primary linux-swap 100M 1024M" + + " mkpart primary 1024M -1s", + "udevadm settle", + "mkswap /dev/vda2 -L swap", + "swapon -L swap", + "zpool create -O mountpoint=legacy rpool /dev/vda3", + "echo -n password | zfs create" + + " -o encryption=aes-256-gcm -o keyformat=passphrase rpool/root", + "mount -t zfs rpool/root /mnt", + "mkfs.ext3 -L boot /dev/vda1", + "mkdir -p /mnt/boot", + "mount LABEL=boot /mnt/boot", + "udevadm settle") + ''; + extraConfig = '' + boot.initrd.clevis.devices."rpool/root".secretFile = "/etc/nixos/clevis-secret.jwe"; + boot.zfs.requestEncryptionCredentials = true; + + + # Using by-uuid overrides the default of by-id, and is unique + # to the qemu disks, as they don't produce by-id paths for + # some reason. + boot.zfs.devNodes = "/dev/disk/by-uuid/"; + networking.hostId = "00000000"; + ''; + preBootCommands = '' + tpm = Tpm() + tpm.check() + '' + optionalString fallback '' + machine.start() + ${if systemdStage1 then '' + machine.wait_for_text("Enter key for rpool/root") + '' else '' + machine.wait_for_text("Key load error") + ''} + machine.send_chars("password\n") + ''; + }; + in { # !!! `parted mkpart' seems to silently create overlapping partitions. @@ -1175,6 +1386,13 @@ in { ) ''; }; +} // { + clevisBcachefs = mkClevisBcachefsTest { }; + clevisBcachefsFallback = mkClevisBcachefsTest { fallback = true; }; + clevisLuks = mkClevisLuksTest { }; + clevisLuksFallback = mkClevisLuksTest { fallback = true; }; + clevisZfs = mkClevisZfsTest { }; + clevisZfsFallback = mkClevisZfsTest { fallback = true; }; } // optionalAttrs systemdStage1 { stratisRoot = makeInstallerTest "stratisRoot" { createPartitions = '' |