crypttab: add /etc/crypttab.initramfs support with keyfile, tries, nofail options#318
Closed
pilotstew wants to merge 9 commits intoanatol:masterfrom
Closed
crypttab: add /etc/crypttab.initramfs support with keyfile, tries, nofail options#318pilotstew wants to merge 9 commits intoanatol:masterfrom
pilotstew wants to merge 9 commits intoanatol:masterfrom
Conversation
Parses fido2-device=auto and tpm2-device=auto from rd.luks.options, deferring the keyboard passphrase prompt until the token attempt fails or token-timeout elapses (default 30s). Prevents simultaneous keyboard and FIDO2/TPM2 unlock flows when a hardware token is enrolled. Also adds token-timeout=DURATION support (bare integer = seconds, matching systemd semantics; 0 = wait forever).
Generator bundles /etc/crypttab.initramfs as /etc/crypttab in the initramfs when the file exists on the host (opt-in by file presence, same pattern as mkinitcpio/dracut). Referenced keyfiles and detached LUKS headers are automatically bundled — no extra_files entry needed. Init reads /etc/crypttab at boot and merges entries into the LUKS unlock queue. Supported options: discard, same-cpu-crypt, submit-from-crypt-cpus, no-read-workqueue, no-write-workqueue, fido2-device=auto, tpm2-device=auto, token-timeout=, key-slot=, header=, noauto. Kernel cmdline rd.luks.* parameters take precedence over crypttab entries for the same device. Also fixes recoverSystemdFido2Password to select on a done channel so goroutines exit cleanly when the device is unlocked by another means, and adds token gating so systemd-fido2/tpm2 tokens are only attempted when the corresponding fido2-device=/tpm2-device= option is set.
nofail, keyfile-offset=, keyfile-size=, tries=, and keyfile-on-separate-device are silently ignored; mark them with TODOs so they are easy to find later.
Parse tries=N from crypttab options and enforce the limit in requestKeyboardPassword. tries=0 (default) retains the existing unlimited retry behaviour. Cached passphrases from other volumes are still tried silently before the limit is applied.
When nofail is set, any luksOpen error is logged as a warning and boot continues. Also introduces a senderWg that tracks every goroutine able to send to the volumes channel; when all give up the channel is closed so luksOpen unblocks instead of hanging forever. This fixes a pre-existing deadlock for token-only setups where all tokens fail and there are no keyboard slots, regardless of nofail.
Parse keyfile-offset= and keyfile-size= options and honour them when reading the keyfile. Replaces os.ReadFile with a readKeyfile helper that seeks to the offset and reads at most keyfileSize bytes (0 = read to end), matching the systemd-cryptsetup behaviour.
Implement the keyfile:UUID=<dev> / keyfile:LABEL=<dev> / etc. syntax
from crypttab(5) that places the keyfile on a removable block device
rather than bundling it in the initramfs.
At unlock time booster:
- Waits for the named device to appear (up to keyfile-timeout= or
MountTimeout) via BTRFS_IOC_DEVICES_READY-style polling
- Mounts it read-only at /run/booster/keydev-<name>
- Reads the keyfile (honoring keyfile-offset= / keyfile-size=)
- Unmounts immediately after the key is read
Falls back to keyboard passphrase if the device is not found within
the timeout, matching systemd-cryptsetup behaviour.
The same keyfile field syntax is also supported on the rd.luks.key=
kernel command-line parameter.
The generator skips bundling the keyfile into the initramfs image when
it detects a device reference suffix (UUID=/LABEL=/PARTUUID=/PARTLABEL=).
New unit tests cover parseKeyfileField, keyfile-timeout= parsing, the
same-device error, and the generator-side isKeyfileOnDevice helper.
When two LUKS volumes appear simultaneously (e.g. btrfs RAID1 across two encrypted drives), both goroutines could check an empty passphrase cache and proceed to readPassword before either finished PBKDF. The result: two prompts for what should be a single-passphrase setup, and a deadlock when the second prompt is never answered. Root cause: inputMutex was released before PBKDF, so the second goroutine could acquire the console and start prompting while the first was still deriving keys. Fix: hold inputMutex through PBKDF on the console path. When the next goroutine acquires the mutex it re-checks the cache and finds the newly added password, unlocking silently without a second prompt. Add readPasswordLocked (caller holds inputMutex) to support this; the existing readPassword wrapper is unchanged for other callers. Verified with QEMU integration tests: btrfs RAID1 across two LUKS2 volumes with identical passphrases prompts exactly once; with distinct passphrases both are prompted in device-enumeration order.
Add integration tests exercising the new crypttab features and the
passphrase cache fix end-to-end in QEMU:
- TestLUKS2CrypttabPassphrase: unlock via /etc/crypttab only
- TestLUKS2NofailCrypttab: nofail entry for absent device
- TestLUKS2CrypttabKeyfileOffsetSize: keyfile-offset= / keyfile-size=
- TestLUKS2KeyfileOnDeviceCmdline: rd.luks.key= with separate keydev
- TestLUKS2KeyfileOnDeviceCrypttab: same via /etc/crypttab
- TestBtrfsRaid1LuksSharedPassphrase: btrfs RAID1, two LUKS2 drives
with the same passphrase — prompts once, cache unlocks the second
- TestBtrfsRaid1LuksDifferentPassphrases: btrfs RAID1, distinct
passphrases — both prompted in device-enumeration order
Add asset generators:
- luks_keyfile_offset.sh: LUKS2 with 512-byte preamble keyfile
- luks_keyfile_device.sh: LUKS2 with keyfile on a separate vfat image
- luks_btrfs_two.sh: two LUKS2 images forming a single btrfs RAID1
All tests verified passing under QEMU.
This was referenced Mar 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds support for
/etc/crypttab.initramfs, which is bundled into theinitramfs as
/etc/crypttabat image-build time (opt-in by file presence,same pattern as mkinitcpio/dracut). Kernel cmdline
rd.luks.*parameterstake precedence over crypttab entries for the same device.
Prerequisites included in this PR:
rd.luks.options: supportfido2-device=auto,tpm2-device=auto, andtoken-timeout=— defers the keyboard prompt until the token attemptcompletes (or times out), preventing simultaneous FIDO2 and keyboard
unlock flows. Also improves the FIDO2 PIN prompt to show the LUKS
mapping name and retry on incorrect PIN.
New crypttab options:
x-initrd.attachdiscard,same-cpu-crypt, etc.rd.luks.options)fido2-device=auto,tpm2-device=autotoken-timeout=key-slot=noautotries=Nnofailkeyfile-offset=,keyfile-size=keyfile:UUID=…/keyfile:LABEL=…Also fixes a passphrase cache race: when two LUKS volumes appear
simultaneously (e.g. btrfs RAID1 across two encrypted drives), the second
goroutine now finds the cached password and unlocks silently without a
second prompt.
Test plan
go test ./init/... ./generator/...cd tests && go test -v -run "TestLuksCrypttab|TestLuksKeyfileDevice|TestLuksKeyfileOffset|TestBtrfsRaid1Luks"/etc/crypttab.initramfs, rebuild initramfs, boot