Initial public release
A chezmoi-based fleet-dotfiles template for macOS workstations: - Two-way auto-sync via launchd watcher + 5-min puller - Mesh SSH via modify_authorized_keys driven by .chezmoidata/fleet.yaml - age-encrypted secrets file - Bundled Claude Code agentic team (11 agents) + /lite + /lite-sub commands - Verify-before-claiming Stop hook - Generic statusline + project-boundary validate-path hook - Reference launchd plist for cross-fleet task-durations aggregation (companion repo: gitea.tojo.team/cardinale/task-durations) - AGENTS.md walks an agent through the entire setup Q&A interactively - docs/ covers architecture, security model, fleet onboarding
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Security model
|
||||
|
||||
What's encrypted, what isn't, and where each line is drawn.
|
||||
|
||||
## Encryption boundaries
|
||||
|
||||
| Lives where | Encrypted at rest? | Why |
|
||||
|---|---|---|
|
||||
| `~/.config/fleet-dotfiles/secrets.env` (live, on disk) | **No** — plaintext, mode 600 | Sourced by `.zshrc` on shell start; encryption would block that. |
|
||||
| `dot_config/private_fleet-dotfiles/encrypted_private_secrets.env.age` (chezmoi source) | **Yes** — age | Pushed to a forge that may be public-readable. |
|
||||
| `private_dot_ssh/encrypted_private_id_ed25519_*.age` (chezmoi source) | **Yes** — age | Same. |
|
||||
| `~/.ssh/id_ed25519_*` (live, on disk) | **No** — plaintext, mode 600 | OpenSSH reads it directly; no decryption hook. |
|
||||
| `~/.ssh/id_ed25519` (machine identity) | n/a — never enters chezmoi | Each machine generates its own; this key never travels. |
|
||||
| `~/.ssh/authorized_keys` | n/a — plaintext is the format | Composed by a `modify_` script that appends fleet pubkeys; pubkeys aren't secret. |
|
||||
| `~/.config/chezmoi/key.txt` (age private key) | n/a — never enters chezmoi | Distributed via secure side-channel only. |
|
||||
|
||||
## Threat model
|
||||
|
||||
This template is designed for a **fleet of personal machines you control**, syncing through a forge **you trust** (your own gitea, a private GitHub repo, etc.).
|
||||
|
||||
It assumes:
|
||||
|
||||
- The forge can read the encrypted artifacts but can't decrypt them.
|
||||
- A network observer can read the forge HTTPS traffic (just encrypted blobs).
|
||||
- The age private key is distributed out-of-band (USB, password manager, secure messaging).
|
||||
- Each machine's local disk is encrypted at the OS level (FileVault on macOS).
|
||||
|
||||
It does NOT defend against:
|
||||
|
||||
- A forge admin running arbitrary code on the server (they could swap `chezmoi-auto-sync.sh` and exfiltrate plaintext secrets the next time the watcher fires). Mitigation: only use forges you control or fully trust.
|
||||
- A compromised local user account (they'd have read access to the cleartext `secrets.env` and the age private key). Mitigation: standard endpoint hygiene; rotate aggressively if a machine is lost.
|
||||
- Side-channel disclosure of secrets via shell history, tmux scrollback, or process arg lists. Mitigation: never `echo` secrets, never pass them as command-line args, prefer env-var sourcing.
|
||||
|
||||
## Age key handling
|
||||
|
||||
- The fleet uses a **single age recipient public key** for encryption, stored in `.chezmoi.toml.tmpl`. It's safe to commit publicly.
|
||||
- Every fleet machine carries the **matching private key** at `~/.config/chezmoi/key.txt`. This is what decrypts the encrypted source files on apply. **Never commit this file.** Never email it. Never drop it in cloud storage. Move it via secure messaging or a USB drive.
|
||||
- If the private key is lost, you can still decrypt the source files on any machine that still has its copy of the key. Re-encrypt with a new keypair, distribute the new private key, and rotate keys-of-keys (any tokens that lived inside the encrypted secrets) since you'd want to assume any machine that lost track of its key may have leaked it.
|
||||
|
||||
## What gets pushed to the forge
|
||||
|
||||
After a successful watcher fire, the chezmoi source repo on the forge contains:
|
||||
|
||||
- `dot_*` and `private_dot_*` directories with cleartext config (paths, hostnames, settings).
|
||||
- `encrypted_*.age` files for anything containing secrets.
|
||||
- `*.tmpl` files (chezmoi templates with cleartext placeholders).
|
||||
- The chezmoi-auto-sync.sh script (cleartext bash).
|
||||
|
||||
It does NOT contain:
|
||||
|
||||
- The age private key.
|
||||
- The cleartext `secrets.env`.
|
||||
- The machine-identity SSH private key (`~/.ssh/id_ed25519`).
|
||||
- Any `~/.claude/projects/` transcripts (these are local-only).
|
||||
- Any task-durations parquet output (lives at `~/.local/share/task-durations/`, outside the watch tree).
|
||||
|
||||
## SSH between fleet machines
|
||||
|
||||
Mesh access is set up by `private_dot_ssh/modify_private_authorized_keys.tmpl`. On every `chezmoi apply`, it reads `.chezmoidata/fleet.yaml`, walks every machine's pubkey, and appends any that aren't already in `~/.ssh/authorized_keys`. Existing entries (e.g., a GitHub deploy key, a colleague's pubkey) are preserved.
|
||||
|
||||
The flow when a new machine joins:
|
||||
|
||||
1. New machine generates `~/.ssh/id_ed25519` locally.
|
||||
2. You add its pubkey to `.chezmoidata/fleet.yaml` (committed via the watcher).
|
||||
3. Within ~7 minutes, every existing fleet machine pulls the change and the `modify_` script appends the new pubkey to its `authorized_keys`.
|
||||
4. Now the new machine can SSH any peer.
|
||||
|
||||
## Rotation playbook
|
||||
|
||||
| Compromise | Action |
|
||||
|---|---|
|
||||
| One token leaked | Edit `~/.config/fleet-dotfiles/secrets.env` with the new value → save → watcher commits + pushes encrypted form → puller fans it out. Rotate the token at the issuing service. |
|
||||
| Age private key leaked | Generate new keypair → decrypt all `*.age` files in source → re-encrypt with new public key (`chezmoi re-add --encrypt`) → distribute new private key out-of-band → rotate every token in secrets.env (assume they leaked). |
|
||||
| One machine lost / stolen | Remove its entry from `.chezmoidata/fleet.yaml` → commit → on next puller cycle, remaining machines no longer accept the lost machine's pubkey. Revoke any tokens the lost machine had local cleartext access to. |
|
||||
| Forge compromised | Treat as if all encrypted secrets leaked: rotate every token. Move the fleet to a new forge. |
|
||||
Reference in New Issue
Block a user