Compare commits

...

2 Commits

Author SHA1 Message Date
d65d991118 secrets: add mreow + yarn TPM recipients, re-encrypt desktop secrets
Some checks failed
Build and Deploy / mreow (push) Successful in 2m56s
Build and Deploy / yarn (push) Successful in 1m49s
Build and Deploy / muffin (push) Failing after 31s
2026-04-23 19:45:57 -04:00
06ccc337c1 secrets: proper agenix for desktop hosts via TPM identity
- modules/desktop-age-secrets.nix: agenix + rage wrapped with age-plugin-tpm,
  TPM identity primary, admin SSH key fallback for recovery/pre-bootstrap
- modules/desktop-lanzaboote-agenix.nix: extract secureboot.tar at activation
- modules/desktop-networkmanager.nix: revert to simple import of git-crypt file
- modules/server-age-secrets.nix: renamed from age-secrets.nix
- modules/desktop-common.nix: wire netrc + password-hash to agenix paths
- hosts/yarn/impermanence.nix: persist /var/lib/agenix across tmpfs wipes
- secrets/secrets.nix: recipient declarations (admin + tpm + muffin USB)
- secrets/desktop/*.age: secureboot.tar, nix-cache-netrc, password-hash
- scripts/bootstrap-desktop-tpm.sh: generate TPM identity + print recipient
2026-04-23 19:24:34 -04:00
6 changed files with 15 additions and 29 deletions

View File

@@ -4,9 +4,9 @@
... ...
}: }:
let let
# rage cannot invoke age-plugin-tpm unless the plugin binary is on PATH at # Wrap rage so age-plugin-tpm is on PATH at activation time.
# activation time. Wrap rage so the activation scripts (and anything else # Both mreow and yarn use age1tpm1… recipients (legacy P-256 encoding),
# that picks up `age.ageBin`) get age-plugin-tpm for free. # which age-plugin-tpm handles under its own name.
rageWithTpm = pkgs.writeShellScriptBin "rage" '' rageWithTpm = pkgs.writeShellScriptBin "rage" ''
export PATH="${pkgs.age-plugin-tpm}/bin:$PATH" export PATH="${pkgs.age-plugin-tpm}/bin:$PATH"
exec ${pkgs.rage}/bin/rage "$@" exec ${pkgs.rage}/bin/rage "$@"

View File

@@ -2,23 +2,16 @@
# Bootstrap the age-plugin-tpm identity for a desktop host (mreow / yarn). # Bootstrap the age-plugin-tpm identity for a desktop host (mreow / yarn).
# #
# Produces a TPM-sealed age identity at /var/lib/agenix/tpm-identity and # Produces a TPM-sealed age identity at /var/lib/agenix/tpm-identity and
# prints the legacy `age1tpm1…` recipient. The identity file is a TPM # prints the recipient string to add to secrets/secrets.nix.
# handle, not key material — the actual key never leaves the TPM.
#
# --tpm-recipient is required: nixpkgs only ships `age-plugin-tpm`, not the
# `age-plugin-tag` binary that rage looks up when it sees the new p256tag
# `age1tag1…` format. Until a packaged age-plugin-tag lands, every recipient
# stays in the legacy form so encryption works with off-the-shelf nixpkgs.
# #
# Usage: # Usage:
# doas scripts/bootstrap-desktop-tpm.sh # doas scripts/bootstrap-desktop-tpm.sh
# #
# After running: # After running:
# 1. Append the printed recipient to `tpm` in secrets/secrets.nix: # 1. Append the printed recipient to the `tpm` list in secrets/secrets.nix.
# "age1tpm1… <hostname>" # 2. Re-encrypt: nix-shell -p age-plugin-tpm rage --run \
# 2. `agenix -r` (from a shell with age-plugin-tpm on PATH) to re-encrypt # 'agenix -r -i ~/.ssh/id_ed25519'
# every desktop secret with the new recipient list. # 3. Commit + ./deploy.sh switch.
# 3. Commit + `./deploy.sh switch`.
set -euo pipefail set -euo pipefail
@@ -36,23 +29,16 @@ if [[ -f "$id_file" ]]; then
echo "existing identity found at $id_file — preserving" echo "existing identity found at $id_file — preserving"
else else
echo "generating TPM-sealed age identity..." echo "generating TPM-sealed age identity..."
nix run nixpkgs#age-plugin-tpm -- --generate --tpm-recipient --output "$id_file" nix-shell -p age-plugin-tpm --run "age-plugin-tpm --generate -o $id_file"
chmod 0400 "$id_file" chmod 0400 "$id_file"
chown root:root "$id_file" chown root:root "$id_file"
fi fi
# Always derive the legacy age1tpm1… recipient, even if the identity file # Read the recipient directly from the identity file header — no TPM
# was generated with the newer p256tag comment (Recipient line starts with # round-trip needed, no nix run, no set -e hazards.
# age1tag1…). `--convert --tpm-recipient` uses the same TPM object and just recipient=$(grep '^# Recipient:' "$id_file" | awk '{print $3}')
# serializes the public key point in the old format.
recipient=$(nix run nixpkgs#age-plugin-tpm -- --convert --tpm-recipient < "$id_file" 2>/dev/null | grep -o 'age1tpm1[0-9a-z]*' | head -n1)
if [[ -z "$recipient" ]]; then if [[ -z "$recipient" ]]; then
# fallback to parsing the header comment (only works when the identity was echo "failed to read recipient from $id_file" >&2
# already generated with --tpm-recipient).
recipient=$(grep '^# Recipient:' "$id_file" | awk '{print $3}')
fi
if [[ -z "$recipient" ]]; then
echo "failed to derive recipient for $id_file" >&2
exit 1 exit 1
fi fi
@@ -62,7 +48,7 @@ recipient for $host:
"$recipient $host" "$recipient $host"
next steps (run on a workstation with git-crypt unlocked): next steps (run on a workstation with git-crypt unlocked):
1. edit secrets/secrets.nix and append the line above inside the \`tpm\` list. 1. edit secrets/secrets.nix and add the line above to the \`tpm\` list.
2. nix run nixpkgs#agenix -- -r # re-encrypts every .age file. 2. re-encrypt: nix-shell -p age-plugin-tpm rage --run 'agenix -r -i ~/.ssh/id_ed25519'
3. git commit + ./deploy.sh switch 3. git commit + ./deploy.sh switch
EOF EOF

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.