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

@ -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 {