From 36f887ca90ea0fdde13d02c4abd9b5153e2b7358 Mon Sep 17 00:00:00 2001 From: Wroclaw Date: Thu, 28 Nov 2024 16:29:33 +0100 Subject: [PATCH 1/3] nixos/luksroot: add timeout option for devices This option allows for setting timeout on password prompt, and upon reaching timeout, it will poweroff the system. --- nixos/modules/system/boot/luksroot.nix | 116 +++++++++++++++++++++---- 1 file changed, 97 insertions(+), 19 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 3769c46f64de..bda6fde8c413 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,58 @@ 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.concatStringsSep "\n" ( + lib.mapAttrsToList (name: dev: '' + if [ "$1" = "${lib.escapeShellArg dev.device}" ]; then + echo "${toString dev.timeout}" + return + fi + '') luks.devices + )} + echo "0" + } - 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; @@ -1004,6 +1073,15 @@ in Extra options to append to the last column of the generated crypttab file. ''; }; + + 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 (clevis.enable && (hasAttr name clevis.devices)) { From 50f1f692e47027d1e3668d4c538aa594a102a499 Mon Sep 17 00:00:00 2001 From: Wroclaw Date: Fri, 29 Nov 2024 22:59:48 +0100 Subject: [PATCH 2/3] nixos/luksroot: add global timeout option this option defines default for each device timeout --- nixos/modules/system/boot/luksroot.nix | 34 ++++++++++++++++++-------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index bda6fde8c413..1578947acb73 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -610,15 +610,19 @@ let ${commonFunctions} get_timeout_for_device() { - ${lib.concatStringsSep "\n" ( - lib.mapAttrsToList (name: dev: '' - if [ "$1" = "${lib.escapeShellArg dev.device}" ]; then - echo "${toString dev.timeout}" - return - fi - '') luks.devices - )} - echo "0" + ${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}" } while true; do @@ -1076,7 +1080,8 @@ in timeout = mkOption { type = types.nullOr types.ints.positive; - default = null; + 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. @@ -1127,6 +1132,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) { From a8413bf805265b2214956f631b451929874fc6bc Mon Sep 17 00:00:00 2001 From: Wroclaw Date: Sun, 15 Feb 2026 12:33:27 +0100 Subject: [PATCH 3/3] nixos/luksroot: add timeout support for systemd-initrd --- nixos/modules/system/boot/luksroot.nix | 86 +++++++++++++++----------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 1578947acb73..8afa2816fb50 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -1337,42 +1337,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 ]; };