spec/tasks: pull repo-credential plumbing into Phase 1

Adds P1-32/33/34: encrypted repo creds carried on the enrollment token,
agent-side AEAD secrets file, end-to-end smoke. spec.md §4.2 and §7.3
rewritten to describe the full flow (server-issued at token time,
pushed via config.update on hello, persisted encrypted on the agent)
and to make the encrypted-file-now / OS-keyring-Phase-2 split
explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 12:32:53 +01:00
parent 51bbb555d4
commit 8d8150ee6e
2 changed files with 51 additions and 4 deletions
+32 -4
View File
@@ -123,7 +123,14 @@ It is built for small-to-medium fleets (initial target: ~12 endpoints) and is in
- **Service integration:** systemd unit (Linux). Windows service via
`golang.org/x/sys/windows/svc` — Phase 2.
- **Footprint goal:** ≤ 15 MB binary, ≤ 50 MB RSS idle
- **Persistence:** local config file + small state DB (BoltDB or JSON) for queued reports if server is unreachable
- **Persistence:** `agent.yaml` (server URL, host ID, bearer, secrets
key) + an AEAD-encrypted secrets blob (`secrets.enc`) holding the
restic repo URL + password. Both files are mode 0600 owned by the
agent service user. Phase 1 ships the encrypted-file form on
Linux; Phase 2 swaps that for OS-keyring storage (DPAPI on Windows,
Secret Service / `pass` on Linux where a session bus is
available — see §7.3). A small state DB (BoltDB or JSON) for
queued reports lands when offline-resilience work does.
- **Restic invocation:** spawns `restic` with `--json`, parses streamed output, forwards to server in real time
- **Updates:** distributed via OS package manager — apt repo (Linux) and
Chocolatey package (Windows), both pointing at gitea releases. No
@@ -341,9 +348,30 @@ offline.
- **viewer:** read-only
### 7.3 Secret handling
- Restic repo passwords and REST-server credentials encrypted at rest in SQLite using a server-side key (loaded from env or file at startup)
- Pushed to agents only over the authenticated WS, only when needed for a job
- Agent stores them in OS keyring where available (Windows DPAPI, Linux Secret Service / fallback to encrypted file with restricted perms)
- Restic repo passwords and REST-server credentials encrypted at rest
in SQLite using a server-side key (loaded from env or file at
startup, AEAD via `internal/crypto`).
- Operator supplies repo URL + username + password when minting an
enrollment token. The token row holds them as a single encrypted
blob; on `ConsumeEnrollmentToken` the blob is moved to a
`host_credentials` row keyed by `host_id` (same tx).
- Pushed to agents over the authenticated WS as a `config.update`
message — sent immediately after the agent's `hello` on every
connect, and again whenever the operator edits the credential.
Agents that connect before credentials exist proceed normally
but refuse to start backup jobs until the push arrives.
- Agent persistence:
- **Phase 1, Linux:** AEAD-encrypted file at
`/var/lib/restic-manager/secrets.enc`, key stored in
`agent.yaml` alongside the bearer (same 0600 trust boundary).
Atomic writes (tmp+fsync+rename).
- **Phase 2:** OS keyring where available — Windows DPAPI; Linux
Secret Service via `pass` / `gnome-keyring` / `kwallet` when a
session bus is present. The encrypted-file path stays as the
fallback for headless boxes.
- Plaintext repo passwords never appear in `agent.yaml`, server logs,
audit-log payloads, or job-log streams. The audit log records
*that* a credential was set/changed and by whom, never the value.
### 7.4 Repo protection
- Restic REST server runs with `--append-only` for routine backups