RAF: full docs pass — components, flows, security, scanners, reference, testing, verification, overview
This commit is contained in:
parent
565baa0819
commit
a19dcf4f14
34 changed files with 5085 additions and 28 deletions
56
RAF/components/04-helper.md
Normal file
56
RAF/components/04-helper.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Helper Component
|
||||
|
||||
**A privileged, network-less Rust executor that trusts nothing it didn't verify itself — the last gate before mutation.**
|
||||
|
||||
---
|
||||
|
||||
## Doctrine
|
||||
|
||||
The helper (`helper/src/main.rs`, a single ~2,000-line binary) is the only thing on a gated host allowed to mutate packages, and it earns that privilege by being structurally incapable of being talked into anything. It has no network stack in play, reads its trust inputs from root-owned pinned files, and performs exactly one operation per invocation — the one described by a validly signed capability token. Everything else is a typed denial.
|
||||
|
||||
Deny-by-default is the architecture, not a configuration: every failure path returns a `Denial` with a distinct exit code and a `log_security` entry. There is no flag that weakens verification. See [[security/05-supply-chain-gate]] for the token contract this enforces.
|
||||
|
||||
---
|
||||
|
||||
## Invocation
|
||||
|
||||
The agent invokes it via `sudo systemd-run --pipe --property=ProtectSystem=no` — a transient unit with full filesystem access, separate from the agent's own locked-down unit. The token arrives on stdin (or a file path for self-update flows). The agent holds zero install sudo; the sudoers file grants discovery commands plus this one invocation line. See [[components/02-agent]].
|
||||
|
||||
---
|
||||
|
||||
## The Verification Pipeline
|
||||
|
||||
`run()` executes, in order — any failure stops the world:
|
||||
|
||||
1. **Trust-input self-validation (SEC-021)** — `validate_trusted_path`: every file the helper relies on (keyring dir, agent-id file, state) must be root-owned, not a symlink, and not group/other-writable. The helper defends its own inputs instead of trusting that the installer set permissions correctly.
|
||||
2. **Keyring load** — pinned Ed25519 public keys from `/etc/redflag/trusted-keys`. Verify keys, not servers (load-bearing constraint #4).
|
||||
3. **Closure hash** — `closure_hash()` recomputes the canonical hash over the token's resolved closure. This must be **byte-identical** to the Go implementation; cross-language tests pin the contract ([[testing/01-test-pyramid]]).
|
||||
4. **Signature** — `verify_signature()` checks the token's Ed25519 signature over the signed message (which embeds the closure hash) against the keyring, by `key_id`.
|
||||
5. **Artifact hashes** — `verify_artifacts()` SHA-256s every artifact the token authorizes. A mismatch anywhere is a denial.
|
||||
6. **Replay check** — `replay_check_and_record()`: token IDs are recorded in local state; a token executes once.
|
||||
7. **Plan + execute** — `build_plan()` translates the token into the exact package-manager commands; `execute_plan()` runs them. No interpretation, no substitution.
|
||||
8. **Receipt** — `emit_result()` writes a `PolicyResult` the agent reports back; the server reconciles it into lifecycle state.
|
||||
|
||||
---
|
||||
|
||||
## Binary Self-Update Path
|
||||
|
||||
Agent, helper, and desktop binaries update through the same gate as packages: `stage_and_verify_binary` (hash check before anything moves) → `atomic_replace_binary` (rename, never write-in-place; failed swap leaves `<binary>.bak`). During agent upgrades, `reconcile_agent_unit_dropin()` heals fleet systemd units to the current template — this is how pre-`AmbientCapabilities` units get fixed without manual fleet surgery ([[deployment/01-docker-stack]]).
|
||||
|
||||
---
|
||||
|
||||
## Standalone Mint Mode
|
||||
|
||||
The helper also carries a local-authority minting path (`MintRequest` / `MintedToken` / `load_mint_key`) for deployments where the signing authority runs beside the host rather than on a central server. Design of record: [[security/06-standalone-authority]].
|
||||
|
||||
---
|
||||
|
||||
## Why a Separate Binary, Why Rust
|
||||
|
||||
- **Privilege separation:** the long-running, network-facing agent stays unprivileged; the privileged thing is short-lived, single-purpose, and offline.
|
||||
- **Can't be redirected:** no outbound capability means a compromised server can lie in a token — and the signature/hash checks catch that — but nothing can make the helper fetch from somewhere else.
|
||||
- **Small audit surface:** one file, explicit pipeline, typed denials. The binary is meant to be read.
|
||||
|
||||
---
|
||||
|
||||
*Last reviewed: 2026-06-11*
|
||||
Loading…
Reference in a new issue