Files
clawstor/claw-store/Cargo.toml
T
Omar Sobh 4d93f955c5 Phase 1c: QUIC transport with fleet-CA mTLS + cluster-ping
Wraps quinn 0.11 in cluster/transport.rs with rustls 0.23 + rcgen 0.13
for identity management. Every peer connection is mTLS: both sides must
present certs signed by the shared fleet CA, and rustls verifies the
peer's cert SAN matches the requested server name.

Public API on `cluster::transport`:
- `NodeIdentity` (cert chain + private key + trusted CA)
- `NodeIdentity::generate_test_pair(a, b)` — ephemeral CA + two signed
  leaves; shape matches the production PEM-file loader (Phase 1d)
- `QuicServer::bind(addr, identity)` — bind mTLS-enforced listener
- `QuicServer::accept()` — accept one connection (Option<Result<_>>)
- `QuicClient::new(local_addr, identity)` — build client endpoint
- `QuicClient::connect(peer_addr, expected_name)` — outbound with SAN check
- `ping(&conn, payload)` — bidi stream RPC; server echoes as `pong:<payload>`
- `ping_handler_loop(conn)` — server-side accept/echo forever
- `CLAWSTOR_RPC_ALPN` constant, single ALPN "clawstor-rpc/1"

Config extension:
- `bind_rpc_lan`, `bind_rpc_tailscale` on `ClusterConfig` (both optional).
- Defaults: gossip port + 1 (so gossip UDP and QUIC UDP don't collide).
- Helper: `ClusterConfig::rpc_lan()`, `rpc_tailscale()`,
  `PeerEntry::rpc_lan()`, `rpc_tailscale()`.

Gossip.rs now advertises the RPC address, not the gossip address, on
the well-known `clawstor.rpc.lan` / `clawstor.rpc.tailscale` keys.

CLI:
- `cluster-ping --name <me> --peer <you> --rpc-addr <addr> [--payload X]`
  Runs a full mTLS handshake and single ping. For dev/loopback use
  today (both sides need to share a CA); persistent-identity ping
  lands in Phase 1d.

Tests (6 new, real UDP + TLS handshake, no mocks):
- generate_test_pair produces two distinct leaves that share a CA
- ping_pong_between_two_mtls_peers: real 2-node QUIC round trip with
  full mTLS chain verification, `open_bi()`/`accept_bi()`, byte-exact
  response check
- client_rejects_peer_with_wrong_ca: TLS chain verification failure
  when the server presents a cert signed by a different CA
- client_rejects_wrong_server_name: SAN mismatch is enforced
- server_binds_wildcard_and_reports_concrete_local_addr: port 0 → real
- ping_rejects_oversize_payload: MAX_MESSAGE_BYTES cap enforced client-side

Fixed-port allocator range 42000+ so transport tests don't conflict
with gossip tests (41000+).

72 tests pass. Pre-existing `hot::test_project_target_size_bytes`
macOS-only failure unchanged.

File sizes (all under 1300-line ceiling):
- cluster/transport.rs: 485
- cluster/gossip.rs: 570
- cluster.rs: 275
- config.rs: 480
- main.rs: 564

Follow-on Phase 1 cut (1d):
- Load NodeIdentity from persistent PEM files at /etc/claw-store/tls/
- Fleet CA bootstrap ceremony (rcgen → write CA cert; per-node leaf CSR)
- Daemon-level RPC server that runs alongside gossip + serves real
  operations (blob get/put, metadata sync)
- cluster-status reads from live daemon via API instead of standalone
2026-07-11 21:50:28 -07:00

51 lines
2.0 KiB
TOML

[package]
name = "claw-store"
version = "0.3.0"
edition = "2021"
[[bin]]
name = "claw-store"
path = "src/main.rs"
[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
toml = "0.8"
tokio = { version = "1", features = ["full"] }
sysinfo = "0.30"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
chrono = { version = "0.4", features = ["serde"] }
axum = { version = "0.7", features = ["macros"] }
tower-http = { version = "0.5", features = ["cors", "fs"] }
tokio-stream = "0.1"
serde_json = "1"
# v0.2.0 — flock(2) wrapper for atomic+locked manifest writes
# (manifest.rs). Already a transitive dep; declaring it directly
# makes the call site obvious.
libc = "0.2"
# v0.2.0 — sibling-tempfile + rename for atomic manifest persistence.
# Was previously dev-dep only; promoted to main.
tempfile = "3"
# v0.11.1 — cluster membership via scuttlebutt gossip + phi-accrual failure
# detection. Wraps in cluster/gossip.rs. UDP transport, keyed KV state per
# node, seed_nodes bootstrap from [[cluster.peers]] config.
chitchat = "0.11"
# v0.11 — QUIC transport for peer RPC (Phase 1c). UDP-based; runs on a
# separate port from chitchat gossip. TLS 1.3 by default; we wire mTLS
# against a fleet root CA in cluster/transport.rs.
quinn = { version = "0.11", default-features = false, features = ["runtime-tokio", "rustls-ring"] }
# v0.23 — TLS 1.3 provider driving quinn's crypto. Pinned to the ring
# provider so a single CryptoProvider is installed process-wide.
rustls = { version = "0.23", default-features = false, features = ["ring"] }
# v0.13 — X.509 cert generation for the fleet CA + per-node leaf certs.
# Used both by production bootstrap (writes PEM to /etc/claw-store/tls/)
# and by tests (in-memory ephemeral CA).
rcgen = { version = "0.13", features = ["pem"] }
[dev-dependencies]
tempfile = "3"
tower = { version = "0.5", features = ["util"] }
http-body-util = "0.1"