crypttab: keyfile-on-device, header=, fido2-device=, tpm2-device=#331
Open
pilotstew wants to merge 7 commits intoanatol:masterfrom
Open
crypttab: keyfile-on-device, header=, fido2-device=, tpm2-device=#331pilotstew wants to merge 7 commits intoanatol:masterfrom
pilotstew wants to merge 7 commits intoanatol:masterfrom
Conversation
The keyfile field in /etc/crypttab accepts a path with an optional device specifier suffix: /path/to/key:UUID=xxxx (or LABEL=, PARTUUID=, PARTLABEL=). When a device specifier is present the init binary waits for that device to appear, mounts it read-only at /run/booster/keydev-<name>, reads the keyfile, then unmounts. Add keyfile-timeout= option to control how long to wait for the keyfile device (bare integer = seconds, or any duration accepted by time.ParseDuration, e.g. "30s"). Defaults to the global mount_timeout. Add acquireFile() as a shared helper used by both acquireKeyfilePassword() and acquireHeader() — both follow the same pattern of optionally mounting a device, resolving a file path within it, and returning a cleanup func. Keyfiles on runtime devices are not bundled into the initramfs by the generator (already handled by the existing isKeyfileOnDevice check in generator/crypttab.go).
Parse the header= option in /etc/crypttab entries. The value follows the same path:SPECIFIER= syntax as the keyfile field: header=/path/to/header — file bundled into initramfs header=/path/to/header:UUID=xxxx — file on a separate device header=/dev/sdx — raw block device For a header on a separate device, init waits for the device, mounts it read-only at /run/booster/hdrdev-<name>, opens the LUKS device with that header, then unmounts. Refactored acquireHeader() to use the shared acquireFile() helper introduced in the previous commit. The generator bundles absolute-path headers that exist on the host at image build time. Headers on runtime devices (/dev/... or :UUID= paths) are left for runtime resolution.
Parse the three token-related crypttab options:
fido2-device=auto — sets tokenFido2; booster auto-detects enrolled
systemd-fido2 tokens from the LUKS header
tpm2-device=auto — sets tokenTpm2; same auto-detection for TPM2
token-timeout=<dur> — how long to wait for tokens before also starting
the keyboard passphrase prompt; bare integer =
seconds, or any duration string (e.g. "30s")
The option values for fido2-device= and tpm2-device= are intentionally
ignored — booster discovers which tokens are enrolled by inspecting the
LUKS header at runtime, so specifying a particular device path has no
additional effect. Setting the option is the explicit opt-in that enables
priority token scheduling in luksOpen().
token-timeout= reuses parseTokenTimeout() from init/cmdline.go, which
already parses the same format for rd.luks.options.
When fido2-device= or tpm2-device= is set in a crypttab entry, the corresponding systemd-fido2 / systemd-tpm2 LUKS tokens become "priority" tokens: the keyboard passphrase prompt is deferred until they have all been tried (or token-timeout= elapses). Without these flags the existing behavior is preserved — all enrolled tokens are tried and the keyboard runs after all token goroutines finish. Key changes to luksOpen(): - priorityTypes map built from tokenFido2/tokenTpm2 flags. - Priority tokens track in tokenWg and close done immediately on success (via closeDone sync.Once), signalling the keyboard fallback to skip. - Non-priority tokens remain in tokenWg for keyboard timing but do not signal done early. - checkSlotsWithPassword only includes slots not covered by any token when hasPriority; otherwise all available slots are passed (preserving the existing behavior for non-crypttab volumes). - Watcher goroutine checks done before closing volumes to avoid racing with a priority token that already signalled success.
When any bundled crypttab entry contains fido2-device=, the generator now automatically sets enableFido2 = true and bundles fido2plugin.so into the initramfs — no manual enable_fido2: true in /etc/booster.yaml is required. This is possible for the crypttab path (unlike the rd.luks.uuid= cmdline path) because the generator can see fido2-device= directly in the crypttab file at image build time. appendCrypttab() now returns (hasFido2 bool, err error); the caller in generateInitRamfs() uses the new return value to set conf.enableFido2.
Add integration tests for the keyfile-on-device, header=, fido2-device=,
and tpm2-device= crypttab features added in earlier commits:
TestCrypttabKeyfileDevice: unlocks LUKS2 via a keyfile on a separate
ext4 block device configured as /keyfile:UUID=<dev> in crypttab.
No passphrase prompt expected.
TestCrypttabHeader: unlocks LUKS2 with a detached header referenced
via the crypttab header= option. The generator bundles the header
file automatically; the test reuses the luks2.detached_header.* assets.
TestCrypttabFido2: full end-to-end FIDO2 unlock via QEMU USB passthrough.
Verifies fido2-device=auto triggers FIDO2 unlock and that fido2plugin.so
is auto-bundled without enable_fido2: true. Requires a hardware FIDO2
device but not a specific one — any device detectable by fido2-token(1)
works. Skipped when BOOSTER_TEST_FIDO2_PIN is unset. Set it without
exposing the PIN in shell history:
read -s "pin?FIDO2 PIN: " && echo
BOOSTER_TEST_FIDO2_PIN="$pin" go test -v -run TestCrypttabFido2 .
During image creation the PIN is written to a tmpfs file under
XDG_RUNTIME_DIR and deleted immediately after systemd-cryptenroll
enrols the credential — it never touches disk.
TestCrypttabFido2NoDevice: exercises the token-timeout → passphrase
fallback path without any hardware. Uses a static image containing
a fake systemd-fido2 LUKS token (random credential/salt) created by
luks_fido2_nodev.sh; the VM waits 30 s, finds no matching device, and
falls back to the keyboard passphrase prompt.
TestCrypttabTPM2: verifies tpm2-device=auto triggers TPM2 token unlock
using the swtpm software emulator.
Rewrite tests/generators/systemd_fido2.sh to run cryptsetup luksFormat
and systemd-cryptenroll directly on the image file rather than via a loop
device. The previous approach created the loop device first, which caused
udev to fire blkid immediately and hold an exclusive advisory lock on the
device — racing with cryptsetup and producing intermittent EBUSY failures.
Both tools support regular files for LUKS2 directly; a loop device is now
created only for the final mkfs/mount step once the LUKS2 header is fully
written.
Add luks_keyfile_device.sh generator script (creates the LUKS root image
and key device in a single run) and register both new scripts in assets.go.
Expand the crypttab section to cover the options added in this branch that differ from standard crypttab(5) semantics: - keyfile on a separate device (/path:UUID=...) - header= auto-bundling and runtime device-mount forms - fido2-device=auto auto-enabling fido2plugin.so in the generator - token-timeout= / keyfile-timeout= duration format
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.
Extends
/etc/crypttabsupport (built on the now-merged crypttab-core and libfido2 work) with the remaining advanced options. All features are opt-in via crypttab entry options and require no configuration changes when not used.New options
keyfile=/path:UUID=xxx— keyfile on a separate block device. Booster waits for the device, mounts it read-only, reads the key, then unmounts.keyfile-timeout=controls how long to wait (default:mount_timeout).header=— detached LUKS header. Plain absolute paths are bundled into the initramfs at build time. The/path:UUID=xxxform mounts the header device at runtime;/dev/...uses the raw block device directly.fido2-device=auto— activates FIDO2 token unlock. The generator auto-detects this option and bundlesfido2plugin.sowithout requiringenable_fido2: truein the config. Init iterates attached FIDO2 devices, prompts for PIN (with Plymouth support), and falls back to the keyboard passphrase aftertoken-timeout=elapses (default: 30 s).tpm2-device=auto— activates TPM2 token unlock with the same priority scheduling and timeout behaviour asfido2-device=.token-timeout=/keyfile-timeout=— accept a bare integer (seconds) or any Go duration string (e.g.30s,2m).Plymouth improvements
The switch from the
fido2-assertsubprocess to the nativego-libfido2plugin gives the init binary fine-grained control over the FIDO2 assertion flow for the first time. Underfido2-assert, the subprocess handled device interaction internally and returned only a final result — there was no opportunity to surface intermediate state like "waiting for touch" or "PIN incorrect, try again" to the user. With the plugin, each stage of the assertion is visible to the caller, making meaningful status messages possible.This is why the Plymouth integration is appearing here rather than in the Plymouth PR: the messages it carries are only meaningful now. Routing them to either Plymouth or the console throughout the unlock flow prompted a small
statusMessage()helper inplymouth.gothat avoids repeated if/else blocks. The TPM2 PIN prompt was also updated to route through Plymouth consistently — a gap that had existed since TPM2 support was first added.Testing
TestCrypttabKeyfileDeviceandTestCrypttabHeaderuse static assets and run entirely in QEMU.TestCrypttabFido2requires a hardware FIDO2 device but not a specific one — any device recognised byfido2-token -Lworks. It is skipped whenBOOSTER_TEST_FIDO2_PINis unset. The recommended invocation to avoid exposing the PIN in shell history:During image creation the PIN is written only to a tmpfs file under
XDG_RUNTIME_DIR, passed tosystemd-cryptenrollviaCREDENTIALS_DIRECTORY, and deleted immediately after enrolment — it never touches disk.TestCrypttabFido2NoDevicecovers the token-timeout → passphrase fallback path without any hardware, using a static image with a fakesystemd-fido2LUKS token. This was the primary development vehicle for iterating on the fallback logic.TestCrypttabTPM2verifiestpm2-device=autousing theswtpmsoftware emulator.Closes #319.