R4 (e2e half): 3-hop nested-SSH circuit delivery + per-hop pcaps (gate item 1)

run_circuit_fixture stands up an isolated docker N-hop nested-SSH chain, pipes a
seed-deterministic self-generated payload through it, verifies end-to-end
delivery, captures + checksums a per-hop pcap, emits R3 events, and always tears
the circuit down. Containment is load-bearing: assert_isolated refuses
local/unknown engines up front, every hop is built through the ForwarderPlan
guard, only our own fixture bytes move between our own containers, and the runner
is wired for docker only (multipass e2e refused, not faked). Adds the sor-hop
fixture image (alpine + sshd + tcpdump, lab-relay only) and a docker-gated
acceptance test that skips where no daemon/image is present. Instrument-validation
gate item 1 GREEN: live 3-hop delivery verified, 3 distinct hops, pcaps checksum.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-19 17:08:26 -07:00
parent 92da24dfe2
commit a78441b938
3 changed files with 393 additions and 19 deletions
+260 -19
View File
@@ -1,30 +1,43 @@
"""R4 (partial) — containment guard for the SOR nested-SSH forwarder.
"""R4 — SSH-tunnel data plane (nested-circuit forwarder), isolated-engine-only.
This module lands **only instrument-validation gate item 5**: forwarders are
*isolated-engine-only*. It is the guard that every forwarder MUST pass before it
does anything — ``assert engine != local`` or refuse. It moves no traffic, opens
no socket, spawns no engine, and builds no SSH chain.
Two parts, both gated by the same containment invariant (``assert engine !=
local`` or refuse):
The traffic-moving half of R4 (gate item 1: a 3-hop self-traffic circuit across
the grid delivering a known payload e2e, with per-hop pcaps) is intentionally
**NOT** implemented here — it is HELD for a live isolated engine + grid (see
OVERSEER-STATUS.md). Committing an unrunnable forwarder would violate the build
discipline; this guard, by contrast, is fully verifiable offline: constructing a
forwarder plan for a ``local`` (or unknown) engine refuses, for an isolated
engine it yields the isolation exec prefix.
1. **The guard** (gate item 5): ``assert_isolated`` / ``isolation_prefix`` /
``ForwarderPlan`` — every forwarder must pass this before it does anything.
There is no code path here that returns an exec prefix for the host; ``local``
(the warned exception the *chat* sandbox allows, ``bridge.py:540``) is never
valid for a SOR run. This half is fully verifiable offline.
Containment law (CLAUDE.md §Containment #1) is encoded structurally: there is no
code path in this module that returns an exec prefix for the host. ``local`` —
the explicit warned exception the *chat* sandbox allows (``bridge.py:540``) — is
never valid for a SOR run. The allow-list is imported from ``provenance`` so the
forwarder and the run manifest can never disagree about what counts as isolated.
2. **The e2e circuit runner** (gate item 1): ``run_circuit_fixture`` stands up an
``N``-hop nested-SSH chain of isolated **docker** containers, pipes a
seed-deterministic *self-generated* payload through it, captures a per-hop
pcap, verifies end-to-end delivery, checksums each pcap, emits R3 events, and
tears the circuit down. It moves only fixture traffic between our own
containers on our own engine — no external target, no real user data, no
forwarder on the host.
Containment (CLAUDE.md §Containment) is load-bearing: the runner refuses any
non-isolated engine up front, uses the docker control plane only to manage
containers (the forwarder *processes* — ssh/tcpdump — run inside containers, not
on the host), and always tears the circuit down. The isolated-engine allow-list
is imported from ``provenance`` so the forwarder and the run manifest can never
disagree about what counts as isolated.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import List
import hashlib
import json
import shutil
import subprocess
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional
from cmd_chat.sor.config import Domain, SorRng
from cmd_chat.sor.events import EventLog
from cmd_chat.sor.provenance import ISOLATED_ENGINES
# The host. Never a valid engine for a SOR forwarder — its whole purpose is to
@@ -98,3 +111,231 @@ class ForwarderPlan:
def exec_prefix(self) -> List[str]:
"""The isolated exec argv prefix for this plan (validated at build time)."""
return isolation_prefix(self.engine, self.container)
# --------------------------------------------------------------------------- #
# The e2e circuit runner (gate item 1).
#
# Stands up an N-hop nested-SSH chain of isolated docker containers, pipes a
# seed-deterministic self-generated payload through it, captures a per-hop pcap,
# verifies end-to-end delivery, checksums each pcap, emits R3 events, and tears
# the circuit down. Every container it creates is a ForwarderPlan-gated hop; the
# host (`local`) is refused up front. Traffic is our own fixture bytes moving
# between our own containers on our own engine — no external target, no real
# user data, no forwarder process on the host.
# --------------------------------------------------------------------------- #
# The fixture relay image (built from fixtures/hop.Dockerfile): alpine + sshd +
# openssh-client + tcpdump. Only ever used as an isolated lab relay for
# self-traffic; it never runs on the host.
HOP_IMAGE = "sor-hop:latest"
# ssh hardening applied inside the *client* container only (never the host): the
# lab hops use throwaway host keys, so the client accepts-on-first-use and keeps
# no known_hosts. This is a containment-internal convenience, not a security
# posture we ship to anyone.
_CLIENT_SSH_CONFIG = (
"Host *\n"
" StrictHostKeyChecking accept-new\n"
" UserKnownHostsFile /dev/null\n"
" LogLevel ERROR\n"
)
class CircuitError(RuntimeError):
"""The e2e circuit could not be stood up, delivered, or verified. Raised
(never silently swallowed) so a failed run is loud; teardown still runs."""
def _docker(*args: str, check: bool = True, capture: bool = True,
input_bytes: Optional[bytes] = None, timeout: int = 120) -> subprocess.CompletedProcess:
"""Run a single `docker ...` control-plane command. Args are passed as
separate argv items (never a shell string) so container/circuit names can't
inject shell metacharacters."""
return subprocess.run(
["docker", *args],
check=check,
capture_output=capture,
input=input_bytes,
timeout=timeout,
)
@dataclass
class CircuitResult:
"""The verifiable outcome of one e2e fixture run (all offline-checkable)."""
run_id: str
seed: int
engine: str
delivered: bool
payload_sha256: str
received_sha256: str
hop_containers: List[str]
pcaps: Dict[int, Path] = field(default_factory=dict)
pcap_sha256: Dict[int, str] = field(default_factory=dict)
events_sha256: Optional[str] = None
def _seed_payload(seed: int, size: int = 4096) -> bytes:
"""A self-generated, seed-deterministic payload (no real data). Drawn from
the R1 PADDING stream so the same seed yields the same bytes on every run."""
stream = SorRng(seed).stream(Domain.PADDING)
return bytes(stream.next_below(256) for _ in range(size))
def run_circuit_fixture(
seed: int,
*,
engine: str = "docker",
hops: int = 3,
out_root: Optional[Path] = None,
payload_size: int = 4096,
keep: bool = False,
) -> CircuitResult:
"""Drive instrument-validation gate item 1: an ``hops``-hop nested-SSH circuit
of isolated containers delivers a seed-deterministic payload end-to-end, with
a per-hop pcap captured + checksummed and R3 events emitted.
Containment (load-bearing):
* ``assert_isolated(engine)`` up front — ``local`` and unknown engines are
refused before any container is created; only ``docker`` is wired for e2e.
* every hop is built through :class:`ForwarderPlan`, so each exec target is
re-validated as isolated.
* only self-generated fixture bytes move, between our own containers, and
the circuit is always torn down (``finally``) unless ``keep=True``.
Returns a :class:`CircuitResult`; raises :class:`CircuitError` on any failure
(teardown still runs). Requires a live docker daemon and the ``sor-hop`` image
— callers/tests that lack them should skip."""
assert_isolated(engine)
if engine != "docker":
# multipass e2e is not built; refuse rather than pretend.
raise CircuitError(
f"containment: e2e circuit runner only wired for docker, not {engine!r}"
)
if shutil.which("docker") is None:
raise CircuitError("docker control plane not found on PATH")
if hops < 3:
raise CircuitError(f"gate item 1 requires >=3 hops, got {hops}")
run_id = f"sorfix-{seed:016x}-{int(time.time())}"
out_root = Path(out_root) if out_root else Path("output/sor-runs")
run_dir = out_root / run_id
pcap_dir = run_dir / "pcap"
pcap_dir.mkdir(parents=True, exist_ok=True)
net = f"sorfix-net-{run_id}"
client = f"sorfix-client-{run_id}"
hop_names = [f"sorfix-hop{i}-{run_id}" for i in range(hops)]
hop_alias = [f"hop{i}" for i in range(hops)]
payload = _seed_payload(seed, payload_size)
payload_sha = hashlib.sha256(payload).hexdigest()
log = EventLog(run_dir, run_id, seed)
result = CircuitResult(
run_id=run_id,
seed=seed,
engine=engine,
delivered=False,
payload_sha256=payload_sha,
received_sha256="",
hop_containers=list(hop_names),
)
created: List[str] = []
net_created = False
try:
# 1) Isolated user-defined network so hops resolve each other by alias.
_docker("network", "create", net)
net_created = True
# 2) Bring up client + hop containers (each hop is a ForwarderPlan-gated
# isolated target; building the plan re-asserts containment).
for name, alias in [(client, "client"), *zip(hop_names, hop_alias)]:
plan = ForwarderPlan(engine=engine, container=name) # re-validates isolation
_docker(
"run", "-d", "--rm",
"--name", name,
"--hostname", alias,
"--network", net,
"--network-alias", alias,
"--cap-add", "NET_RAW", # tcpdump; present by default but explicit
HOP_IMAGE,
)
created.append(name)
# Sanity: the plan's exec prefix targets this isolated container.
assert plan.exec_prefix()[:3] == ["docker", "exec", "-i"]
# 3) Client keypair + accept-on-first-use ssh config (client only).
_docker("exec", client, "sh", "-c",
"ssh-keygen -t ed25519 -N '' -f /root/.ssh/id_ed25519 -q")
_docker("exec", "-i", client, "sh", "-c",
"cat > /root/.ssh/config && chmod 600 /root/.ssh/config",
input_bytes=_CLIENT_SSH_CONFIG.encode())
pub = _docker("exec", client, "cat", "/root/.ssh/id_ed25519.pub").stdout
# 4) Authorize the client key on every hop.
for name in hop_names:
_docker("exec", "-i", name, "sh", "-c",
"cat >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys",
input_bytes=pub)
log.emit("circuit_build", circuit_id=run_id, hop_index=hops)
# 5) Start a per-hop pcap (adjacent-hop SSH ciphertext only — the nested
# onion property). Detached so capture spans the transfer.
for i, name in enumerate(hop_names):
_docker("exec", "-d", name, "sh", "-c",
"tcpdump -i eth0 -w /cap.pcap 'tcp port 22' >/dev/null 2>&1")
log.emit("hop_add", node_fp=hop_alias[i].encode().hex()[:8],
circuit_id=run_id, hop_index=i, bytes_=payload_size)
time.sleep(1.0) # let tcpdump bind before traffic
# 6) Stage payload in the client, push it through the nested-SSH chain to
# the final hop, read it back. ProxyJump nests hop0->hop1->...->hopN-1.
_docker("exec", "-i", client, "sh", "-c",
"cat > /tmp/payload", input_bytes=payload)
jumps = ",".join(f"root@{a}" for a in hop_alias[:-1])
final = f"root@{hop_alias[-1]}"
t0 = time.time()
_docker("exec", client, "sh", "-c",
f"ssh -J {jumps} {final} 'cat > /tmp/recv' < /tmp/payload")
recv_sha = _docker("exec", hop_names[-1], "sha256sum", "/tmp/recv").stdout
latency_ms = (time.time() - t0) * 1000.0
received_sha = recv_sha.decode().split()[0]
result.received_sha256 = received_sha
result.delivered = received_sha == payload_sha
log.emit("bridge_forward", circuit_id=run_id, hop_index=hops - 1,
bytes_=payload_size, latency_ms=latency_ms,
decision="delivered" if result.delivered else "corrupt")
if not result.delivered:
raise CircuitError(
f"e2e delivery mismatch: sent {payload_sha[:12]} got {received_sha[:12]}"
)
# 7) Stop capture, copy each pcap out, checksum it (write-once artifact).
for i, name in enumerate(hop_names):
_docker("exec", name, "sh", "-c", "pkill tcpdump || true", check=False)
time.sleep(0.5)
for i, name in enumerate(hop_names):
dst = pcap_dir / f"hop{i}.pcap"
_docker("cp", f"{name}:/cap.pcap", str(dst))
result.pcaps[i] = dst
result.pcap_sha256[i] = hashlib.sha256(dst.read_bytes()).hexdigest()
result.events_sha256 = log.close()
return result
except subprocess.CalledProcessError as exc: # noqa: PERF203
stderr = exc.stderr.decode(errors="replace") if exc.stderr else ""
raise CircuitError(f"docker control-plane step failed: {exc} {stderr}") from exc
finally:
if not log._closed:
log.close()
if not keep:
for name in created:
_docker("rm", "-f", name, check=False, timeout=60)
if net_created:
_docker("network", "rm", net, check=False, timeout=60)