clevis: add boot.initrd.clevisLuksAskpass module

Enable upstream clevis-luks-askpass systemd units in initrd for
automatic LUKS unlocking using clevis bindings stored in LUKS headers.
This commit is contained in:
Kevin Cotrone
2026-02-16 22:31:38 -08:00
parent 556e0e6533
commit b5cb2cc43f
5 changed files with 189 additions and 4 deletions
@@ -91,6 +91,8 @@
- [nohang](https://github.com/hakavlad/nohang), a daemon for Linux that prevents out of memory (OOM) situations from affecting system responsiveness. Available as [services.nohang](#opt-services.nohang.enable)
- [clevis-luks-askpass](https://github.com/latchset/clevis), automatic LUKS unlocking in initrd using clevis token bindings stored in LUKS headers. Available as [boot.initrd.clevisLuksAskpass](#opt-boot.initrd.clevisLuksAskpass.enable).
- [bentopdf](https://github.com/alam00000/bentopdf), a privacy-first PDF toolkit running completely in-browser. Available as [services.bentopdf](#opt-services.bentopdf.enable).
- [hyprwhspr-rs](https://github.com/better-slop/hyprwhspr-rs), a keybind activated speech-to-text voice dictation utility built for use with Hyprland. Available as `services.hyprwhspr-rs`
+1
View File
@@ -1892,6 +1892,7 @@
./system/activation/switchable-system.nix
./system/activation/top-level.nix
./system/boot/binfmt.nix
./system/boot/clevis-luks-askpass.nix
./system/boot/clevis.nix
./system/boot/emergency-mode.nix
./system/boot/grow-partition.nix
@@ -0,0 +1,84 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.boot.initrd.clevisLuksAskpass;
in
{
options = {
boot.initrd.clevisLuksAskpass.enable = lib.mkEnableOption ''
clevis-luks-askpass in initrd.
Watches for systemd password requests during boot and answers them
using clevis tokens bound to LUKS headers. Runs in parallel with
the interactive password prompt. If clevis cannot unlock a device
(tang unreachable, no binding, etc.) the user can still type the
passphrase.
Prerequisites:
- Bind clevis to each LUKS device:
clevis luks bind -d /dev/xxx tang '{"url":"..."}'
- Configure networking in the initrd so tang servers are reachable
'';
boot.initrd.clevisLuksAskpass.package = lib.mkPackageOption pkgs "clevis" { };
boot.initrd.clevisLuksAskpass.useTang = lib.mkOption {
description = "Whether the Clevis headers used to decrypt the devices uses a Tang server as a pin.";
default = false;
type = lib.types.bool;
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = config.boot.initrd.systemd.enable;
message = "clevis-luks-askpass requires boot.initrd.systemd.enable = true";
}
];
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.systemd = {
# Install upstream clevis-luks-askpass.path and clevis-luks-askpass.service into the initrd
packages = [ cfg.package ];
storePaths = [
cfg.package
"${pkgs.systemd}/lib/systemd/systemd-reply-password"
"${pkgs.jose}/bin/jose"
"${pkgs.curl}/bin/curl"
"${pkgs.cryptsetup}/bin/cryptsetup"
"${pkgs.gnused}/bin/sed"
"${pkgs.gnugrep}/bin/grep"
"${pkgs.gawk}/bin/gawk"
"${pkgs.coreutils}/bin/cat"
"${pkgs.luksmeta}/bin/luksmeta"
"${pkgs.tpm2-tools}/bin/tpm2_createprimary"
"${pkgs.tpm2-tools}/bin/tpm2_flushcontext"
"${pkgs.tpm2-tools}/bin/tpm2_load"
"${pkgs.tpm2-tools}/bin/tpm2_unseal"
];
# This is in the [Install] section of clevis-luks-askpass.path but that's not processed in nixos so we add it here
paths.clevis-luks-askpass = {
wantedBy = [ "cryptsetup.target" ];
};
services.clevis-luks-askpass = {
wants = lib.optional cfg.useTang "network-online.target";
after = lib.optional cfg.useTang "network-online.target";
};
};
};
}
+68 -4
View File
@@ -21,6 +21,7 @@ let
forceGrubReinstallCount ? 0,
withTestInstrumentation ? true,
clevisTest,
clevisAskpassTest ? false,
}:
pkgs.writeText "configuration.nix" ''
{ config, lib, pkgs, modulesPath, ... }:
@@ -73,7 +74,12 @@ let
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; };
${
if clevisAskpassTest then
"clevisLuksAskpass = { enable = true; useTang = true; };"
else
"clevis = { enable = true; useTang = true; };"
}
network.enable = true;
};
''}
@@ -108,6 +114,7 @@ let
testFlakeSwitch,
testByAttrSwitch,
clevisTest,
clevisAskpassTest ? false,
clevisFallbackTest,
disableFileSystems,
}:
@@ -190,6 +197,7 @@ let
grubUseEfi
extraConfig
clevisTest
clevisAskpassTest
;
}
}",
@@ -197,11 +205,20 @@ let
)
installer.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret")
${optionalString clevisTest ''
with subtest("Create the Clevis secret with Tang"):
${optionalString (clevisTest && !clevisAskpassTest)
''
with subtest("Create the Clevis secret with Tang"):
installer.systemctl("start network-online.target")
installer.wait_for_unit("network-online.target")
installer.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 clevisAskpassTest ''
with subtest("Bind Clevis to LUKS header"):
installer.systemctl("start network-online.target")
installer.wait_for_unit("network-online.target")
installer.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')''}
installer.succeed("echo -n password | clevis luks bind -y -k - -d /dev/vda3 sss '{\"t\": 2, \"pins\": {\"tpm2\": {}, \"tang\": {\"url\": \"http://192.168.1.2\"}}}'")
''}
${optionalString clevisFallbackTest ''
with subtest("Shutdown Tang to check fallback to interactive prompt"):
@@ -283,6 +300,7 @@ let
grubUseEfi
extraConfig
clevisTest
clevisAskpassTest
;
forceGrubReinstallCount = 1;
}
@@ -316,6 +334,7 @@ let
grubUseEfi
extraConfig
clevisTest
clevisAskpassTest
;
forceGrubReinstallCount = 2;
}
@@ -384,6 +403,7 @@ let
grubUseEfi
extraConfig
clevisTest
clevisAskpassTest
;
forceGrubReinstallCount = 1;
withTestInstrumentation = false;
@@ -482,6 +502,7 @@ let
grubUseEfi
extraConfig
clevisTest
clevisAskpassTest
;
forceGrubReinstallCount = 1;
}
@@ -518,6 +539,7 @@ let
grubUseEfi
extraConfig
clevisTest
clevisAskpassTest
;
forceGrubReinstallCount = 1;
withTestInstrumentation = false;
@@ -637,6 +659,7 @@ let
testFlakeSwitch ? false,
testByAttrSwitch ? false,
clevisTest ? false,
clevisAskpassTest ? false,
clevisFallbackTest ? false,
disableFileSystems ? false,
selectNixPackage ? pkgs: pkgs.nixVersions.stable,
@@ -820,6 +843,7 @@ let
testFlakeSwitch
testByAttrSwitch
clevisTest
clevisAskpassTest
clevisFallbackTest
disableFileSystems
;
@@ -1044,6 +1068,43 @@ let
'';
};
mkClevisLuksAskpassTest =
{
fallback ? false,
}:
makeInstallerTest "clevis-luks-askpass${optionalString fallback "-fallback"}" {
clevisTest = true;
clevisAskpassTest = true;
clevisFallbackTest = fallback;
enableOCR = fallback;
extraInstallerConfig = {
environment.systemPackages = with pkgs; [ clevis ];
};
createPartitions = ''
installer.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")
'';
postBootCommands = optionalString fallback ''
target.wait_for_text("Please enter")
target.send_chars("password\n")
'';
};
mkClevisZfsTest =
{
fallback ? false,
@@ -1741,6 +1802,9 @@ in
};
}
// optionalAttrs systemdStage1 {
clevisLuksAskpass = mkClevisLuksAskpassTest { };
clevisLuksAskpassFallback = mkClevisLuksAskpassTest { fallback = true; };
stratisRoot = makeInstallerTest "stratisRoot" {
createPartitions = ''
installer.succeed(
+34
View File
@@ -5,6 +5,7 @@
cryptsetup,
curl,
fetchFromGitHub,
gawk,
gnugrep,
gnused,
jansson,
@@ -17,6 +18,7 @@
nixosTests,
pkg-config,
stdenv,
systemd,
tpm2-tools,
}:
@@ -52,6 +54,7 @@ stdenv.mkDerivation (finalAttrs: {
jose
libpwquality
luksmeta
systemd
tpm2-tools
];
@@ -74,11 +77,26 @@ stdenv.mkDerivation (finalAttrs: {
# The xargs command is a little bit convoluted because a simpler version would
# be vulnerable to code injection. This hint is a courtesy of Stack Exchange:
# https://unix.stackexchange.com/a/267438
#
# Meson's find_program only searches paths relative to the build prefix
# and /usr/lib, neither of which contain systemd-reply-password in the
# nix store. Prepend the actual nix store path so meson can find it.
#
# The systemd pkgconfig variable 'systemdsystemunitdir' points into the
# read-only systemd store path. Redirect unit installation to $out.
postPatch = ''
for f in $(find src/ -type f -print0 |\
xargs -0 -I@ sh -c 'grep -q "/bin/cat" "$1" && echo "$1"' sh @); do
substituteInPlace "$f" --replace-fail '/bin/cat' '${lib.getExe' coreutils "cat"}'
done
substituteInPlace src/luks/systemd/meson.build \
--replace-fail "sd_reply_pass = find_program(" \
"sd_reply_pass = find_program('${systemd}/lib/systemd/systemd-reply-password',"
substituteInPlace src/luks/systemd/meson.build \
--replace-fail "unitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir')" \
"unitdir = join_paths(get_option('prefix'), 'lib', 'systemd', 'system')"
'';
# We wrap the main clevis binary entrypoint but not the sub-binaries.
@@ -94,10 +112,23 @@ stdenv.mkDerivation (finalAttrs: {
luksmeta
tpm2-tools
];
askpassPath = [
coreutils
cryptsetup
curl
gawk
gnugrep
gnused
jose
luksmeta
];
in
''
wrapProgram $out/bin/clevis \
--prefix PATH ':' "${lib.makeBinPath includeIntoPath}:${placeholder "out"}/bin"
wrapProgram $out/libexec/clevis-luks-askpass \
--prefix PATH ':' "${lib.makeBinPath askpassPath}:${placeholder "out"}/bin"
'';
passthru.tests = {
@@ -111,6 +142,9 @@ stdenv.mkDerivation (finalAttrs: {
;
clevisLuksSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisLuks;
clevisLuksFallbackSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisLuksFallback;
clevisLuksAskpassSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisLuksAskpass;
clevisLuksAskpassFallbackSystemdStage1 =
nixosTests.installer-systemd-stage-1.clevisLuksAskpassFallback;
clevisZfsSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisZfs;
clevisZfsFallbackSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisZfsFallback;
};