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:
parent
4882c2d47a
commit
7e83a66573
22 changed files with 501 additions and 74 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue