teleo-infrastructure/ops/leoclean_nosend_service_control.py
fwazb a0fabc3072
Some checks are pending
CI / lint-and-test (push) Waiting to run
Add fail-closed leoclean staging installer
2026-07-21 10:58:36 -07:00

319 lines
11 KiB
Python

#!/usr/bin/python3 -I
"""Control one digest-bound leoclean no-send container by verified full ID."""
from __future__ import annotations
import argparse
import json
import os
import re
import stat
import subprocess
import sys
import time
from collections.abc import Callable
from pathlib import Path
from typing import Any
DOCKER_BINARY = "/usr/bin/docker"
DOCKER_HOST = "unix:///var/run/docker.sock"
DOCKER_CONFIG = "/var/lib/livingip/leoclean-nosend/docker-config"
CONTAINER_NAME = "livingip-leoclean-nosend"
CIDFILE = Path("/run/leoclean-gcp-nosend/container.cid")
IMAGE_REPOSITORY = "europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging"
SAFE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
MAX_DOCKER_OUTPUT_BYTES = 1024 * 1024
HEX_64 = re.compile(r"^[0-9a-f]{64}$")
IMAGE_REFERENCE = re.compile(rf"^{re.escape(IMAGE_REPOSITORY)}@sha256:[0-9a-f]{{64}}$")
IMAGE_CONFIG_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
Runner = Callable[[list[str]], subprocess.CompletedProcess[bytes]]
class ControlError(RuntimeError):
"""A bounded service-control failure."""
def _require(condition: bool, message: str) -> None:
if not condition:
raise ControlError(message)
def _strict_json(raw: bytes) -> Any:
_require(len(raw) <= MAX_DOCKER_OUTPUT_BYTES, "docker output exceeded the safety limit")
def reject_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
value: dict[str, Any] = {}
for key, item in pairs:
if key in value:
raise ValueError("duplicate key")
value[key] = item
return value
try:
return json.loads(
raw.decode("utf-8", "strict"),
object_pairs_hook=reject_duplicates,
parse_constant=lambda _value: (_ for _ in ()).throw(ValueError("constant")),
)
except (UnicodeError, ValueError, json.JSONDecodeError) as exc:
raise ControlError("docker inspection was invalid") from exc
def _default_runner(argv: list[str]) -> subprocess.CompletedProcess[bytes]:
environment = {"PATH": SAFE_PATH}
return subprocess.run(
argv,
check=False,
stdin=subprocess.DEVNULL,
capture_output=True,
timeout=45,
env=environment,
)
def _docker(arguments: list[str], runner: Runner) -> subprocess.CompletedProcess[bytes]:
argv = [
DOCKER_BINARY,
f"--host={DOCKER_HOST}",
f"--config={DOCKER_CONFIG}",
*arguments,
]
try:
completed = runner(argv)
except (OSError, subprocess.SubprocessError) as exc:
raise ControlError("docker command failed") from exc
_require(
len(completed.stdout) <= MAX_DOCKER_OUTPUT_BYTES and len(completed.stderr) <= MAX_DOCKER_OUTPUT_BYTES,
"docker output exceeded the safety limit",
)
return completed
def _inspect(reference: str, runner: Runner) -> dict[str, Any] | None:
completed = _docker(["container", "inspect", reference], runner)
if completed.returncode != 0:
try:
error = completed.stderr.decode("utf-8", "strict").strip()
except UnicodeError as exc:
raise ControlError("container absence could not be proven") from exc
absence = re.compile(
rf"^(?:Error response from daemon: |Error: )?No such (?:container|object): {re.escape(reference)}$"
)
_require(absence.fullmatch(error) is not None, "container absence could not be proven")
return None
value = _strict_json(completed.stdout)
_require(
isinstance(value, list) and len(value) == 1 and isinstance(value[0], dict),
"container inspection was not exact",
)
return value[0]
def _validate_container(
value: dict[str, Any],
*,
container_id: str,
release_sha256: str,
image_reference: str,
image_config_digest: str,
) -> bool:
config = value.get("Config")
labels = config.get("Labels") if isinstance(config, dict) else None
state = value.get("State")
_require(
value.get("Id") == container_id
and value.get("Name") == f"/{CONTAINER_NAME}"
and value.get("Image") == image_config_digest
and isinstance(config, dict)
and config.get("Image") == image_reference
and isinstance(labels, dict)
and labels.get("livingip.release.sha256") == release_sha256
and labels.get("livingip.container.role") == "leoclean-gcp-nosend"
and isinstance(state, dict)
and isinstance(state.get("Running"), bool),
"container identity did not match the installed release",
)
return bool(state["Running"])
def _cidfile_parent_descriptor(cidfile: Path) -> int:
flags = os.O_RDONLY
if hasattr(os, "O_DIRECTORY"):
flags |= os.O_DIRECTORY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
descriptor = os.open(cidfile.parent, flags)
observed = os.fstat(descriptor)
named = os.stat(cidfile.parent, follow_symlinks=False)
except OSError as exc:
raise ControlError("container ID directory was unsafe") from exc
if not (
stat.S_ISDIR(observed.st_mode)
and (observed.st_dev, observed.st_ino) == (named.st_dev, named.st_ino)
and observed.st_uid == os.geteuid()
and observed.st_mode & 0o077 == 0
):
os.close(descriptor)
raise ControlError("container ID directory was unsafe")
return descriptor
def _read_cidfile(cidfile: Path) -> tuple[str, tuple[int, int]] | None:
parent_descriptor = _cidfile_parent_descriptor(cidfile)
descriptor: int | None = None
try:
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
descriptor = os.open(cidfile.name, flags, dir_fd=parent_descriptor)
except FileNotFoundError:
return None
observed = os.fstat(descriptor)
named = os.stat(cidfile.name, dir_fd=parent_descriptor, follow_symlinks=False)
_require(
stat.S_ISREG(observed.st_mode)
and (observed.st_dev, observed.st_ino) == (named.st_dev, named.st_ino)
and observed.st_uid == os.geteuid()
and stat.S_IMODE(observed.st_mode) == 0o600
and observed.st_nlink == 1
and observed.st_size <= 65,
"container ID file was unsafe",
)
raw = os.read(descriptor, 66)
try:
container_id = raw.decode("ascii", "strict").strip()
except UnicodeError as exc:
raise ControlError("container ID file was invalid") from exc
_require(HEX_64.fullmatch(container_id) is not None, "container ID file was invalid")
return container_id, (observed.st_dev, observed.st_ino)
except OSError as exc:
raise ControlError("container ID file could not be read") from exc
finally:
if descriptor is not None:
os.close(descriptor)
os.close(parent_descriptor)
def _unlink_cidfile(cidfile: Path, identity: tuple[int, int]) -> None:
parent_descriptor = _cidfile_parent_descriptor(cidfile)
try:
observed = os.stat(cidfile.name, dir_fd=parent_descriptor, follow_symlinks=False)
_require(
stat.S_ISREG(observed.st_mode) and (observed.st_dev, observed.st_ino) == identity,
"container ID file changed during cleanup",
)
os.unlink(cidfile.name, dir_fd=parent_descriptor)
os.fsync(parent_descriptor)
except OSError as exc:
raise ControlError("container ID file cleanup failed") from exc
finally:
os.close(parent_descriptor)
def _wait_absent(container_id: str, runner: Runner, sleeper: Callable[[float], None]) -> None:
for _attempt in range(60):
if _inspect(container_id, runner) is None:
return
sleeper(0.25)
raise ControlError("container did not stop within the bounded wait")
def _require_name_unbound(runner: Runner) -> None:
_require(_inspect(CONTAINER_NAME, runner) is None, "container name was rebound after the trusted container exited")
def _stop_exact(container_id: str, runner: Runner, sleeper: Callable[[float], None]) -> None:
completed = _docker(["container", "stop", "--time=30", container_id], runner)
_require(completed.returncode == 0, "container stop failed")
_wait_absent(container_id, runner, sleeper)
def control_container(
action: str,
release_sha256: str,
image_reference: str,
image_config_digest: str,
*,
runner: Runner = _default_runner,
sleeper: Callable[[float], None] = time.sleep,
cidfile: Path = CIDFILE,
) -> dict[str, object]:
_require(action in {"prestart", "stop"}, "service-control action was invalid")
_require(HEX_64.fullmatch(release_sha256) is not None, "release hash was invalid")
_require(IMAGE_REFERENCE.fullmatch(image_reference) is not None, "image reference was invalid")
_require(IMAGE_CONFIG_DIGEST.fullmatch(image_config_digest) is not None, "image config digest was invalid")
cid = _read_cidfile(cidfile)
by_name = _inspect(CONTAINER_NAME, runner)
if cid is None:
_require(by_name is None, "container name is occupied without a trusted ID file")
return {"action": action, "status": "pass", "container": "absent"}
container_id, cid_identity = cid
by_id = _inspect(container_id, runner)
if by_id is None:
_require(by_name is None, "container name was rebound after the trusted container exited")
_require_name_unbound(runner)
_unlink_cidfile(cidfile, cid_identity)
return {"action": action, "status": "pass", "container": "absent"}
running = _validate_container(
by_id,
container_id=container_id,
release_sha256=release_sha256,
image_reference=image_reference,
image_config_digest=image_config_digest,
)
_require(
by_name is not None and by_name.get("Id") == container_id,
"container name and trusted ID did not agree",
)
if running:
_stop_exact(container_id, runner, sleeper)
else:
_wait_absent(container_id, runner, sleeper)
_require_name_unbound(runner)
_unlink_cidfile(cidfile, cid_identity)
return {"action": action, "status": "pass", "container": "stopped"}
class _Parser(argparse.ArgumentParser):
def error(self, _message: str) -> None:
raise ControlError("invalid arguments")
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = _Parser(description=__doc__, add_help=False)
parser.add_argument("action", choices=("prestart", "stop"))
parser.add_argument("release_sha256")
parser.add_argument("image_reference")
parser.add_argument("image_config_digest")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
try:
args = parse_args(argv)
result = control_container(
args.action,
args.release_sha256,
args.image_reference,
args.image_config_digest,
)
sys.stdout.write(json.dumps(result, separators=(",", ":"), sort_keys=True) + "\n")
return 0
except ControlError as exc:
sys.stderr.write(
json.dumps({"error": str(exc), "status": "fail"}, separators=(",", ":"), sort_keys=True) + "\n"
)
return 65
except Exception:
sys.stderr.write('{"error":"service control failed","status":"fail"}\n')
return 65
if __name__ == "__main__":
raise SystemExit(main())