diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 9108f0e6d2a2..aa8c7bdfcdbd 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -199,6 +199,7 @@ let while true; do echo -n "Passphrase for ${dev.device}: " passphrase= + ${lib.optionalString (dev.timeout != null) "time_passed=0"} while true; do if [ -e /crypt-ramfs/passphrase ]; then echo "reused" @@ -229,6 +230,13 @@ let echo break fi + ${lib.optionalString (dev.timeout != null) '' + time_passed=$((time_passed + 1)) + if [ $time_passed -ge ${builtins.toString dev.timeout} ]; then + echo "Timeout reached" + poweroff -f + fi + ''} fi done echo -n "Verifying passphrase for ${dev.device}..." @@ -328,6 +336,7 @@ let ${optionalString dev.yubikey.twoFactor '' echo -n "Enter two-factor passphrase: " k_user= + ${lib.optionalString (dev.timeout != null) "time_passed=0"} while true; do if [ -e /crypt-ramfs/passphrase ]; then echo "reused" @@ -351,6 +360,13 @@ let echo break fi + ${lib.optionalString (dev.timeout != null) '' + time_passed=$((time_passed + 1)) + if [ $time_passed -ge ${builtins.toString dev.timeout} ]; then + echo "Timeout reached" + poweroff -f + fi + ''} fi done ''} @@ -451,6 +467,7 @@ let for try in $(seq 3); do echo -n "PIN for GPG Card associated with device ${dev.device}: " pin= + ${lib.optionalString (dev.timeout != null) "time_passed=0"} while true; do if [ -e /crypt-ramfs/passphrase ]; then echo "reused" @@ -474,6 +491,13 @@ let echo break fi + ${lib.optionalString (dev.timeout != null) '' + time_passed=$((time_passed + 1)) + if [ $time_passed -ge ${builtins.toString dev.timeout} ]; then + echo "Timeout reached" + poweroff -f + fi + ''} fi done echo -n "Verifying passphrase for ${dev.device}..." @@ -523,8 +547,22 @@ let '' else '' - read -rsp "FIDO2 salt for ${dev.device}: " passphrase - echo + ${lib.optionalString (dev.timeout != null) "time_passed=0"} + echo -n "FIDO2 salt for ${dev.device}: " + while true; do + IFS= read -t 1 -rs passphrase + if [ -n "$passphrase" ]; then + echo + break + fi + ${lib.optionalString (dev.timeout != null) '' + time_passed=$((time_passed + 1)) + if [ $time_passed -ge ${builtins.toString dev.timeout} ]; then + echo "Timeout reached" + poweroff -f + fi + ''} + done '' } ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' @@ -562,27 +600,62 @@ let ${dev.postOpenCommands} ''; - askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' - #!/bin/sh + askPass = + let + configHasTimeouts = lib.any (dev: dev.timeout != null) (lib.attrValues luks.devices); + in + pkgs.writeScriptBin "cryptsetup-askpass" '' + #!/bin/sh - ${commonFunctions} + ${commonFunctions} - while true; do - wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" - device=$(cat /crypt-ramfs/device) + get_timeout_for_device() { + ${lib.pipe luks.devices [ + (lib.filterAttrs (name: dev: dev.timeout != luks.timeout)) + (lib.mapAttrsToList ( + name: dev: '' + if [ "$1" = "${lib.escapeShellArg dev.device}" ]; then + echo "${toString dev.timeout}" + return + fi + '' + )) + (lib.concatStringsSep "\n") + ]} + echo "${builtins.toString luks.timeout}" + } - echo -n "Passphrase for $device: " - IFS= read -rs passphrase - ret=$? - echo - if [ $ret -ne 0 ]; then - die "End of file reached. Exiting shell." - fi + while true; do + wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" + device=$(cat /crypt-ramfs/device) + ${lib.optionalString configHasTimeouts "time_passed=0"} + timeout=$(get_timeout_for_device $device) - rm /crypt-ramfs/device - echo -n "$passphrase" > /crypt-ramfs/passphrase - done - ''; + echo -n "Passphrase for $device: " + while true; do + IFS= read -t 1 -r passphrase + ret=$? + if [ $ret -eq 1 ]; then + echo + die "End of file reached. Exiting shell." + fi + if [ -n "$passphrase" ]; then + echo + break + fi + ${lib.optionalString configHasTimeouts '' + time_passed=$((time_passed + 1)) + if [ $timeout -gt 0 && $time_passed -ge $timeout ]; then + echo "Timeout reached" + poweroff -f + fi + ''} + done + + rm /crypt-ramfs/device + echo -n "$passphrase" > /crypt-ramfs/passphrase + done + ''; preLVM = filterAttrs (n: v: v.preLVM) luks.devices; postLVM = filterAttrs (n: v: !v.preLVM) luks.devices; @@ -1015,6 +1088,16 @@ in Extra options to append to the last column of the generated crypttab file. ''; }; + + timeout = mkOption { + type = types.nullOr types.ints.positive; + default = luks.timeout; + defaultText = "{option}`boot.initrd.luks.timeout`"; + description = '' + The amount of time in seconds to wait on the passphrase prompt. + If the timeout is reached, the system will power off. + ''; + }; }; config = mkIf (clevis.enable && (hasAttr name clevis.devices)) { @@ -1060,6 +1143,15 @@ in ''; }; + boot.initrd.luks.timeout = mkOption { + type = types.nullOr types.ints.positive; + default = null; + description = '' + The amount of time in seconds to wait on the passphrase prompt. + If the timeout is reached, the system will power off. + ''; + }; + }; config = mkIf (luks.devices != { } || luks.forceLuksSupportInInitrd) { @@ -1251,42 +1343,58 @@ in boot.initrd.systemd.services = let devicesWithClevis = filterAttrs (device: _: (hasAttr device clevis.devices)) luks.devices; + devicesWithTimeout = filterAttrs (_: dev: dev.timeout != null) 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 = optional clevis.useTang "network-online.target"; - after = [ - "systemd-modules-load.service" - "tpm2.target" - ] - ++ 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 - ); + mkMerge [ + (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 = optional clevis.useTang "network-online.target"; + after = [ + "systemd-modules-load.service" + "tpm2.target" + ] + ++ 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 + )) + + (mkIf systemd.enable ( + mapAttrs' ( + name: dev: + nameValuePair "systemd-cryptsetup@${utils.escapeSystemdPath name}" { + overrideStrategy = "asDropin"; + unitConfig = { + JobTimeoutSec = dev.timeout; + JobTimeoutAction = "poweroff"; + }; + } + ) devicesWithTimeout + )) + ]; environment.systemPackages = [ pkgs.cryptsetup ]; };