Admit E3 and capacity on the test plane

Register in-process E3 and capacity fixtures, keep live database and
substrate targets pending, and ask ops-mason for namespace-only provision.
No packets, no credentials, no cancelled engagement IDs.

Assistant: grok
Assistant-Session: 01a02670-3345-76f2-a014-70fde8e2a2bb
This commit is contained in:
tegwick 2026-08-22 09:40:27 +02:00
parent 4882c2d47a
commit 7e83a66573
22 changed files with 501 additions and 74 deletions

View file

@ -9,7 +9,7 @@ from pathlib import Path
from .audit_fixtures import AuditFixture, audit_probe_suite
from .capacity import CapacitySample, characterize
from .differential import execute
from .e3 import CADENCE, PROBES
from .e3 import CADENCE, PROBES, e3_calibration
from .engagement import AuthorizationError, Engagement
from .fixtures import FixtureService, probe_suite
from .model import RunReport, utc_now
@ -95,6 +95,8 @@ def main(argv: list[str] | None = None) -> None:
deliver.add_argument("report")
deliver.add_argument("--outbox", default="outbox")
commands.add_parser("e3-plan")
e3_fix = commands.add_parser("e3-fixtures", help="calibrate E3 probes offline")
e3_fix.add_argument("--output")
commands.add_parser("capacity-fixture")
message = commands.add_parser("risk-message")
message.add_argument("report")
@ -164,6 +166,14 @@ def main(argv: list[str] | None = None) -> None:
print(json.dumps({"cadence": CADENCE, "probes": [asdict(probe) for probe in PROBES]},
indent=2, sort_keys=True))
return
if args.command == "e3-fixtures":
result = e3_calibration()
rendered = json.dumps(result, indent=2, sort_keys=True) + "\n"
if args.output:
Path(args.output).write_text(rendered, encoding="utf-8")
else:
print(rendered, end="")
raise SystemExit(0 if result["outcome"] == "pass" else 1)
if args.command == "capacity-fixture":
print(json.dumps(capacity_fixture(), indent=2, sort_keys=True))
return

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import Callable, Literal
from .model import Outcome
from .model import Outcome, utc_now
Expectation = Literal["zero_rows", "statement_rejected", "false", "documented_limit"]
Query = Callable[[str], object]
@ -49,6 +49,52 @@ def evaluate(probe: E3Probe, *, rows: int = 0, rejected: bool = False,
return E3Result(probe.probe_id, "finding", f"expectation failed: {probe.expectation}")
def fixture_results(*, enforce: bool) -> list[E3Result]:
"""In-process known-good/known-bad outcomes. No database connection."""
results: list[E3Result] = []
for probe in PROBES:
if probe.expectation == "documented_limit":
results.append(evaluate(probe))
continue
if enforce:
results.append(evaluate(probe, rows=0, rejected=True, boolean=False))
else:
results.append(evaluate(probe, rows=1, rejected=False, boolean=True))
return results
def e3_calibration() -> dict:
started = utc_now()
good = fixture_results(enforce=True)
bad = fixture_results(enforce=False)
def expected(result: E3Result, *, enforce: bool) -> bool:
probe = next(item for item in PROBES if item.probe_id == result.probe_id)
if probe.expectation == "documented_limit":
return result.outcome == "inconclusive"
return result.outcome == ("pass" if enforce else "finding")
ok = all(expected(item, enforce=True) for item in good) and all(
expected(item, enforce=False) for item in bad
)
return {
"schema_version": "whitehat-e3-calibration/v1",
"evidence_class": "fixture",
"run_id": f"e3-calibration-{started}",
"started_at": started,
"ended_at": utc_now(),
"outcome": "pass" if ok else "finding",
"cadence": CADENCE,
"known_good": [result.__dict__ for result in good],
"known_bad": [result.__dict__ for result in bad],
"limitations": [
"Offline E3 calibration evaluates the harness; it is not target assurance.",
"No database connection or live credential was used.",
"sql-compromise-reset is E3's documented limit and stays inconclusive.",
],
}
CADENCE = {
"interval": "24h",
"maximum_detection_window": "24h plus run and reporting latency",

View file

@ -19,9 +19,12 @@ TERMINAL_IDS = frozenset({
APPROVAL_CLASSES = {
"fixture-e2": frozenset({"e2-differential"}),
"live-e2": frozenset({"e2-differential"}),
"fixture-e3": frozenset({"e3-rls"}),
"e3": frozenset({"e3-rls"}),
"fixture-capacity": frozenset({"p1-noisy-neighbour", "p2-noisy-neighbour"}),
"capacity": frozenset({"p1-noisy-neighbour", "p2-noisy-neighbour"}),
}
FIXTURE_CLASSES = frozenset({"fixture-e2", "fixture-e3", "fixture-capacity"})
def repo_root() -> Path:
@ -102,7 +105,7 @@ class IdentityHandle:
class CredentialBroker(Protocol):
def project(self, engagement: Engagement, registration: dict[str, Any]
) -> tuple[IdentityHandle, IdentityHandle]:
) -> tuple[IdentityHandle, ...]:
...
def revoke(self, lease_id: str) -> None:
@ -113,7 +116,7 @@ class UnconnectedCustodyBroker:
"""Fail-closed live broker. It never requests or returns credential values."""
def project(self, engagement: Engagement, registration: dict[str, Any]
) -> tuple[IdentityHandle, IdentityHandle]:
) -> tuple[IdentityHandle, ...]:
raise AuthorizationError(
"live custody broker is not connected; no credential was requested"
)
@ -132,20 +135,25 @@ class LocalBroker:
self._leases: dict[str, tuple[str, str]] = {}
def project(self, engagement: Engagement, registration: dict[str, Any]
) -> tuple[IdentityHandle, IdentityHandle]:
) -> tuple[IdentityHandle, ...]:
if registration.get("applicability") != "applicable":
raise AuthorizationError("broker will not project identities for a non-applicable target")
count = int(registration["identities"]["count"])
lease_id = os.urandom(8).hex()
ttl = min(int(engagement.raw["credential_max_ttl_seconds"]), STANDING_E2_TTL)
expires_at = (datetime.now(UTC) + timedelta(seconds=ttl)).isoformat().replace("+00:00", "Z")
owner = IdentityHandle("owner", f"/var/run/secrets/whitehat/{lease_id}/token-b",
lease_id, expires_at)
attacker = IdentityHandle("attacker", f"/var/run/secrets/whitehat/{lease_id}/token-a",
lease_id, expires_at)
self._secrets[owner.mount_path] = os.urandom(32)
self._secrets[attacker.mount_path] = os.urandom(32)
self._leases[lease_id] = (owner.mount_path, attacker.mount_path)
return owner, attacker
handles: list[IdentityHandle] = []
if count >= 1:
role = "owner" if count == 2 else "runtime"
path = f"/var/run/secrets/whitehat/{lease_id}/{'token-b' if count == 2 else 'runtime'}"
handles.append(IdentityHandle(role, path, lease_id, expires_at))
self._secrets[path] = os.urandom(32)
if count == 2:
path = f"/var/run/secrets/whitehat/{lease_id}/token-a"
handles.append(IdentityHandle("attacker", path, lease_id, expires_at))
self._secrets[path] = os.urandom(32)
self._leases[lease_id] = tuple(handle.mount_path for handle in handles)
return tuple(handles)
def revoke(self, lease_id: str) -> None:
paths = self._leases.pop(lease_id, None)
@ -159,7 +167,7 @@ class LocalBroker:
class PlaneLease:
engagement: Engagement
registration: dict[str, Any]
identities: tuple[IdentityHandle, IdentityHandle]
identities: tuple[IdentityHandle, ...]
watcher: RateWatcher
lease_id: str
broker_name: str
@ -172,7 +180,7 @@ class PlaneLease:
def default_broker(engagement: Engagement) -> CredentialBroker:
if (engagement.raw.get("environment") == "fixture"
and engagement.raw.get("approval_class") == "fixture-e2"):
and engagement.raw.get("approval_class") in FIXTURE_CLASSES):
return LocalBroker()
return UnconnectedCustodyBroker()
@ -212,32 +220,26 @@ def admit(*, engagement: Engagement, registration: dict[str, Any],
for route in engagement.raw["routes"]:
if route not in registration["routes"]:
raise AuthorizationError(f"route not registered: {route}")
if approval_class == "live-e2":
if engagement.raw.get("plane_namespace") != PLANE_NAMESPACE:
raise AuthorizationError("live E2 requires the whitehat plane namespace")
if not engagement.raw.get("runner_image_digest"):
raise AuthorizationError("live E2 requires a pinned runner image digest")
if engagement.raw["credential_max_ttl_seconds"] > STANDING_E2_TTL:
raise AuthorizationError("E2 credential TTL must be at most 900 seconds")
if engagement.raw["environment"] == "fixture":
raise AuthorizationError("live E2 cannot use the fixture environment")
elif approval_class == "fixture-e2":
if engagement.raw["environment"] != "fixture":
raise AuthorizationError("fixture-e2 requires environment=fixture")
elif approval_class in {"e3", "capacity"}:
raise AuthorizationError(
f"{approval_class} requires a separate explicit operator window; "
"the E2 plane will not admit it"
)
_enforce_class_envelope(approval_class, engagement)
selected = broker or default_broker(engagement)
identities = selected.project(engagement, registration)
if len(identities) != 2:
raise AuthorizationError("broker must project exactly two identities")
roles = {handle.role for handle in identities}
if roles != {"owner", "attacker"}:
raise AuthorizationError("broker must project owner and attacker identities")
if any(hasattr(handle, "secret") and getattr(handle, "secret") for handle in identities):
raise AuthorizationError("broker exposed a credential value")
count = int(registration["identities"]["count"])
if count:
identities = selected.project(engagement, registration)
if len(identities) != count:
raise AuthorizationError(f"broker must project exactly {count} identities")
expected = {2: {"owner", "attacker"}, 1: {"runtime"}}[count]
if {handle.role for handle in identities} != expected:
raise AuthorizationError(f"broker must project {sorted(expected)} identities")
if any(hasattr(handle, "secret") and getattr(handle, "secret") for handle in identities):
raise AuthorizationError("broker exposed a credential value")
lease_id = identities[0].lease_id
else:
if approval_class not in FIXTURE_CLASSES:
selected.project(engagement, registration)
identities = ()
lease_id = os.urandom(8).hex()
if hasattr(selected, "_leases"):
selected._leases[lease_id] = ()
watcher = RateWatcher(
per_minute=engagement.raw["rate_limit_per_minute"],
max_concurrency=engagement.raw["max_concurrency"],
@ -249,11 +251,31 @@ def admit(*, engagement: Engagement, registration: dict[str, Any],
registration=registration,
identities=identities,
watcher=watcher,
lease_id=identities[0].lease_id,
lease_id=lease_id,
broker_name=type(selected).__name__,
)
def _enforce_class_envelope(approval_class: str, engagement: Engagement) -> None:
environment = engagement.raw["environment"]
if approval_class in FIXTURE_CLASSES:
if environment != "fixture":
raise AuthorizationError(f"{approval_class} requires environment=fixture")
return
if environment == "fixture":
raise AuthorizationError(f"{approval_class} cannot use the fixture environment")
if engagement.raw.get("plane_namespace") != PLANE_NAMESPACE:
raise AuthorizationError(f"{approval_class} requires the whitehat plane namespace")
if not engagement.raw.get("runner_image_digest"):
raise AuthorizationError(f"{approval_class} requires a pinned runner image digest")
if engagement.raw["credential_max_ttl_seconds"] > STANDING_E2_TTL:
raise AuthorizationError("credential TTL must be at most 900 seconds")
if approval_class == "e3" and not engagement.raw.get("database"):
raise AuthorizationError("live E3 requires a named database")
if approval_class == "capacity" and engagement.raw.get("aggressor_ceiling") is None:
raise AuthorizationError("live capacity requires an aggressor_ceiling")
def cleanup(lease: PlaneLease, broker: CredentialBroker) -> dict[str, str]:
broker.revoke(lease.lease_id)
return {

View file

@ -37,9 +37,15 @@ def load_registration(path: str | Path) -> dict[str, Any]:
if not data["routes"]:
raise AuthorizationError(f"{path}: applicable target must register routes")
identities = data["identities"]
if identities.get("count") != 2:
count = identities.get("count")
classes = set(data["approval_classes"])
if classes & {"fixture-e2", "live-e2"} and count != 2:
raise AuthorizationError(f"{path}: E2 registration must project two identities")
if identities.get("ttl_seconds", 0) > 900:
if classes & {"fixture-e3", "e3"} and count != 1:
raise AuthorizationError(f"{path}: E3 registration must project one runtime identity")
if classes & {"fixture-capacity", "capacity"} and count not in {0, 1}:
raise AuthorizationError(f"{path}: capacity registration projects at most one aggressor identity")
if count and identities.get("ttl_seconds", 0) > 900:
raise AuthorizationError(f"{path}: identity TTL must be at most 900 seconds")
if data.get("known_bad_calibration") in {None, "", "pending"}:
raise AuthorizationError(f"{path}: applicable target needs completed known-bad calibration")