Clevis LUKS Askpass in Initrd

Updated

October 31, 2026

Clevis LUKS Askpass in Initrd

NixOS 26.05 adds boot.initrd.clevisLuksAskpass: automatic LUKS unlock in the systemd Stage 1 initrd by answering systemd password requests with Clevis tokens already bound into the LUKS headers. The boring default for a systemd-stage1 desk is: bind Clevis (Tang and/or TPM2) on the LUKS device once, enable the module (and initrd networking if Tang), keep an interactive passphrase as fallback, and point fileSystems."/".device at /dev/mapper/<name> matching boot.initrd.luks.devices. Prefer this header-token path over the older boot.initrd.clevis.devices.*.secretFile JWE-file approach for LUKS on systemd stage1.

Clevis (latchset/clevis) is a pluggable automated-decryption framework. Pins such as Tang (network server) and TPM2 unseal a secret without typing a passphrase when the pin conditions hold. The 26.05 module ships upstream clevis-luks-askpass.path / .service into the initrd so those bindings can answer systemd-cryptsetup prompts in parallel with a human typing at the console.

Mental model

boot (systemd Stage 1 initrd)
        │
        ▼
 cryptsetup.target
   wantedBy: clevis-luks-askpass.path
        │
        ▼
 systemd password request for LUKS
   ┌────┴────┐
   │         │
   ▼         ▼
 clevis-luks-askpass.service     interactive passphrase prompt
   (reads Clevis token in        (always available if pin fails)
    LUKS header; Tang/TPM2…)
        │
        ▼
 /dev/mapper/<name> ready
        │
        ▼
 fileSystems."/".device = "/dev/mapper/<name>"
Piece Role
boot.initrd.clevisLuksAskpass.enable Install askpass path/service into systemd initrd
boot.initrd.clevisLuksAskpass.package Clevis package (default pkgs.clevis)
boot.initrd.clevisLuksAskpass.useTang Order askpass after/wants network-online.target
boot.initrd.systemd.enable Required (assertion); default true on 26.05
boot.initrd.network.enable / boot.initrd.systemd.network.enable Needed when Tang must be reached from initrd
clevis luks bind -d /dev/… tang '…' / tpm2 One-time bind: token lives in the LUKS header
Older boot.initrd.clevis + devices.*.secretFile JWE file shipped into initrd secrets — different path
fileSystems."/".device = "/dev/mapper/<name>" Avoid systemd passphrase/device timeout on 26.05 stage1
systemctl default Replaces gone cryptsetup-askpass in stage1 emergency

Two desks, one rule:

  1. Header tokens + askpass (26.05+ systemd stage1 LUKS): bind Clevis into the LUKS header; enable clevisLuksAskpass; networking only if Tang.
  2. JWE files (boot.initrd.clevis): still valid for some ZFS/bcachefs and legacy layouts, but you manage a secret file path per device — not the boring default for plain LUKS on systemd stage1.

Worked examples

Case 1: Bind Clevis to a LUKS volume (prerequisite, once)

Clevis must already be bound on the device before the module can unlock it. From a live system (or installer) with the LUKS partition unlocked or with passphrase access:

# Identify the encrypted partition (lab example — use your real by-uuid / by-id)
lsblk -f
# Bind a Tang pin (server must be reachable from this host for the bind)
sudo clevis luks bind -d /dev/disk/by-uuid/AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE \
  tang '{"url":"http://tang.lab.example:80"}'
# Optional second pin: TPM2 on the same slot set (machine-local)
sudo clevis luks bind -d /dev/disk/by-uuid/AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE tpm2 '{}'
# Inspect bindings
sudo clevis luks list -d /dev/disk/by-uuid/AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE

Representative listing:

1: tang '{"url":"http://tang.lab.example:80"}'
2: tpm2 '{}'

The token lives in the LUKS header (via LUKSMeta / Clevis metadata). There is no JWE file to copy into the Nix store for clevisLuksAskpass.

Case 2: Enable askpass on a Tang-backed desk

Save as desk_clevis_tang.nix and import from the host flake:

# desk_clevis_tang.nix
{ lib, ... }:

