secrets overhaul: use tpm for laptop (need to migrate desktop later)
This commit is contained in:
21
AGENTS.md
21
AGENTS.md
@@ -36,10 +36,11 @@ lib/
|
||||
overlays.nix # jellyfin-exporter, igpu-exporter, reflac, ensureZfsMounts
|
||||
patches/nixpkgs/ # applied to nixpkgs-stable for muffin builds
|
||||
secrets/
|
||||
desktop/ # git-crypt: mreow + yarn share these (wifi, nix-cache-netrc, secureboot.tar, password-hash, disk-password)
|
||||
secrets.nix # agenix recipients (who can decrypt each .age)
|
||||
desktop/ # agenix *.age (mreow + yarn) + disk-password (install-time only, git-crypt)
|
||||
home/ # git-crypt: per-user HM secrets (api keys, steam id)
|
||||
server/ # agenix *.age + git-crypt *.nix/*.tar/livekit_keys
|
||||
usb-secrets/ # USB-resident agenix identity key (git-crypt inside the repo)
|
||||
server/ # agenix *.age + git-crypt *.nix/*.tar/livekit_keys (muffin)
|
||||
usb-secrets/ # USB-resident agenix identity for muffin (git-crypt inside the repo)
|
||||
```
|
||||
|
||||
**Never read or write files under `secrets/`.** They are encrypted at rest (git-crypt for plaintext, agenix for `.age`). The git-crypt key is delivered to `muffin` at runtime as `/run/agenix/git-crypt-key-nixos.age`.
|
||||
@@ -89,7 +90,7 @@ If Nix complains about a missing file, `git add` it first — flakes only see tr
|
||||
| `common-` | imported by ALL hosts | `common-doas.nix`, `common-nix.nix`, `common-shell-fish.nix` |
|
||||
| `desktop-` | imported by mreow + yarn only | `desktop-common.nix`, `desktop-steam.nix`, `desktop-networkmanager.nix` |
|
||||
| `server-` | imported by muffin only | `server-security.nix`, `server-power.nix`, `server-impermanence.nix`, `server-lanzaboote-agenix.nix` |
|
||||
| *(none)* | host-specific filename-scoped; see file contents | `age-secrets.nix`, `zfs.nix`, `no-rgb.nix` (yarn + muffin) |
|
||||
| *(none)* | host-specific filename-scoped; see file contents | `zfs.nix`, `no-rgb.nix` (yarn + muffin) |
|
||||
|
||||
New modules: pick the narrowest prefix that's true, then add the import explicitly in the host's `default.nix` (there is no auto-discovery).
|
||||
|
||||
@@ -117,14 +118,18 @@ New modules: pick the narrowest prefix that's true, then add the import explicit
|
||||
## Secrets
|
||||
|
||||
- **git-crypt** covers `secrets/**` per the root `.gitattributes`. Initialized with a single symmetric key checked into `secrets/server/git-crypt-key-nixos.age` (agenix-encrypted to the USB SSH identity).
|
||||
- **agenix** decrypts `secrets/server/*.age` at activation into `/run/agenix/` on muffin.
|
||||
- **USB identity**: `/mnt/usb-secrets/usb-secrets-key` on muffin; the age identity path is wired in `modules/usb-secrets.nix`.
|
||||
- **Encrypting a new agenix secret** uses the SSH public key directly with `age -R`:
|
||||
- **agenix** decrypts `*.age` into `/run/agenix/` at activation on every host:
|
||||
- **muffin**: identity is `/mnt/usb-secrets/usb-secrets-key` (ssh-ed25519 on a physical USB). Wired in `modules/usb-secrets.nix`.
|
||||
- **mreow + yarn**: identity is `/var/lib/agenix/tpm-identity` (an `age-plugin-tpm` handle sealed by the host's TPM 2.0). Wired in `modules/desktop-age-secrets.nix`; yarn persists `/var/lib/agenix` through impermanence.
|
||||
- **Recipients** are declared in `secrets/secrets.nix`. Desktop secrets are encrypted to the admin SSH key + each host's TPM recipient; server secrets stay encrypted to the muffin USB key.
|
||||
- **Bootstrap a new desktop**: run `doas scripts/bootstrap-desktop-tpm.sh` on the host. It generates a TPM-sealed identity at `/var/lib/agenix/tpm-identity` and prints an `age1tpm1…` recipient. Append it to the `tpm` list in `secrets/secrets.nix`, run `agenix -r` to re-encrypt, commit, `./deploy.sh switch`.
|
||||
- **Encrypting a new server secret** uses the SSH public key directly with `age -R`:
|
||||
```sh
|
||||
age -R <(ssh-keygen -y -f secrets/usb-secrets/usb-secrets-key) \
|
||||
-o secrets/server/<name>.age \
|
||||
/path/to/plaintext
|
||||
```
|
||||
For desktop secrets, prefer `agenix -e secrets/desktop/<name>.age` from a shell with `age-plugin-tpm` on PATH — it reads `secrets/secrets.nix` and encrypts to every recipient listed there.
|
||||
- **DO NOT use `ssh-to-age`**. It produces `X25519` recipient stanzas, which the SSH private key on muffin cannot decrypt (it only decrypts `ssh-ed25519` stanzas produced by `age -R` against the SSH pubkey). Mismatched stanzas show up as `age: error: no identity matched any of the recipients` at deploy time.
|
||||
- Never read or commit plaintext secrets. Never log secret values.
|
||||
|
||||
@@ -210,7 +215,7 @@ Prior art: the 3-path `{kernel,initrd,kernel-modules}` diff is lifted from nixpk
|
||||
|
||||
- **Privilege escalation**: `doas` everywhere; `sudo` is disabled on every host.
|
||||
- **Shell**: fish. `bash` login shells re-exec into fish via `programs.bash.interactiveShellInit` (see `modules/common-shell-fish.nix`).
|
||||
- **Secure boot**: lanzaboote. Desktops extract keys from `secrets/desktop/secureboot.tar`; muffin extracts from an agenix-decrypted tar (see `modules/server-lanzaboote-agenix.nix`).
|
||||
- **Secure boot**: lanzaboote. Every host extracts keys from an agenix-decrypted tar at activation — desktops via `modules/desktop-lanzaboote-agenix.nix`, muffin via `modules/server-lanzaboote-agenix.nix`.
|
||||
- **Impermanence**: muffin is tmpfs-root with `/persistent` surviving reboots (`modules/server-impermanence.nix`); yarn binds `/home/primary` from `/persistent` (`hosts/yarn/impermanence.nix`).
|
||||
- **Disks**: disko.
|
||||
- **Binary cache**: muffin runs harmonia; desktops consume it at `https://nix-cache.sigkill.computer`.
|
||||
|
||||
Reference in New Issue
Block a user