aa74294a7d
gates / gates (push) Successful in 8s
175 -> 99 effective lines. New .claude/rules/{proxmox,localapi,backup,storage}.md alongside the
existing health-checks.md. The release section points at the felhom-build-deploy skill rather than
restating a table that drifts from the script; the layout section's per-package annotations moved
into the rule file for their area instead of being deleted.
Kept in the core because it is the only part re-injected after /compact: the root-CLI fence and its
three exceptions, the destructive-op gate, prove-ownership (audit A1), the gate entry point, the F9
live-validation fence, and the checklist.
health-checks.md overlaps localapi.md and storage.md on three globs -- deliberate, both load,
stated in each file. Go build/vet/test green and unchanged.
45 lines
2.4 KiB
Markdown
45 lines
2.4 KiB
Markdown
---
|
|
paths: ["internal/proxmox/**", "internal/reconcile/**", "internal/signedjobs/**"]
|
|
---
|
|
|
|
# Proxmox — the API contract, and how destructive work is gated
|
|
|
|
`internal/proxmox/` is the API-first `Client` plus the fenced root-CLI `Privileged`.
|
|
`internal/reconcile/` is the reconcile engine, reversibility gate, op journal and crash recovery.
|
|
`internal/signedjobs/` holds the operator-signed destructive executors (wipe, decommission).
|
|
|
|
## A 200 on the POST is not success
|
|
|
|
**Every mutating op is async**: it returns a **UPID**, and `WaitTask` must assert
|
|
`exitstatus == "OK"`. Authorization can fail at *task execution* long after the HTTP call returned
|
|
200. Treating the POST's status as the result is how a failed destroy reads as a successful one.
|
|
|
|
## The privsep token gotcha
|
|
|
|
A `--privsep 1` token's rights are the **intersection** of the backing user's permissions **and** the
|
|
token's own ACLs. The role must be granted on **both** or every call 403s. The same intersection rule
|
|
bites on PBS (`token ∩ user`).
|
|
|
|
## TLS
|
|
|
|
**SHA-256 leaf-cert pinning** against the self-signed host cert. **No insecure default**, ever. The
|
|
pin is the raw leaf-DER sha — the SAN is never checked, so a cert rotation changes the pin and the
|
|
agent must be re-pinned.
|
|
|
|
## The destructive path — never the direct call
|
|
|
|
| Do not | Because | Use |
|
|
|---|---|---|
|
|
| `Client.DestroyLXC` / `Vzdump` / `SetConfig` ad-hoc | skips classification, signature, per-guest serialization, crash recovery | `reconcile.Engine` paths / `RunSignedJob`; queue via `Queue.Submit` |
|
|
| add a method to `proxmox.Privileged` | breaks the 3-exception root-CLI fence (`routing_test.go`) | `proxmox.Runner` + a new sudoers `Cmnd_Alias` + `validate.go`-style checks |
|
|
| treat `ListLXC` output as "guests we own" | audit A1 — pre-v0.62.0 the stale-lock reaper did exactly this, contained only by the pool-scoped token | intersect with `Client.Pool` membership (`staleLockController.Guests()`); **fail safe on read failure** |
|
|
|
|
Full trap table: `REUSE.md` §3. Every guest joins the `felhom` pool — `VM.Audit` comes from the
|
|
`/pool` grant, not from a per-guest ACL.
|
|
|
|
<!--
|
|
The fence is not stylistic. It is what makes this component auditable: two types, one of which can
|
|
only speak HTTP and one of which can only shell out, with a test asserting neither crosses. A single
|
|
convenience method on Privileged that also makes an HTTP call would end that property silently.
|
|
-->
|