{
  # 26.05 default is true; keep it explicit on encrypted desks.
  boot.initrd.systemd.enable = true;

  boot.initrd.luks.devices.cryptroot = {
    device = "/dev/disk/by-uuid/AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
  };

  # Mapper name must match the LUKS device attr name above.
  fileSystems."/" = {
    device = "/dev/mapper/cryptroot";
    fsType = "ext4";
  };

  boot.initrd.clevisLuksAskpass = {
    enable = true;
    useTang = true; # after/wants network-online.target in initrd
  };

  # Tang needs a route in Stage 1.
  boot.initrd.systemd.network.enable = true;
  # Or: boot.initrd.network.enable = true;  # legacy/scripted-adjacent path
}

Apply and confirm the units land in the initrd closure:

sudo nixos-rebuild boot --flake .#desk-workstation
# Inspect generated initrd systemd units (lab VM with systemd stage1)
nixos-rebuild build-vm --flake .#desk-workstation   # optional disposable check
systemctl --no-pager cat clevis-luks-askpass.path 2>/dev/null || true
# After reboot into the generation, from an emergency shell if needed:
# journalctl -b -u clevis-luks-askpass.service --no-pager

With Tang up, Stage 1 should unlock without typing. With Tang down, the interactive passphrase prompt still works — askpass runs in parallel; it does not replace the human path.

Case 3: TPM2-only (no Tang, no initrd network)

When the only pin is TPM2, leave useTang = false (default) and skip initrd networking:

# desk_clevis_tpm2.nix
{
  boot.initrd.systemd.enable = true;

  boot.initrd.luks.devices.cryptroot.device =
    "/dev/disk/by-uuid/AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";

  fileSystems."/".device = "/dev/mapper/cryptroot";

  boot.initrd.clevisLuksAskpass.enable = true;
  # useTang defaults to false — no network-online ordering, no Tang warning.
}

Bind first (clevis luks bind … tpm2 '{}'). Expect unlock on the same hardware; expect fallback passphrase if the TPM state no longer matches the binding (PCR policy changes, board swap).

Case 4: Eval-time assertion and Tang networking warning

The module asserts systemd initrd:

# Anti-pattern — will fail evaluation
{
  boot.initrd.systemd.enable = false; # scripted stage1 (deprecated; removal 26.11)
  boot.initrd.clevisLuksAskpass.enable = true;
}

Representative eval failure:

error: clevis-luks-askpass requires boot.initrd.systemd.enable = true

Tang without initrd networking emits a warning (does not hard-fail):

# Warns at eval: configure networking in initrd for Tang
{
  boot.initrd.systemd.enable = true;
  boot.initrd.clevisLuksAskpass = {
    enable = true;
    useTang = true;
  };
  # missing: boot.initrd.network.enable or boot.initrd.systemd.network.enable
}

Representative warning text from the module:

In order to use a Tang pinned secret you must configure networking in initrd

Case 5: Contrast — older JWE-file boot.initrd.clevis

Pre-askpass desks (and ZFS/bcachefs Clevis paths) often used a JWE file declared per device:

# legacy_clevis_jwe.nix  — still valid, not the 26.05 LUKS-header default
{
  boot.initrd.clevis = {
    enable = true;
    useTang = true;
    devices.cryptroot.secretFile = ./secrets/cryptroot.jwe;
  };
  boot.initrd.network.enable = true;
}
Approach Where the secret lives Stage 1 fit
clevisLuksAskpass (26.05) Clevis token in LUKS header after clevis luks bind systemd askpass watches password requests
boot.initrd.clevis + secretFile JWE file in initrd secrets (/etc/clevis/<name>.jwe) Extra utils / storePaths; device-keyed map

For a plain LUKS root on systemd Stage 1, pick header bind + clevisLuksAskpass. Keep the JWE module when your unlock story is still file-based or filesystem-module-driven.

Case 6: 26.05 LUKS / Stage 1 operator notes (passphrase timeout)

