Skip to content

feat: implement 002 sidecar-push model (v0.2.0)#11

Merged
pofallon merged 5 commits into
mainfrom
002-laptop-push-secrets
May 31, 2026
Merged

feat: implement 002 sidecar-push model (v0.2.0)#11
pofallon merged 5 commits into
mainfrom
002-laptop-push-secrets

Conversation

@pofallon
Copy link
Copy Markdown
Contributor

Summary

  • Redesigns remo-broker from an external-backend model (fnox-core / AWS SDK, bootstrap token) to a purely in-memory sidecar-push daemon
  • Secrets are pushed into daemon memory by a sidecar devcontainer via push-creds over the admin socket; cleared with clear-creds
  • Wire protocol bumped to v2 (breaking: rotate-bootstrap removed, push-creds/clear-creds added)

Key changes

Core implementation

  • src/store.rs — new: InMemorySecretStore (lock-free ArcSwap<HashMap<String, SecretString>>)
  • src/proto/admin.rs — v2 wire types: PushCreds, ClearCreds, PushCredsResponse; StatusResponse gains secrets_loaded_at/secret_count
  • src/server.rsdispatch_push_creds (validate → atomic swap → cache flush → audit), dispatch_clear_creds, dispatch_get resolves against store
  • src/audit.rsSecretsPushed/SecretsCleared events (counts only, no values, FR-007)
  • src/config.rs, src/main.rs — stripped of all bootstrap/backend surface

Supply-chain / NFR wins

  • Deleted src/backend.rs, src/bootstrap.rs, Cross.toml
  • Removed fnox-core from Cargo.toml → no AWS SDK transitive tree
  • deny.toml advisories ignore list emptied (was 6 entries, all via fnox-core)
  • Binary: 2.1 MiB stripped (was ~32 MiB; NFR-001 target ≤15 MiB ✓)
  • CI: removed all libudev-dev installs; release.yml uses plain cargo + gcc-aarch64-linux-gnu
  • Systemd: removed LoadCredentialEncrypted= block

Schema

  • schema/remo-broker.v2.json — v2 wire schema generated by schema-gen feature
  • schema/remo-broker.v1.json — updated to tolerate per-secret fetch_as directive

Test plan

  • All CI jobs pass (fmt, clippy, test, deny, packaging, harnesses, benches, nfr)
  • cargo test — 128 tests pass
  • cargo test --features schema-gen — 130 tests pass (includes schema snapshot tests)
  • cargo deny check — green with empty ignore list
  • Release binary ≤ 15 MiB (currently 2.1 MiB)
  • Soak smoke: 500 ops, 0 errors, 0 panics
  • Audit log verified: secrets.pushed/secrets.cleared events carry counts only (no secret values)

🤖 Generated with Claude Code

pofallon and others added 5 commits May 30, 2026 13:13
After end-to-end testing of remo's 005-credential-broker (PR #32) on
2026-05-29, the bootstrap-token-on-instance design implemented in this
repo as v0.1.0 was identified as carrying a residual on-disk credential
that contradicts the supply-chain threat model the broker was built to
defend. Closed PR #32 in remo; superseding 001 here with 002.

The new design strips the entire external-backend integration:
  - delete src/backend.rs (114 LOC) and src/bootstrap.rs (819 LOC)
  - drop fnox-core dependency; this cascades to delete Cross.toml,
    empty deny.toml's [advisories].ignore (all 6 entries reach via
    fnox-core → AWS SDK), and shrink the binary from ~32 MiB toward
    the original 15 MiB NFR target
  - replace BackendSession with InMemorySecretStore populated from
    /var/lib/remo-broker/secrets.enc (age ciphertext), decrypted at
    startup using a key from \$CREDENTIALS_DIRECTORY/secrets-key
    (systemd LoadCredentialEncrypted=)
  - new admin ops: push-creds, clear-creds, get-public-key
  - remove rotate-bootstrap and BootstrapMode; wire protocol bumps
    to v2 per docs/wire-protocol.md §4 (removing ops/fields = breaking)
  - ship schema/remo-broker.v2.json as release artifact

~80% of the daemon chassis carries forward unchanged (proto framing,
manifest, registry, audit, cache, server lifecycle, systemd hardening).

Cross-repo: paired with remo spec 006-credential-broker-laptop-push
(landed on remo via separate PR).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirrors remo's 006 second pivot. The first 002 draft (2026-05-30,
laptop pushes age-encrypted blob; daemon decrypts at startup from
/var/lib/remo-broker/secrets.enc via systemd LoadCredentialEncrypted=)
is replaced by a much simpler model: the daemon is purely in-memory.

A sidecar devcontainer on the same LXC pushes plaintext to the broker's
admin socket whenever its fnox storage changes. Push is over a local
Unix socket — no network in transit, no encryption needed, no pubkey
trust. On broker restart, in-memory store is empty; sidecar re-pushes
as part of its own startup.

