Skip to content

let LUKS header backups go straight off-system #62

Description

@danneu

Problem

Today's LUKS header backup workflow is a 3-step dance:

  1. braid add, braid replace, braid enroll, and braid recover write a local copy of the LUKS header to /var/lib/braid/luks-headers/<mapper>.luksheader.
  2. The user copies the file off-system (USB, second machine, cloud key storage).
  3. The user deletes the local copy. braid status and the TUI emit an advisory (LUKS header backups exist in ... -- copy offsite and delete local copies) for as long as a local copy persists, because keeping it on the same machine defeats the off-system backup model.

The local file is documented in docs/luks-unlock.md:104-122 as a "transient byproduct," not the intended backup target. It exists only because no command currently accepts a user-supplied off-system destination.

The result is a UX smell: the product warns the user about an artifact it just created. A workflow with two manual steps after the command (copy, delete) is more error-prone than a workflow with zero. The advisory loop in cli/src/luks.rs:934-951 (header_backup_advisories_in), wired into cli/src/status.rs:345 and the TUI, exists only to nag users through that dance.

Current callsites

All call luks::backup_luks_header / backup_luks_header_to against the default state-paths target:

  • cli/src/enroll_key_file.rs:306 -- in apply_enrollment
  • cli/src/add.rs:905 -- after fresh-disk LUKS format + keyfile enroll
  • cli/src/replace.rs:488 -- after the new disk's LUKS format + keyfile enroll
  • cli/src/recover.rs:2179 and cli/src/recover.rs:2467 -- post-recovery rebuild paths

Proposed direction

Sketch only -- pick one or combine:

  • Option A: --backup-to <path> flag on add, replace, and enroll to direct the header backup straight to a user-supplied off-system path (mounted USB, secondary disk, etc.). Default to today's local path if the flag is absent so callers can opt in incrementally.
  • Option B: dedicated braid backup-headers <dir> primitive that produces backups for all current pool members on demand. Pairs cleanly with future workflows like keyfile rotation or "audit my headers."

With either, the long-term goal is to retire the local default at /var/lib/braid/luks-headers/ -- which then retires the advisory in cli/src/luks.rs:934-951, the status surface at cli/src/status.rs:345, and the TUI advisory band.

Open questions

  • Atomicity. add, replace, and enroll mutate LUKS slots before the backup runs. If a user-supplied off-system target is unavailable mid-command (USB unplugged, mount failed), do we fail, fall back to a local default, or skip the backup with a warning? Today's behavior is ?-propagate, which leaves the keyfile enrolled but the operation reported as failed.
  • Plan/dry-run preview. compile_enroll_steps in cli/src/enroll_key_file.rs:354-395 lists the backup target in the dry-run plan. A user-supplied path has to be resolvable at planning time so the preview can show it.
  • Mount-point invariant. Off-system targets on USB devices should probably reuse the mount-point validation pattern already in validate_generated_keyfile_target (cli/src/enroll_key_file.rs:546-582) -- the same "is this a real mount, not the host root falling through?" guard.
  • Messaging invariant interaction. docs/luks-unlock.md:104-122 forbids user-visible recovery guidance from referencing local .luksheader files. Removing the local default makes that invariant trivially satisfied; the messaging code in doctor, status, and unlock errors should be re-audited once the local path is gone.

Non-goals

  • Don't add backwards-compatibility shims for the local default once it's removed. braid is unreleased.
  • Don't change the failure semantics of luks::backup_luks_header_to itself in this work -- a separate, smaller change can improve the per-failure error message while this larger refactor is still pending.

Why not now

Feature work, not a fix. Only worth doing if the advisory churn is real friction in practice. Filed for tracking; surfaced during a verify-issue review of an enroll-then-backup-failure code-review finding that misread the documented "transient byproduct" model. The narrow fix the finding proposed (re-create the local backup on AlreadyEnrolled re-runs) was rejected because it would re-create the file the user is documented to delete -- this larger refactor is the right shape if it's worth doing at all.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions