NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

commit de158dfad01b94e31ed96cdab01748dda83c5fa2
parent 7f4371bbd34381c9353b92739e917b297084ffe0
Author: DAEMON <zer0sec.xp@icloud.com>
Date:   Sat, 10 Oct 2026 12:06:44 +0100

feat(ssd): add SOPS-encrypted LUKS header backup to secure-vault

- Add a `backup-header` subcommand that exports the LUKS header, encrypts it with SOPS, and publishes it to a backup path without overwriting anything
- Remove the unused container file if `luksFormat` fails before a LUKS header is written; keep it if a header exists
- Prompt the user to type uppercase YES at the cryptsetup confirmation step
- Ignore vault images, headers, and key material in `.gitignore` so they stay out of the public flake

Commit-Date: 2026-10-10T12:06:51+01:00
Commit-Host: daemonsec@nixos

Diffstat:
M.gitignore | 15+++++++++++++++
Mmodules/hosts/laptop/ssd.nix | 45+++++++++++++++++++++++++++++++++++++++++----
2 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -8,3 +8,18 @@ keys.txt # scratch of the plan executor and a stray empty clone, never flake source .superpowers/ /NixDaemon/ + +# Vault data and plaintext recovery material belong outside this public flake. +*.luks +*.luks.building +*.luksHeader +*.header.bin +ssd.key +vault.key +vault.passphrase +secure-vault-header.* +/secrets/*.key +/secrets/*.pem +/secrets/*.backup +/secrets/*.bak +/secrets/*.tmp diff --git a/modules/hosts/laptop/ssd.nix b/modules/hosts/laptop/ssd.nix @@ -18,12 +18,12 @@ let vault = pkgs.writeShellApplication { name = "secure-vault"; - runtimeInputs = with pkgs; [ cryptsetup util-linux coreutils e2fsprogs psmisc gawk ]; + runtimeInputs = with pkgs; [ cryptsetup util-linux coreutils e2fsprogs psmisc gawk sops ]; text = '' export LC_ALL=C case "''${1:-help}" in - init|open|lock|status|busy) ;; - *) echo 'Usage: secure-vault {init|open|lock|status|busy}'; exit 0 ;; + init|open|lock|status|busy|backup-header) ;; + *) echo 'Usage: secure-vault {init|open|lock|status|busy|backup-header DEST.sops.json}'; exit 0 ;; esac if (( EUID != 0 )); then exec /run/wrappers/bin/sudo "$0" "$@" @@ -74,7 +74,15 @@ fallocate -l 100000000000 "$pending" echo 'Creating a NEW 100 GB container. Choose a long, unique vault passphrase.' echo 'This does not format or close the existing SSD.' - cryptsetup luksFormat --type luks2 --pbkdf argon2id --iter-time 3000 "$pending" + echo 'At the cryptsetup confirmation prompt, type uppercase YES.' + if ! cryptsetup luksFormat --type luks2 --pbkdf argon2id --iter-time 3000 "$pending"; then + # Only this newly created file may be removed, and only without a LUKS header. + if cryptsetup isLuks "$pending"; then + fail 'Formatting stopped but a LUKS header exists; unfinished container preserved.' + fi + rm -- "$pending" + fail 'Creation aborted; unused container file removed. You can retry init.' + fi # Opening a regular file uses a cryptsetup-managed loop device. cryptsetup open --type luks "$pending" "$mapper" cleanup_init() { @@ -120,6 +128,35 @@ fi echo 'Private vault locked. Existing SSD remains available.' ;; + backup-header) + require_ssd + [[ -f "$image" && ! -L "$image" ]] || fail 'Vault container does not exist.' + destination="''${2:-}" + [[ "$destination" == /*.sops.json ]] || fail 'Supply an absolute destination ending in .sops.json on your backup drive.' + [[ ! -e "$destination" && ! -L "$destination" ]] || fail 'Backup destination already exists; refusing to overwrite it.' + # Only public SOPS recipients/rules enter the Nix store. + # Header plaintext stays in root-only /run and is removed on exit. + umask 077 + header="" + encrypted="" + cleanup_header() { + [[ -z "$header" ]] || rm -f "$header" + [[ -z "$encrypted" ]] || rm -f "$encrypted" + return 0 + } + trap cleanup_header EXIT + header=$(mktemp /run/secure-vault-header.XXXXXX) + encrypted=$(mktemp "$(dirname "$destination")/.vault-header-encrypted.XXXXXX") + rm "$header" + cryptsetup luksHeaderBackup "$image" --header-backup-file "$header" + sops encrypt --config ${../../../.sops.yaml} \ + --filename-override secrets/vault-header.sops.json \ + --input-type binary --output-type json "$header" > "$encrypted" + # Atomic publication refuses overwrite, including symlinks. + ln "$encrypted" "$destination" + chown "$owner:$(id -gn "$owner")" "$destination" + echo "SOPS-encrypted header backup: $destination" + ;; status) cryptsetup status "$mapper" || true findmnt --mountpoint "$target" || true