What disappears compared to the first 002 draft:
  - On-disk secrets.enc blob (no persistence in the broker)
  - LoadCredentialEncrypted= block in the systemd unit (broker loads
    no credential from systemd at startup)
  - age decrypt in the daemon (no encryption anywhere in the broker)
  - get-public-key admin op (no pubkey because no encryption)
  - Atomic write-to-tmp + fsync + rename dance (no on-disk blob)
  - The src/crypto.rs module entirely

What remains: the same chassis described in the first 002 draft
(~80% of v0.1.0 carries forward), plus:
  - src/store.rs — simple Arc<ArcSwap<HashMap<String, SecretString>>>
  - push-creds + clear-creds admin ops (plaintext input now)
  - AuditEvent::SecretsPushed / SecretsCleared
  - StatusResponse v2 (drops decryption_key_source — no key to source)
  - MAX_MESSAGE_BYTES raised to 1 MiB for push-creds (typical payload
    of ~10 secrets exceeds the v0.1.0 64 KiB cap)

Wire protocol still bumps to v2 (removing rotate-bootstrap and
bootstrap_mode are breaking per docs/wire-protocol.md §4).

Estimate down from ~7 days to ~5 days focused work.

Also updates specs/001-broker-daemon/spec.md status line to reflect
both pivots.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Redesign remo-broker from an external-backend credential broker (fnox-core /
AWS SDK, on-disk bootstrap token) into a purely in-memory secrets daemon.

**Architecture change**: secrets are pushed into the daemon's memory by a
sidecar devcontainer over the local admin socket (`push-creds`) and cleared
with `clear-creds`; project devcontainers continue to fetch via per-project
Unix sockets with manifest allowlist enforcement unchanged.

**Subtractive-first**: deleted backend.rs (~114 LOC) and bootstrap.rs
(~819 LOC); stripped all bootstrap/backend surface from config, server, and
main; added one small module (store.rs ~50 LOC, an ArcSwap<HashMap> wrapper),
two admin ops (push-creds, clear-creds), two audit events, and a reshaped
StatusResponse.

Key changes:
- src/store.rs: InMemorySecretStore (ArcSwap, atomic whole-map replace)
- src/proto/admin.rs: v2 wire types — PushCreds, ClearCreds, PushCredsResponse,
  InvalidPayload/PayloadTooLarge codes; StatusResponse drops bootstrap_mode,
  adds secrets_loaded_at/secret_count; rotate-bootstrap removed
- src/proto/mod.rs: PROTOCOL_VERSION 1→2; MAX_PUSH_CREDS_BYTES 1 MiB
- src/server.rs: dispatch_push_creds (validate → swap → flush caches → audit),
  dispatch_clear_creds, dispatch_get resolved against store (not_found when
  absent, D4), admin socket mode 0660 (FR-011)
- src/audit.rs: SecretsPushed / SecretsCleared events (counts only, FR-007)
- src/config.rs: stripped BootstrapSource/Kind and all bootstrap fields
- src/main.rs: minimal CLI (no bootstrap flags), starts with empty store
- Cargo.toml: removed fnox-core; bumped to 0.2.0
- Cross.toml: deleted (NFR-002 — aarch64 now via bare cargo + linker)
- deny.toml: advisories.ignore emptied (NFR-003 — all 6 entries were via fnox)
- .github/workflows: removed all libudev installs; release.yml uses plain
  cargo + gcc-aarch64-linux-gnu instead of cross-rs
- packaging/systemd/remo-broker.service: removed LoadCredentialEncrypted block
- src/manifest.rs: tolerate per-secret fetch_as directive (T029/D8)
- schema/remo-broker.v2.json: v2 wire schema emitted by schema-gen feature
- schema/remo-broker.v1.json: updated for fetch_as field in RawAllowlist
- benches/latency.rs, examples/soak.rs, examples/killtest.rs: rewritten for
  in-memory model (push-creds drives the workload)

Results:
- 128 tests pass (130 with --features schema-gen); RUSTFLAGS="-D warnings" clean
- Binary: 2.1 MiB stripped (NFR-001: ≤15 MiB; was ~32 MiB with fnox-core)
- cargo deny check: green with empty ignore list
- Soak smoke: 500 ops, 0 errors, 0 panics, 3 MiB RSS

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ci/measure_nfr.sh: remove --bootstrap-token-path and --fnox-config
  flags (removed in v0.2.0); daemon now starts with no flags and waits
  for push-creds from the sidecar; also promote binary size to NFR-001
  (enforced, since we now hit 2.1 MiB well under the 15 MiB target)
- ci.yml harnesses: add explicit `cargo build --release` step before
  building examples so the daemon binary exists at
  target/release/remo-broker when killtest runs (--examples alone does
  not build [[bin]] targets)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pofallon pofallon merged commit c2946ea into main May 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant