Files
pulse/scripts/release_control/secure_runtime_rootless_attestation_v1.py
T
2026-09-01 21:26:49 +01:00

910 lines
49 KiB
Python

#!/usr/bin/env python3
"""Validate the secret-free opt-in rootless runtime qualification receipt.
This validator deliberately makes no claim about published release artifacts or
whether the safe collector profile is suitable as a default. It validates one
local, artifact-bound qualification receipt for both Docker and Podman.
"""
from __future__ import annotations
import argparse
import contextlib
import hashlib
import json
import math
import os
import re
import stat
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, NoReturn
RECEIPT_SCHEMA_VERSION = 1
ATTESTATION_SCHEMA_VERSION = 1
RECEIPT_KIND = "pulse-secure-runtime-rootless-qualification"
ATTESTATION_KIND = "pulse-secure-runtime-rootless-receipt-attestation"
CLASSIFICATION = "local-opt-in-rootless-runtime-artifact-bound-self-attestation"
MAX_RECEIPT_BYTES = 2 * 1024 * 1024
MAX_ARTIFACT_BYTES = 256 * 1024 * 1024
SOURCE_MANIFEST_SCHEMA_VERSION = 1
SOURCE_MANIFEST_ID = "secure-runtime-rootless-v1"
SOURCE_MANIFEST_PATH = "scripts/release_control/secure_runtime_rootless_source_manifest_v1.json"
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
COMMIT_RE = re.compile(r"^[0-9a-f]{40}$")
GO_VERSION_RE = re.compile(r"^go1\.[0-9]+(?:\.[0-9]+)?$")
IDENTITY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,255}$")
VERSION_RE = re.compile(r"^v?[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$")
TIMESTAMP_RE = re.compile(
r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?Z$"
)
SENSITIVE_KEY_PARTS = frozenset(
{
"authorization",
"bearer",
"cookie",
"credential",
"credentials",
"password",
"passwd",
"privatekey",
"secret",
"secrets",
"token",
"tokens",
}
)
SENSITIVE_VALUE_PATTERNS = (
re.compile(r"-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----"),
re.compile(r"(?i)(?:^|\s)(?:bearer|basic)\s+[A-Za-z0-9+/=_-]{8,}"),
re.compile(r"^[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}$"),
re.compile(r"^[a-z][a-z0-9+.-]*://[^/@\s:]+:[^/@\s]+@", re.IGNORECASE),
)
REQUIRED_RUNTIMES = ("docker", "podman")
REQUIRED_SCENARIOS = (
"fresh_install",
"legacy_migration",
"collector_restart",
"daemon_restart",
"socket_loss_helper_fallback",
"direct_recovery",
"dual_socket_ambiguity_refusal",
"exact_pin_recovery",
"telemetry_parity",
"authority_isolation",
"cleanup",
)
class ValidationError(ValueError):
"""The receipt is malformed or does not prove the required contract."""
def fail(message: str) -> NoReturn:
raise ValidationError(message)
def require(condition: bool, message: str) -> None:
if not condition:
fail(message)
def require_object(value: Any, label: str, keys: set[str]) -> dict[str, Any]:
require(isinstance(value, dict), f"{label} must be an object")
actual = set(value)
require(actual == keys, f"{label} fields differ: missing={sorted(keys - actual)} extra={sorted(actual - keys)}")
return value
def require_bool(value: Any, expected: bool, label: str) -> None:
require(type(value) is bool and value is expected, f"{label} must be {str(expected).lower()}")
def require_int(value: Any, label: str, *, minimum: int = 0) -> int:
require(type(value) is int and value >= minimum, f"{label} must be an integer >= {minimum}")
return value
def require_text(
value: Any,
label: str,
*,
pattern: re.Pattern[str] | None = None,
maximum: int = 256,
) -> str:
require(isinstance(value, str) and 0 < len(value) <= maximum, f"{label} must be non-empty text <= {maximum} bytes")
require("\x00" not in value and "\n" not in value and "\r" not in value, f"{label} contains control text")
if pattern is not None:
require(pattern.fullmatch(value) is not None, f"{label} has an invalid format")
return value
def require_digest(value: Any, label: str, *, allow_empty: bool = False) -> str:
if allow_empty and value == "":
return ""
return require_text(value, label, pattern=SHA256_RE, maximum=64)
def parse_timestamp(value: Any, label: str) -> datetime:
text = require_text(value, label, pattern=TIMESTAMP_RE, maximum=40)
try:
parsed = datetime.fromisoformat(text[:-1] + "+00:00")
except ValueError as exc:
raise ValidationError(f"{label} is not a valid UTC timestamp") from exc
require(parsed.tzinfo == timezone.utc, f"{label} must be UTC")
return parsed
def reject_sensitive_evidence(value: Any, path: str = "receipt") -> None:
if isinstance(value, dict):
for key, child in value.items():
require(isinstance(key, str), f"{path} contains a non-string key")
if path != "receipt.source_hashes":
normalized = re.sub(r"[^a-z0-9]", "", key.lower())
segmented = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", key)
parts = {part.lower() for part in re.split(r"[^A-Za-z0-9]+", segmented) if part}
require(
normalized not in SENSITIVE_KEY_PARTS and not parts.intersection(SENSITIVE_KEY_PARTS),
f"{path}.{key} is a forbidden sensitive key",
)
reject_sensitive_evidence(child, f"{path}.{key}")
elif isinstance(value, list):
for index, child in enumerate(value):
reject_sensitive_evidence(child, f"{path}[{index}]")
elif isinstance(value, str):
for pattern in SENSITIVE_VALUE_PATTERNS:
require(pattern.search(value) is None, f"{path} contains secret-like evidence")
elif isinstance(value, float):
require(math.isfinite(value), f"{path} contains a non-finite number")
def expected_socket_path(runtime: str, uid: int) -> str:
if runtime == "docker":
return f"/run/user/{uid}/docker.sock"
return f"/run/user/{uid}/podman/podman.sock"
DIRECT_KEYS = {
"collector_pid",
"service_pid",
"collection_path",
"inventory_complete",
"inventory_count",
"semantic_sha256",
"full_fields_present",
"stats_present",
"secondary_structure_sha256",
"daemon_id",
"daemon_rootless",
"socket_path",
"socket_uid",
"socket_gid",
"socket_mode",
"socket_type",
"socket_symlink",
}
def validate_socket_mode(value: Any, label: str) -> str:
mode = require_text(value, label, pattern=re.compile(r"^0[0-7]{3}$"), maximum=4)
numeric_mode = int(mode, 8)
require(numeric_mode & 0o600 == 0o600, f"{label} must grant owner read and write")
require(numeric_mode & 0o006 == 0, f"{label} must not grant world read or write")
return mode
def validate_socket_identity(value: Any, label: str, runtime: str, uid: int) -> dict[str, Any]:
socket = require_object(
value,
label,
{"runtime", "path", "uid", "gid", "mode", "type", "symlink"},
)
require(socket["runtime"] == runtime, f"{label}.runtime must be {runtime}")
require(socket["path"] == expected_socket_path(runtime, uid), f"{label}.path is not the exact rootless path")
require(socket["uid"] == uid, f"{label}.uid must match the collector")
require_int(socket["gid"], f"{label}.gid", minimum=1)
validate_socket_mode(socket["mode"], f"{label}.mode")
require(socket["type"] == "unix", f"{label}.type must be unix")
require_bool(socket["symlink"], False, f"{label}.symlink")
return socket
def validate_direct_evidence(
evidence: Any,
label: str,
runtime_result: dict[str, Any],
*,
expected_collector_pid: int | None = None,
expected_daemon_id: str | None = None,
) -> dict[str, Any]:
item = require_object(evidence, label, DIRECT_KEYS)
collector_pid = require_int(item["collector_pid"], f"{label}.collector_pid", minimum=2)
require_int(item["service_pid"], f"{label}.service_pid", minimum=2)
require(item["service_pid"] == collector_pid, f"{label} collector and service PID must match")
if expected_collector_pid is not None:
require(collector_pid == expected_collector_pid, f"{label} unexpectedly restarted the collector")
require(item["collection_path"] == "collector-owned-rootless-socket", f"{label}.collection_path differs")
require_bool(item["inventory_complete"], True, f"{label}.inventory_complete")
require_int(item["inventory_count"], f"{label}.inventory_count", minimum=1)
for name in ("semantic_sha256", "secondary_structure_sha256"):
require_digest(item[name], f"{label}.{name}")
require_bool(item["full_fields_present"], True, f"{label}.full_fields_present")
require_bool(item["stats_present"], True, f"{label}.stats_present")
daemon_id = require_text(item["daemon_id"], f"{label}.daemon_id", pattern=IDENTITY_RE)
if expected_daemon_id is not None:
require(daemon_id == expected_daemon_id, f"{label} has the wrong daemon identity")
require_bool(item["daemon_rootless"], True, f"{label}.daemon_rootless")
require(item["socket_path"] == runtime_result["socket_path"], f"{label} used a different socket path")
require(item["socket_uid"] == runtime_result["socket_uid"], f"{label} used a different socket owner")
require(item["socket_gid"] == runtime_result["socket_gid"], f"{label} used a different socket group")
require(item["socket_mode"] == runtime_result["socket_mode"], f"{label} used a different socket mode")
require(item["socket_type"] == "unix", f"{label}.socket_type must be unix")
require_bool(item["socket_symlink"], False, f"{label}.socket_symlink")
return item
def validate_scenario(
scenario: Any,
expected_name: str,
label: str,
receipt_start: datetime,
receipt_end: datetime,
) -> tuple[dict[str, Any], datetime, datetime]:
item = require_object(
scenario,
label,
{"name", "result", "started_at", "completed_at", "report_stream_id", "report_sequence", "evidence"},
)
require(item["name"] == expected_name, f"{label}.name must be {expected_name}")
require(item["result"] == "passed", f"{label}.result must be passed")
started = parse_timestamp(item["started_at"], f"{label}.started_at")
completed = parse_timestamp(item["completed_at"], f"{label}.completed_at")
require(receipt_start <= started < completed <= receipt_end, f"{label} chronology is outside the receipt window")
non_reporting = expected_name in {"dual_socket_ambiguity_refusal", "authority_isolation", "cleanup"}
if non_reporting:
require(item["report_stream_id"] is None, f"{label}.report_stream_id must be null")
require(item["report_sequence"] is None, f"{label}.report_sequence must be null")
else:
require_text(item["report_stream_id"], f"{label}.report_stream_id", pattern=IDENTITY_RE)
require_int(item["report_sequence"], f"{label}.report_sequence", minimum=1)
return item, started, completed
def stable_direct_parity(left: dict[str, Any], right: dict[str, Any], label: str) -> None:
for name in ("inventory_count", "semantic_sha256", "secondary_structure_sha256"):
require(left[name] == right[name], f"{label} did not retain {name} parity")
require_bool(right["full_fields_present"], True, f"{label}.full_fields_present")
require_bool(right["stats_present"], True, f"{label}.stats_present")
def validate_run(run: Any, expected_runtime: str, run_index: int, receipt_start: datetime, receipt_end: datetime) -> dict[str, Any]:
label = f"runs[{run_index}]"
run = require_object(run, label, {"host", "runtime", "scenarios"})
host = require_object(run["host"], f"{label}.host", {"machine_id", "architecture", "kernel", "systemd_version"})
require_text(host["machine_id"], f"{label}.host.machine_id", pattern=IDENTITY_RE)
require(host["architecture"] in {"amd64", "arm64"}, f"{label}.host.architecture is unsupported")
require_text(host["kernel"], f"{label}.host.kernel", maximum=128)
require_text(host["systemd_version"], f"{label}.host.systemd_version", maximum=128)
runtime = require_object(
run["runtime"], f"{label}.runtime",
{"runtime", "runtime_version", "daemon_id", "collector_uid", "socket_path", "socket_uid", "socket_gid", "socket_mode", "socket_type", "socket_symlink", "daemon_rootless"},
)
require(runtime["runtime"] == expected_runtime, f"{label}.runtime.runtime must be {expected_runtime}")
require_text(runtime["runtime_version"], f"{label}.runtime.runtime_version", pattern=VERSION_RE, maximum=128)
require_text(runtime["daemon_id"], f"{label}.runtime.daemon_id", pattern=IDENTITY_RE)
uid = require_int(runtime["collector_uid"], f"{label}.runtime.collector_uid", minimum=1)
socket_path = expected_socket_path(expected_runtime, uid)
require(runtime["socket_path"] == socket_path, f"{label}.runtime.socket_path must be {socket_path}")
require(runtime["socket_uid"] == uid, f"{label}.runtime.socket_uid must match collector_uid")
require_int(runtime["socket_gid"], f"{label}.runtime.socket_gid", minimum=1)
validate_socket_mode(runtime["socket_mode"], f"{label}.runtime.socket_mode")
require(runtime["socket_type"] == "unix", f"{label}.runtime.socket_type must be unix")
require_bool(runtime["socket_symlink"], False, f"{label}.runtime.socket_symlink")
require_bool(runtime["daemon_rootless"], True, f"{label}.runtime.daemon_rootless")
scenarios = run["scenarios"]
require(isinstance(scenarios, list) and len(scenarios) == len(REQUIRED_SCENARIOS), f"{label}.scenarios must contain exactly eleven scenarios")
parsed: dict[str, dict[str, Any]] = {}
previous_completed: datetime | None = None
for index, name in enumerate(REQUIRED_SCENARIOS):
scenario, started, completed = validate_scenario(scenarios[index], name, f"{label}.scenarios[{index}]", receipt_start, receipt_end)
if previous_completed is not None:
require(previous_completed <= started, f"{label}.scenarios are not chronological")
previous_completed = completed
parsed[name] = scenario
fresh = validate_direct_evidence(parsed["fresh_install"]["evidence"], f"{label}.fresh_install", runtime)
migration_raw = require_object(
parsed["legacy_migration"]["evidence"], f"{label}.legacy_migration",
DIRECT_KEYS | {"legacy_profile", "target_profile", "authority_reduced", "legacy_collector_pid"},
)
require(migration_raw["legacy_profile"] == "root-command-capable", f"{label}.legacy_migration legacy profile differs")
require(migration_raw["target_profile"] == "typed-helper-monitoring-only", f"{label}.legacy_migration target profile differs")
require_bool(migration_raw["authority_reduced"], True, f"{label}.legacy_migration.authority_reduced")
legacy_collector_pid = require_int(migration_raw["legacy_collector_pid"], f"{label}.legacy_migration.legacy_collector_pid", minimum=2)
migration = validate_direct_evidence({key: migration_raw[key] for key in DIRECT_KEYS}, f"{label}.legacy_migration.direct", runtime)
require(migration["collector_pid"] != legacy_collector_pid, f"{label}.legacy_migration did not replace the legacy collector")
stable_direct_parity(fresh, migration, f"{label}.legacy_migration")
restart_raw = require_object(
parsed["collector_restart"]["evidence"], f"{label}.collector_restart",
DIRECT_KEYS | {"previous_collector_pid", "previous_report_stream_id"},
)
require(restart_raw["previous_collector_pid"] == migration["collector_pid"], f"{label}.collector_restart predecessor differs")
require(restart_raw["previous_report_stream_id"] == parsed["legacy_migration"]["report_stream_id"], f"{label}.collector_restart predecessor stream differs")
restart = validate_direct_evidence({key: restart_raw[key] for key in DIRECT_KEYS}, f"{label}.collector_restart.direct", runtime)
require(restart["collector_pid"] != migration["collector_pid"], f"{label}.collector_restart did not change PID")
stable_direct_parity(fresh, restart, f"{label}.collector_restart")
fresh_stream = parsed["fresh_install"]["report_stream_id"]
migration_stream = parsed["legacy_migration"]["report_stream_id"]
restart_stream = parsed["collector_restart"]["report_stream_id"]
require(len({fresh_stream, migration_stream, restart_stream}) == 3, f"{label} install/migration/restart streams are not distinct")
require(parsed["fresh_install"]["report_sequence"] >= 1, f"{label}.fresh_install sequence is invalid")
require(parsed["legacy_migration"]["report_sequence"] >= 1, f"{label}.legacy_migration sequence is invalid")
daemon_raw = require_object(
parsed["daemon_restart"]["evidence"], f"{label}.daemon_restart",
DIRECT_KEYS | {"previous_daemon_pid", "daemon_pid", "previous_daemon_invocation_id", "daemon_invocation_id"},
)
daemon = validate_direct_evidence({key: daemon_raw[key] for key in DIRECT_KEYS}, f"{label}.daemon_restart.direct", runtime, expected_collector_pid=restart["collector_pid"], expected_daemon_id=runtime["daemon_id"])
previous_daemon_pid = require_int(daemon_raw["previous_daemon_pid"], f"{label}.daemon_restart.previous_daemon_pid", minimum=2)
daemon_pid = require_int(daemon_raw["daemon_pid"], f"{label}.daemon_restart.daemon_pid", minimum=2)
require(previous_daemon_pid != daemon_pid, f"{label}.daemon_restart did not change daemon PID")
previous_invocation = require_text(daemon_raw["previous_daemon_invocation_id"], f"{label}.daemon_restart.previous_daemon_invocation_id", pattern=IDENTITY_RE)
invocation = require_text(daemon_raw["daemon_invocation_id"], f"{label}.daemon_restart.daemon_invocation_id", pattern=IDENTITY_RE)
require(previous_invocation != invocation, f"{label}.daemon_restart did not change InvocationID")
stable_direct_parity(fresh, daemon, f"{label}.daemon_restart")
fallback_label = f"{label}.socket_loss_helper_fallback"
fallback = require_object(
parsed["socket_loss_helper_fallback"]["evidence"], fallback_label,
{"collector_pid", "collection_mode", "direct_runtime_available", "helper_fallback", "inventory_complete", "inventory_count", "rootful_baseline_inventory_count", "semantic_sha256", "rootful_baseline_semantic_sha256", "full_fields_present", "stats_present", "secondary_structure_sha256", "container_actions_enabled", "container_updates_enabled", "collector_restart_count"},
)
require(fallback["collector_pid"] == restart["collector_pid"], f"{fallback_label} restarted collector")
require(fallback["collection_mode"] == "typed-helper-summary", f"{fallback_label} did not use helper summary")
require_bool(fallback["direct_runtime_available"], False, f"{fallback_label}.direct_runtime_available")
require_bool(fallback["helper_fallback"], True, f"{fallback_label}.helper_fallback")
require_bool(fallback["inventory_complete"], True, f"{fallback_label}.inventory_complete")
require_int(fallback["inventory_count"], f"{fallback_label}.inventory_count", minimum=1)
require(fallback["inventory_count"] == fallback["rootful_baseline_inventory_count"], f"{fallback_label} rootful count parity differs")
require_digest(fallback["semantic_sha256"], f"{fallback_label}.semantic_sha256")
require(fallback["semantic_sha256"] == fallback["rootful_baseline_semantic_sha256"], f"{fallback_label} semantic parity differs")
require_bool(fallback["full_fields_present"], False, f"{fallback_label}.full_fields_present")
require(fallback["secondary_structure_sha256"] == "", f"{fallback_label}.secondary_structure_sha256 must be empty")
require_bool(fallback["stats_present"], False, f"{fallback_label}.stats_present")
for name in ("container_actions_enabled", "container_updates_enabled"):
require_bool(fallback[name], False, f"{fallback_label}.{name}")
require(fallback["collector_restart_count"] == 0, f"{fallback_label} restarted collector")
recovery = validate_direct_evidence(parsed["direct_recovery"]["evidence"], f"{label}.direct_recovery", runtime, expected_collector_pid=restart["collector_pid"], expected_daemon_id=runtime["daemon_id"])
stable_direct_parity(fresh, recovery, f"{label}.direct_recovery")
for name in ("collector_restart", "daemon_restart", "socket_loss_helper_fallback", "direct_recovery"):
require(parsed[name]["report_stream_id"] == restart_stream, f"{label}.{name} changed report stream")
sequences = [parsed[name]["report_sequence"] for name in ("collector_restart", "daemon_restart", "socket_loss_helper_fallback", "direct_recovery")]
require(all(left < right for left, right in zip(sequences, sequences[1:])), f"{label} post-restart report sequence is not monotonic")
ambiguity_label = f"{label}.dual_socket_ambiguity_refusal"
ambiguity = require_object(
parsed["dual_socket_ambiguity_refusal"]["evidence"], ambiguity_label,
{"protected_collector_pid", "live_sockets", "probe_kind", "admission_refused", "fail_closed", "daemon_probe_count", "container_actions_enabled", "collector_restart_count"},
)
require(ambiguity["protected_collector_pid"] == restart["collector_pid"], f"{ambiguity_label} changed protected collector PID")
require(ambiguity["probe_kind"] == "separate-unpinned-collector", f"{ambiguity_label}.probe_kind differs")
live_sockets = ambiguity["live_sockets"]
require(isinstance(live_sockets, list) and len(live_sockets) == 2, f"{ambiguity_label}.live_sockets must contain two sockets")
for index, runtime_name in enumerate(REQUIRED_RUNTIMES):
validate_socket_identity(live_sockets[index], f"{ambiguity_label}.live_sockets[{index}]", runtime_name, uid)
require_bool(ambiguity["admission_refused"], True, f"{ambiguity_label}.admission_refused")
require_bool(ambiguity["fail_closed"], True, f"{ambiguity_label}.fail_closed")
require(ambiguity["daemon_probe_count"] == 0, f"{ambiguity_label} probed daemon")
require_bool(ambiguity["container_actions_enabled"], False, f"{ambiguity_label}.container_actions_enabled")
require(ambiguity["collector_restart_count"] == 0, f"{ambiguity_label} restarted collector")
pin_label = f"{label}.exact_pin_recovery"
pin_raw = require_object(
parsed["exact_pin_recovery"]["evidence"], pin_label,
DIRECT_KEYS | {"previous_collector_pid", "previous_report_stream_id", "pin_source", "pinned_socket_path", "socket_absent_observed", "fallback_report_sequence", "recovery_report_sequence", "recovered_socket_path", "selected_socket_path", "recovered_socket_uid", "recovered_socket_gid", "recovered_socket_mode", "recovered_socket_type", "recovered_socket_symlink", "candidate_count", "daemon_probe_count", "collector_restart_count"},
)
require(pin_raw["previous_collector_pid"] == restart["collector_pid"], f"{pin_label} predecessor PID differs")
require(pin_raw["previous_report_stream_id"] == restart_stream, f"{pin_label} predecessor stream differs")
pin = validate_direct_evidence({key: pin_raw[key] for key in DIRECT_KEYS}, f"{pin_label}.direct", runtime, expected_daemon_id=runtime["daemon_id"])
require(pin["collector_pid"] != restart["collector_pid"], f"{pin_label} update did not restart collector")
pin_stream = parsed["exact_pin_recovery"]["report_stream_id"]
require(pin_stream != restart_stream, f"{pin_label} did not create a new report stream")
require(pin_raw["pin_source"] == "root-owned-systemd-unit", f"{pin_label} pin source differs")
for name in ("pinned_socket_path", "recovered_socket_path", "selected_socket_path"):
require(pin_raw[name] == socket_path, f"{pin_label}.{name} differs")
require_bool(pin_raw["socket_absent_observed"], True, f"{pin_label}.socket_absent_observed")
require(pin_raw["recovered_socket_uid"] == uid and pin_raw["recovered_socket_gid"] == runtime["socket_gid"], f"{pin_label} recovered owner differs")
require(pin_raw["recovered_socket_mode"] == runtime["socket_mode"], f"{pin_label} recovered mode differs")
require(pin_raw["recovered_socket_type"] == "unix", f"{pin_label}.recovered_socket_type must be unix")
require_bool(pin_raw["recovered_socket_symlink"], False, f"{pin_label}.recovered_socket_symlink")
fallback_sequence = require_int(pin_raw["fallback_report_sequence"], f"{pin_label}.fallback_report_sequence", minimum=1)
recovery_sequence = require_int(pin_raw["recovery_report_sequence"], f"{pin_label}.recovery_report_sequence", minimum=1)
require(fallback_sequence < recovery_sequence <= parsed["exact_pin_recovery"]["report_sequence"], f"{pin_label} sequence causality differs")
require(pin_raw["candidate_count"] == 1 and pin_raw["daemon_probe_count"] == 1, f"{pin_label} did not select/probe exactly one endpoint")
require(pin_raw["collector_restart_count"] == 1, f"{pin_label} must record exactly one update restart")
stable_direct_parity(fresh, pin, pin_label)
parity_label = f"{label}.telemetry_parity"
parity = require_object(
parsed["telemetry_parity"]["evidence"], parity_label,
{"collector_pid", "baseline_kind", "baseline_inventory_count", "collector_inventory_count", "baseline_semantic_sha256", "collector_semantic_sha256", "collector_full_fields_present", "collector_stats_present", "collector_secondary_inventory_present"},
)
require(parity["collector_pid"] == pin["collector_pid"], f"{parity_label} changed collector PID")
require(parity["baseline_kind"] == "root-client-same-rootless-daemon", f"{parity_label} baseline differs")
baseline_count = require_int(parity["baseline_inventory_count"], f"{parity_label}.baseline_inventory_count", minimum=1)
collector_count = require_int(parity["collector_inventory_count"], f"{parity_label}.collector_inventory_count", minimum=1)
require(baseline_count == collector_count == fresh["inventory_count"], f"{parity_label} count differs")
baseline_semantic = require_digest(parity["baseline_semantic_sha256"], f"{parity_label}.baseline_semantic_sha256")
collector_semantic = require_digest(parity["collector_semantic_sha256"], f"{parity_label}.collector_semantic_sha256")
require(baseline_semantic == collector_semantic == fresh["semantic_sha256"], f"{parity_label} semantic inventory differs")
for name in ("collector_full_fields_present", "collector_stats_present", "collector_secondary_inventory_present"):
require_bool(parity[name], True, f"{parity_label}.{name}")
require(parsed["telemetry_parity"]["report_stream_id"] == pin_stream, f"{parity_label} changed updated stream")
require(parsed["telemetry_parity"]["report_sequence"] > parsed["exact_pin_recovery"]["report_sequence"], f"{parity_label} sequence is not monotonic")
authority_label = f"{label}.authority_isolation"
authority = require_object(
parsed["authority_isolation"]["evidence"], authority_label,
{"collector_pid", "collector_uid", "effective_uid", "effective_root", "safe_profile_enabled", "commands_enabled", "privileged_helper_enabled", "reduction_request_observed", "collector_command_transport_present", "collector_command_session_present", "container_actions_enabled", "container_updates_enabled", "rootful_socket_access", "helper_network_access"},
)
require(authority["collector_pid"] == pin["collector_pid"], f"{authority_label} changed collector PID")
require(authority["collector_uid"] == uid == authority["effective_uid"], f"{authority_label} UID binding differs")
require_bool(authority["effective_root"], False, f"{authority_label}.effective_root")
for name in ("safe_profile_enabled", "privileged_helper_enabled", "reduction_request_observed"):
require_bool(authority[name], True, f"{authority_label}.{name}")
for name in ("commands_enabled", "collector_command_transport_present", "collector_command_session_present", "container_actions_enabled", "container_updates_enabled", "rootful_socket_access", "helper_network_access"):
require_bool(authority[name], False, f"{authority_label}.{name}")
cleanup = require_object(parsed["cleanup"]["evidence"], f"{label}.cleanup", {"runtime_stopped", "socket_absent", "fixtures_removed", "user_state_clean"})
for name in cleanup:
require_bool(cleanup[name], True, f"{label}.cleanup.{name}")
return run
def validate_receipt(receipt: Any) -> dict[str, Any]:
reject_sensitive_evidence(receipt)
root = require_object(
receipt,
"receipt",
{
"schema_version",
"kind",
"result",
"source_commit",
"started_at",
"completed_at",
"artifacts",
"runs",
"source_hashes",
},
)
require(root["schema_version"] == RECEIPT_SCHEMA_VERSION, "receipt.schema_version must be 1")
require(root["kind"] == RECEIPT_KIND, f"receipt.kind must be {RECEIPT_KIND}")
require(root["result"] == "passed", "receipt.result must be passed")
source_commit = require_text(root["source_commit"], "receipt.source_commit", pattern=COMMIT_RE, maximum=40)
started = parse_timestamp(root["started_at"], "receipt.started_at")
completed = parse_timestamp(root["completed_at"], "receipt.completed_at")
require(started < completed, "receipt chronology is invalid")
artifacts = require_object(root["artifacts"], "receipt.artifacts", {"qualification_test", "collector", "helper", "installer"})
expected_basenames = {"qualification_test": "dockeragent.test", "collector": "pulse-agent", "helper": "pulse-agent-helper"}
expected_packages = {
"qualification_test": "github.com/rcourtman/pulse-go-rewrite/scripts/installtests.test",
"collector": "github.com/rcourtman/pulse-go-rewrite/cmd/pulse-agent",
"helper": "github.com/rcourtman/pulse-go-rewrite/cmd/pulse-agent-helper",
}
for name, basename in expected_basenames.items():
artifact = require_object(artifacts[name], f"receipt.artifacts.{name}", {"path_basename", "sha256", "package", "go_version", "vcs_revision", "vcs_modified"})
require(artifact["path_basename"] == basename, f"receipt.artifacts.{name}.path_basename differs")
require_digest(artifact["sha256"], f"receipt.artifacts.{name}.sha256")
require(artifact["package"] == expected_packages[name], f"receipt.artifacts.{name}.package differs")
require_text(artifact["go_version"], f"receipt.artifacts.{name}.go_version", pattern=GO_VERSION_RE, maximum=32)
require(artifact["vcs_revision"] == source_commit, f"receipt.artifacts.{name}.vcs_revision differs from source_commit")
require_bool(artifact["vcs_modified"], False, f"receipt.artifacts.{name}.vcs_modified")
installer = require_object(artifacts["installer"], "receipt.artifacts.installer", {"path_basename", "sha256"})
require(installer["path_basename"] == "install.sh", "receipt.artifacts.installer.path_basename differs")
require_digest(installer["sha256"], "receipt.artifacts.installer.sha256")
source_hashes = root["source_hashes"]
require(isinstance(source_hashes, dict) and source_hashes, "receipt.source_hashes must be a non-empty object")
for source_path, digest in source_hashes.items():
require_text(source_path, "receipt.source_hashes path", maximum=512)
require(
source_path == Path(source_path).as_posix()
and not source_path.startswith("/")
and ".." not in Path(source_path).parts,
"receipt.source_hashes contains a non-canonical path",
)
require_digest(digest, f"receipt.source_hashes[{source_path}]")
require(source_hashes.get("scripts/install.sh") == installer["sha256"], "installer digest must equal governed scripts/install.sh")
runs = root["runs"]
require(isinstance(runs, list) and len(runs) == 2, "receipt.runs must contain two disposable hosts")
validated = [validate_run(runs[index], runtime, index, started, completed) for index, runtime in enumerate(REQUIRED_RUNTIMES)]
require(validated[0]["host"]["machine_id"] != validated[1]["host"]["machine_id"], "Docker and Podman runs must use distinct hosts")
require(validated[0]["runtime"]["daemon_id"] != validated[1]["runtime"]["daemon_id"], "Docker and Podman daemon identities must differ")
docker_stream = validated[0]["scenarios"][REQUIRED_SCENARIOS.index("collector_restart")]["report_stream_id"]
podman_stream = validated[1]["scenarios"][REQUIRED_SCENARIOS.index("collector_restart")]["report_stream_id"]
require(docker_stream != podman_stream, "Docker and Podman report streams must differ")
socket_profiles = {current["runtime"]["runtime"]: current["runtime"] for current in validated}
for current in validated:
uid = current["runtime"]["collector_uid"]
expected_live_sockets = [
{
"runtime": runtime,
"path": expected_socket_path(runtime, uid),
"uid": uid,
"gid": socket_profiles[runtime]["socket_gid"],
"mode": socket_profiles[runtime]["socket_mode"],
"type": "unix",
"symlink": False,
}
for runtime in REQUIRED_RUNTIMES
]
ambiguity = current["scenarios"][REQUIRED_SCENARIOS.index("dual_socket_ambiguity_refusal")]["evidence"]
require(ambiguity["live_sockets"] == expected_live_sockets, "dual-socket evidence differs from run-local socket identities")
return root
def _reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
fail(f"receipt contains duplicate JSON key {key!r}")
result[key] = value
return result
def parse_receipt_bytes(data: bytes) -> dict[str, Any]:
require(len(data) <= MAX_RECEIPT_BYTES, "receipt exceeds the maximum size")
try:
text = data.decode("utf-8")
except UnicodeDecodeError as exc:
raise ValidationError("receipt is not UTF-8") from exc
try:
parsed = json.loads(
text,
object_pairs_hook=_reject_duplicate_keys,
parse_constant=lambda value: fail(f"receipt contains invalid number {value}"),
)
except json.JSONDecodeError as exc:
raise ValidationError(f"receipt is not valid JSON: {exc}") from exc
validate_receipt(parsed)
return parsed
def read_immutable_receipt(path: Path) -> bytes:
flags = os.O_RDONLY
if hasattr(os, "O_CLOEXEC"):
flags |= os.O_CLOEXEC
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
if hasattr(os, "O_NONBLOCK"):
flags |= os.O_NONBLOCK
try:
descriptor = os.open(path, flags)
except OSError as exc:
raise ValidationError(f"unable to open receipt: {exc}") from exc
try:
before = os.fstat(descriptor)
require(stat.S_ISREG(before.st_mode), "receipt must be a regular file")
require(before.st_size <= MAX_RECEIPT_BYTES, "receipt exceeds the maximum size")
chunks: list[bytes] = []
remaining = MAX_RECEIPT_BYTES + 1
while remaining:
chunk = os.read(descriptor, min(1024 * 1024, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
data = b"".join(chunks)
require(len(data) <= MAX_RECEIPT_BYTES, "receipt exceeds the maximum size")
after = os.fstat(descriptor)
require(
(before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns)
== (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns),
"receipt changed while it was being read",
)
require(len(data) == before.st_size, "receipt read was incomplete")
return data
finally:
os.close(descriptor)
def _canonical_relative_path(value: Any, label: str) -> str:
text = require_text(value, label, maximum=512)
candidate = Path(text)
require(
not candidate.is_absolute()
and candidate.as_posix() == text
and text not in {"", "."}
and ".." not in candidate.parts,
f"{label} is not a canonical repository-relative path",
)
return text
def load_source_manifest(checkout: Path, manifest_path: Path) -> tuple[bytes, dict[str, str]]:
manifest_bytes = read_immutable_receipt(manifest_path)
try:
manifest = json.loads(manifest_bytes.decode("utf-8"), object_pairs_hook=_reject_duplicate_keys)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ValidationError(f"source manifest is invalid JSON: {exc}") from exc
manifest = require_object(
manifest,
"source manifest",
{"schema_version", "manifest_id", "target_os", "description", "exact_paths", "recursive_roots", "include_suffixes", "exclude_suffixes"},
)
require(manifest["schema_version"] == SOURCE_MANIFEST_SCHEMA_VERSION, "source manifest schema differs")
require(manifest["manifest_id"] == SOURCE_MANIFEST_ID, "source manifest ID differs")
require(manifest["target_os"] == "linux", "source manifest target_os must be linux")
require_text(manifest["description"], "source manifest description", maximum=1024)
for field in ("exact_paths", "recursive_roots", "include_suffixes", "exclude_suffixes"):
require(isinstance(manifest[field], list) and all(isinstance(item, str) for item in manifest[field]), f"source manifest {field} must be a text array")
require(len(manifest[field]) == len(set(manifest[field])), f"source manifest {field} contains duplicates")
require(manifest["exact_paths"], "source manifest exact_paths must not be empty")
include_suffixes = tuple(manifest["include_suffixes"])
exclude_suffixes = tuple(manifest["exclude_suffixes"])
require(include_suffixes and all(suffix.startswith(".") for suffix in include_suffixes), "source manifest include_suffixes are invalid")
require(all(suffix.startswith("_") or suffix.startswith(".") for suffix in exclude_suffixes), "source manifest exclude_suffixes are invalid")
paths: set[str] = set()
for raw_path in manifest["exact_paths"]:
paths.add(_canonical_relative_path(raw_path, "source manifest exact path"))
for raw_root in manifest["recursive_roots"]:
root_text = _canonical_relative_path(raw_root, "source manifest recursive root")
root = checkout / root_text
require(root.is_dir() and not root.is_symlink(), f"source manifest recursive root is not a regular directory: {root_text}")
for candidate in root.rglob("*"):
if candidate.is_dir():
continue
relative = candidate.relative_to(checkout).as_posix()
if candidate.name.endswith(exclude_suffixes):
continue
if not candidate.name.endswith(include_suffixes):
continue
paths.add(relative)
hashes: dict[str, str] = {}
checkout_resolved = checkout.resolve(strict=True)
for relative in sorted(paths):
candidate = checkout / relative
try:
metadata = candidate.lstat()
except OSError as exc:
raise ValidationError(f"unable to inspect governed source {relative}: {exc}") from exc
require(stat.S_ISREG(metadata.st_mode), f"governed source is not a regular file: {relative}")
require(not stat.S_ISLNK(metadata.st_mode), f"governed source is a symlink: {relative}")
resolved = candidate.resolve(strict=True)
require(resolved.is_relative_to(checkout_resolved), f"governed source escapes checkout: {relative}")
source_bytes = read_immutable_receipt(candidate)
hashes[relative] = hashlib.sha256(source_bytes).hexdigest()
require(hashes, "source manifest expands to no source files")
return manifest_bytes, hashes
def verify_source_commit(checkout: Path, source_commit: str, source_hashes: dict[str, str]) -> None:
def git(*arguments: str) -> bytes:
try:
result = subprocess.run(
["git", *arguments],
cwd=checkout,
check=True,
capture_output=True,
)
except (OSError, subprocess.CalledProcessError) as exc:
detail = exc.stderr.decode("utf-8", errors="replace").strip() if isinstance(exc, subprocess.CalledProcessError) else str(exc)
raise ValidationError(f"unable to verify receipt source commit: {detail}") from exc
return result.stdout
top_level = Path(git("rev-parse", "--show-toplevel").decode("utf-8").strip()).resolve(strict=True)
require(top_level == checkout, "source checkout is not the requested repository root")
resolved = git("rev-parse", "--verify", f"{source_commit}^{{commit}}").decode("ascii").strip()
require(resolved == source_commit, "receipt source_commit is not an exact commit")
for relative, expected_digest in source_hashes.items():
source_bytes = git("show", f"{source_commit}:{relative}")
require(
hashlib.sha256(source_bytes).hexdigest() == expected_digest,
f"governed source {relative} does not match receipt source_commit",
)
@contextlib.contextmanager
def immutable_artifact_snapshot(path: Path, expected_basename: str, *, executable: bool) -> Any:
require(path.name == expected_basename, f"artifact path basename must be {expected_basename}")
flags = os.O_RDONLY
if hasattr(os, "O_CLOEXEC"):
flags |= os.O_CLOEXEC
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
if hasattr(os, "O_NONBLOCK"):
flags |= os.O_NONBLOCK
try:
descriptor = os.open(path, flags)
except OSError as exc:
raise ValidationError(f"unable to open artifact {expected_basename}: {exc}") from exc
try:
before = os.fstat(descriptor)
require(stat.S_ISREG(before.st_mode), f"artifact {expected_basename} must be a regular file")
require(0 < before.st_size <= MAX_ARTIFACT_BYTES, f"artifact {expected_basename} size is invalid")
if executable:
require(before.st_mode & 0o111 != 0, f"artifact {expected_basename} is not executable")
with tempfile.TemporaryDirectory(prefix="pulse-rootless-artifact-") as temporary:
snapshot = Path(temporary) / expected_basename
target = os.open(snapshot, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o700 if executable else 0o600)
digest = hashlib.sha256()
copied = 0
try:
while True:
chunk = os.read(descriptor, 1024 * 1024)
if not chunk:
break
copied += len(chunk)
require(copied <= MAX_ARTIFACT_BYTES, f"artifact {expected_basename} exceeds the maximum size")
digest.update(chunk)
offset = 0
while offset < len(chunk):
offset += os.write(target, chunk[offset:])
os.fsync(target)
finally:
os.close(target)
after = os.fstat(descriptor)
require(
(before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns)
== (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns),
f"artifact {expected_basename} changed while being snapshotted",
)
require(copied == before.st_size, f"artifact {expected_basename} snapshot is incomplete")
yield snapshot, digest.hexdigest()
finally:
os.close(descriptor)
def inspect_go_build_identity(artifact: Path) -> dict[str, Any]:
try:
result = subprocess.run(
["go", "version", "-m", str(artifact)],
check=True,
capture_output=True,
timeout=15,
)
except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
detail = ""
if isinstance(exc, subprocess.CalledProcessError):
detail = exc.stderr.decode("utf-8", errors="replace").strip()
raise ValidationError(f"unable to inspect Go artifact build identity: {detail or exc}") from exc
require(len(result.stdout) <= 256 * 1024, "Go artifact metadata is unexpectedly large")
try:
lines = result.stdout.decode("utf-8").splitlines()
except UnicodeDecodeError as exc:
raise ValidationError("Go artifact metadata is not UTF-8") from exc
require(lines and ": " in lines[0], "Go artifact metadata header is malformed")
go_version = lines[0].rsplit(": ", 1)[1]
require_text(go_version, "Go artifact go_version", pattern=GO_VERSION_RE, maximum=32)
settings: dict[str, str] = {}
package = ""
for line in lines[1:]:
fields = line.strip().split("\t")
if len(fields) == 2 and fields[0] == "path":
require(not package, "Go artifact metadata contains duplicate package path")
package = fields[1]
continue
if len(fields) != 2 or fields[0] != "build" or "=" not in fields[1]:
continue
key, value = fields[1].split("=", 1)
require(key not in settings, f"Go artifact metadata contains duplicate build setting {key}")
settings[key] = value
require(settings.get("vcs") == "git", "Go artifact is not git VCS stamped")
require_text(package, "Go artifact package", maximum=256)
revision = require_text(settings.get("vcs.revision"), "Go artifact vcs.revision", pattern=COMMIT_RE, maximum=40)
require(settings.get("vcs.modified") == "false", "Go artifact is modified or lacks vcs.modified=false")
return {"package": package, "go_version": go_version, "vcs_revision": revision, "vcs_modified": False}
def create_attestation(
receipt_path: Path,
qualification_test_path: Path,
collector_path: Path,
helper_path: Path,
installer_path: Path,
*,
checkout: Path | None = None,
manifest_path: Path | None = None,
verify_git_commit: bool = True,
) -> dict[str, Any]:
data = read_immutable_receipt(receipt_path)
receipt = parse_receipt_bytes(data)
checkout = (checkout or Path(__file__).resolve().parents[2]).resolve(strict=True)
manifest_path = manifest_path or checkout / SOURCE_MANIFEST_PATH
manifest_bytes, source_hashes = load_source_manifest(checkout, manifest_path)
require(receipt["source_hashes"] == source_hashes, "receipt source_hashes do not match the canonical source manifest")
if verify_git_commit:
verify_source_commit(checkout, receipt["source_commit"], source_hashes)
supplied_artifacts = {
"qualification_test": (qualification_test_path, "dockeragent.test", True),
"collector": (collector_path, "pulse-agent", True),
"helper": (helper_path, "pulse-agent-helper", True),
"installer": (installer_path, "install.sh", False),
}
artifact_bindings: dict[str, dict[str, Any]] = {}
with contextlib.ExitStack() as stack:
snapshots: dict[str, Path] = {}
for name, (path, basename, executable) in supplied_artifacts.items():
snapshot, artifact_digest = stack.enter_context(
immutable_artifact_snapshot(path, basename, executable=executable)
)
require(artifact_digest == receipt["artifacts"][name]["sha256"], f"{name} bytes do not match receipt SHA-256")
snapshots[name] = snapshot
artifact_bindings[name] = {"path_basename": basename, "sha256": artifact_digest}
for name in ("qualification_test", "collector", "helper"):
build = inspect_go_build_identity(snapshots[name])
claimed = receipt["artifacts"][name]
require(build["package"] == claimed["package"], f"{name} Go package differs from receipt")
require(build["go_version"] == claimed["go_version"], f"{name} Go version differs from receipt")
require(build["vcs_revision"] == claimed["vcs_revision"] == receipt["source_commit"], f"{name} VCS revision differs")
require_bool(build["vcs_modified"], False, f"{name} vcs.modified")
artifact_bindings[name].update(build)
validator_path = Path(__file__).absolute()
validator_bytes = read_immutable_receipt(validator_path)
return {
"schema_version": ATTESTATION_SCHEMA_VERSION,
"kind": ATTESTATION_KIND,
"classification": CLASSIFICATION,
"receipt_sha256": hashlib.sha256(data).hexdigest(),
"validator_source_sha256": hashlib.sha256(validator_bytes).hexdigest(),
"source_manifest_schema_version": SOURCE_MANIFEST_SCHEMA_VERSION,
"source_manifest_id": SOURCE_MANIFEST_ID,
"source_manifest_sha256": hashlib.sha256(manifest_bytes).hexdigest(),
"source_hash_count": len(source_hashes),
"source_commit": receipt["source_commit"],
"source_commit_verified": verify_git_commit,
"artifact_bindings": artifact_bindings,
"validated_runtimes": list(REQUIRED_RUNTIMES),
"limitations": [
"not-published-release-provenance",
"not-default-profile-authorization",
"not-independent-security-review",
"production-exact-scope-proof-is-external-prior",
],
}
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("receipt", type=Path, help="schema-v1 rootless qualification receipt")
parser.add_argument("--qualification-test", required=True, type=Path, help="exact dockeragent.test qualification binary")
parser.add_argument("--collector", required=True, type=Path, help="exact pulse-agent binary")
parser.add_argument("--helper", required=True, type=Path, help="exact pulse-agent-helper binary")
parser.add_argument("--installer", required=True, type=Path, help="exact install.sh blob")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
arguments = parse_args(argv)
try:
attestation = create_attestation(
arguments.receipt,
arguments.qualification_test,
arguments.collector,
arguments.helper,
arguments.installer,
)
except ValidationError as exc:
print(f"rootless receipt validation failed: {exc}", file=sys.stderr)
return 1
print(json.dumps(attestation, sort_keys=True, separators=(",", ":")))
return 0
if __name__ == "__main__":
raise SystemExit(main())