From eea3cd4040ba8a55f9babc74a766f108d22d2c36 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 29 Jun 2026 18:57:23 +0200 Subject: [PATCH] docs: hub REPORT (v0.19.0 capability alert) + multidrive audit countermeasure pointer Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt --- REPORT.md | 80 ++++++++----------- ...-multidrive-mutual-exclusion-2026-06-29.md | 7 ++ 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/REPORT.md b/REPORT.md index 3a47e8c..3b4415d 100644 --- a/REPORT.md +++ b/REPORT.md @@ -2,55 +2,43 @@ > **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md). -## App-email passthrough — hub leg (`POST /api/v1/mail` → Resend SMTP), hub v0.18.0 +## Agent capability-degraded operator alert — hub leg (HostCapabilityChecker), hub v0.19.0 -**Task:** SMTP app-relay (apps → on-box shim → hub → Resend). Implements -`documentation/audits/SPIKE-smtp-app-relay-2026-06-28.md` (verdict READY). +**Date:** 2026-06-29 · **Baseline:** hub `main` @ `1958204` (v0.18.0) → **v0.19.0** @ `b7b165b`; +manifest synced @ `2312572`. Companion: felhom-agent **v0.44.0**. -### Baseline (verified live) -- Hub `main` @ `4b97855`, version **v0.17.0** (the Resend-key rotation already shipped earlier today 2026-06-29) - → target **v0.18.0**. (The prompt assumed v0.16.0→v0.17.0; the rotation took v0.17.0 first, so this leg is - v0.18.0.) Prerequisite satisfied: Resend key is out-of-band in `Secret/resend-api`, read via `RESEND_API_KEY`. +### What & why +felhom-agent v0.44.0 now rides a `capabilities` snapshot on its host report (each required `sudo -n` +grant: ok/degraded). This is the hub leg: detect the ok→degraded transition and alert the **operator** +— closing the loop that let the 2026-06-28 non-root-cutover regressions go undetected until breakage. -### Files -- **Created** `internal/mailrelay/relay.go` — `ResendSMTP` (`Sender`): STARTTLS to `smtp.resend.com:587`, - `AUTH LOGIN resend/` (small stdlib `net/smtp.Auth` LOGIN impl), raw `MAIL`/`RCPT`/`DATA` **passthrough**. - `FromDomain` (From-header parser). **No new external dependency.** -- **Created** `internal/api/mail.go` — `handleMail`: `checkAuthCustomer` → From-domain allowlist (403 backstop) - → per-customer token-bucket rate limit (429) → passthrough to Resend (200 / 502). `SetMailRelay` wiring + - `mailRateLimiter`. -- **Modified** `internal/api/handler.go` — sender/limiter/allowlist fields + `POST /api/v1/mail` route. -- **Modified** `cmd/hub/main.go` — `MailConfig` (`per_customer_per_minute`, `from_domains`) + wire `ResendSMTP` - when a key is present (else 503). The `notify/dispatcher.go` HTTP-API alert path is **untouched**. +### Implementation +- **`monitor.HostCapabilityChecker` (NEW):** a deliberate sibling of `HostStalenessChecker` — same + per-host state map, seed-without-event, emit-only-on-transition. A host is `degraded` iff its latest + report has any **Critical** capability `status:"degraded"`; non-critical degradations never alert. + Runs on the existing 60s sweep alongside the staleness checkers. +- **Events:** `agent_capability_degraded` (warning, ok→degraded; names the degraded caps + gated + features in message + details JSON), `agent_capability_recovered` (info, degraded→ok). Routed via + the existing `Dispatcher.ProcessEvent` — **operator-only** (not a customer notification toggle, same + mechanism as `host_stale`) with the standard 1 h operator cooldown. +- **`store.GetHostCapabilities` (NEW):** reads the snapshot from the latest host-report's `report_json` + per host, keyed on `MAX(id)` (not `received_at` — within-second ties would return multiple rows). + **No schema migration.** A pre-v0.44.0 agent (no `capabilities`) reads as `ok` → no false alert. +- Cross-repo `host-report.golden.json` mirrors `capabilities: []` (byte-identical with the agent copy). -### Green gate (local) -`go build ./... && go vet ./... && go test ./...` — **PASS** (6 packages ok, 0 failures). +### Tests (all green: `go build/vet/test ./...`) +`TestHostCapabilityChecker` (seed no-event → ok→degraded ONE event → steady degraded none → +degraded→ok recovered), `…_NonCriticalIgnored`, `…_OldAgentNoCaps`. The golden contract test + +key-set tests pass with the mirrored field. -**Tests & §10 companion red-proofs** -- **Passthrough byte-equality (§7 A / §10):** `TestMail_HappyPath_PassthroughRawBytes` — the `Sender` receives - the raw bytes unchanged (not a parsed payload). PASS. -- **From-reject + companion (§7 B / §10):** `TestMail_FromOutsideAllowlist_Rejected_NoSend` (403, sender never - called) + `TestMail_FromReject_CompanionProof` (allowing the domain reaches the sender). PASS. -- **Per-box rate limit + isolation + companion (§7 C / §10):** `TestMail_RateLimit_PerCustomer` (429 on the 2nd - at 1/min; a different customer unaffected) + `TestMail_RateLimit_CompanionProof` (generous limit lets N+1 - through). PASS. -- Send-failure→502, 401/503/400 paths, token-bucket unit (injected clock), LOGIN auth + From-domain parse. PASS. +### Live validation (k3s felhom-system) — deployed + verified +- Built + pushed `felhom-hub:0.19.0` on 180; bumped `manifests/hub.yaml`; ArgoCD hard-refresh → sync → + rollout. **Synced / Healthy**, running image `…felhom-hub:0.19.0`. +- Startup: `felhom-hub 0.19.0 starting` + `Host capability checker initialized: 1 ok, 0 degraded` — + the checker is live and reading the demo host's snapshot (agent reports 40/40 ok). -### Deployment & live validation — DONE (2026-06-29) -- **Deployed** `felhom-hub:0.18.0`: built on 180 → bumped `manifests/hub.yaml` → ArgoCD `felhom` app hard-refresh - + sync (auto-sync off). Rollout OK; live image `…felhom-hub:0.18.0`; startup log: - `[INFO] App-email relay enabled (limit 30/min/customer, From domains [felhom.eu])`. The Resend key is injected - from `Secret/resend-api` (`RESEND_API_KEY`); no key in any committed file. -- **End-to-end (app → on-box shim → hub → Resend):** a raw MIME message with an inline CID image, From - `vaultwarden@felhom.eu`, was relayed from guest 9201's shim through this hub to the live Resend account. Hub - log: `/api/v1/mail: relayed for demo-felhom (from=vaultwarden@felhom.eu rcpts=1 bytes=789)`; the box's shim got - `hub=200` and returned `DATA 250 "OK: queued"` to the sender (Resend accepted/queued for the verified - `felhom.eu` domain). Operator inbox arrival is the final confirmation (sent to the operator's own address). -- **Security backstop (live):** a send From `evil@notfelhom.example` was rejected at the box's shim (`550`) before - ever reaching `/api/v1/mail` — the hub From-allowlist + rate-limit are the documented second backstop. - -### Observations -- App-relay is a **separate** code path from the hub's own structured alerts (which keep using the Resend - **HTTP API**) — raw passthrough is required because the API path silently drops inline CID images (spike §4). -- v1: single-shot, no spool, no idempotency key. v2 would add accept-and-spool + `Resend-Idempotency-Key`. -- Fleet free-tier ceiling is 100 emails/day. No secrets in any committed file. +### NOT yet live-validated (deferred) +The full live agent→hub→operator-email degraded transition: the hub alert fires only on a **Critical** +capability degraded, which on the live host would require removing a Critical grant (forbidden — would +re-break drives) or a separate test host, plus a real operator email. **Fully covered by the unit +tests above.** Deferred to a scratch-host exercise. diff --git a/documentation/audits/SPIKE-multidrive-mutual-exclusion-2026-06-29.md b/documentation/audits/SPIKE-multidrive-mutual-exclusion-2026-06-29.md index 3c06f15..7a9d1f2 100644 --- a/documentation/audits/SPIKE-multidrive-mutual-exclusion-2026-06-29.md +++ b/documentation/audits/SPIKE-multidrive-mutual-exclusion-2026-06-29.md @@ -209,3 +209,10 @@ live parent would orphan the working guest slave — fixed grant takes effect on boot). The §7 "guest_attached / pct config" note is **resolved**: `GuestConfig` is a Proxmox **API** call, not a sudo CLI command, so `guest_attached=false` is not a sudoers gap (and is benign in the intermediary model). + +**Durable countermeasure SHIPPED (2026-06-29):** the agent now self-probes its privileged +capabilities — felhom-agent **v0.44.0** `internal/capability` (build-time manifest⊆sudoers test + +runtime `sudo -n -l` probe → `HostReport.Capabilities`) + hub **v0.19.0** `HostCapabilityChecker` +(operator alert on a Critical grant going degraded). This exact incident's missing `lxc-info` grant +is now a CI build failure and a live `capabilities self-check` degraded signal, not a silent +multi-day regression. Live: agent reports 40/40 ok. See `felhom-agent/REPORT.md`.