Independently of Clevis, NixOS 26.05 systemd stage1 LUKS desks must:

  1. Set fileSystems."/".device to /dev/mapper/<name> matching boot.initrd.luks.devices.<name> (or add "x-systemd.device-timeout=infinity" on complex LVM-on-LUKS layouts).
  2. Remember cryptsetup-askpass is gone — from an initrd emergency shell use systemctl default so passphrase prompts return; for SSH unlock helpers that pipe stdin, use ssh -o RequestTTY=force.
# Emergency Stage 1 shell (e.g. after rd.systemd.debug_shell)
systemctl default
# Not: cryptsetup-askpass

Kernel cmdline escape hatch before you can rebuild:

systemd.default_device_timeout_sec=infinity

The trap

Trap A — enable clevisLuksAskpass without clevis luks bind. The module only answers requests; it does not create bindings. Unbound headers → interactive passphrase every boot (or hang until timeout if mapper/root paths are also wrong).

Trap B — useTang = true without initrd networking. Eval warns; Tang is unreachable at unlock time; you type the passphrase forever and wonder why “automatic unlock” is documented. Set boot.initrd.systemd.network.enable (or boot.initrd.network.enable) and verify Tang from Stage 1.

Trap C — point / at the encrypted UUID while expecting mapper unlock. Systemd stage1 waits on a device unit that never becomes the mounted root. Align mapper name and fileSystems."/".device.

Trap D — ship a JWE via boot.initrd.clevis.devices.*.secretFile and also enable askpass “to be safe” without understanding which path owns unlock. Pick one story per device. Header-token askpass is the boring 26.05 LUKS path.

Trap E — treat askpass as removing the passphrase. Pins fail (Tang down, TPM PCR drift). Keep a strong passphrase slot; practice typing it once per lab cycle.

# Anti-pattern — Tang askpass, no initrd net, wrong root device
{
  boot.initrd.clevisLuksAskpass = { enable = true; useTang = true; };
  fileSystems."/".device = "/dev/disk/by-uuid/ENCRYPTED-PART-UUID"; # wrong for stage1
}
# Boring fix
{
  boot.initrd.systemd.enable = true;
  boot.initrd.systemd.network.enable = true;
  boot.initrd.luks.devices.cryptroot.device = "/dev/disk/by-uuid/ENCRYPTED-PART-UUID";
  fileSystems."/".device = "/dev/mapper/cryptroot";
  boot.initrd.clevisLuksAskpass = { enable = true; useTang = true; };
}

The boring rule

  • On NixOS 26.05, unlock LUKS in systemd Stage 1 with boot.initrd.clevisLuksAskpass after clevis luks bind (Tang and/or TPM2) on the real device.
  • Require boot.initrd.systemd.enable = true (default); do not pair this module with scripted stage1.
  • Set useTang = true only when Tang is a pin, and enable initrd networking (boot.initrd.systemd.network.enable or boot.initrd.network.enable).
  • Keep the interactive passphrase; askpass runs in parallel, it does not delete the human path.
  • Prefer header tokens over boot.initrd.clevis + secretFile for plain LUKS systemd-stage1 desks.
  • Set fileSystems."/".device = "/dev/mapper/<name>" to match boot.initrd.luks.devices.<name>; use systemctl default instead of removed cryptsetup-askpass.
  • Pin desks to current stable (Nix 2.35+ / NixOS 26.05) and re-check release notes when bumping channels.

Try this

  1. On a disposable encrypted 26.05 VM, bind Tang (Case 1), import desk_clevis_tang.nix (Case 2), reboot with Tang up — confirm unlock without typing (journalctl -b -u clevis-luks-askpass.service).
  2. Stop the Tang server (or unplug the lab NIC), reboot — confirm the interactive passphrase still unlocks.
  3. Force-eval Case 4’s assertion (systemd.enable = false) and read the error; restore systemd stage1.
  4. Deliberately set fileSystems."/".device to the encrypted UUID, boot once, observe the timeout; fix to /dev/mapper/cryptroot.
  5. Skim the 26.05 announcement, release notes (clevis-luks-askpass + LUKS stage1 bullets), and the module source clevis-luks-askpass.nix; paste the boring rule into the desk runbook.