nixos/luksroot: add timeout option for devices (#359882)

This commit is contained in:
Artemis Tosini
2026-09-26 11:44:33 +00:00
committed by GitHub

View File

@@ -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 ];
};