Files
ITWorx_MCP_Hub-Public/scripts/verify_demo_token_runtime.py
T

90 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Fail closed unless the live router reads the operator's revocation file.
Run this on the Docker host before minting or revoking a Fleet Ops demo token.
It reads Docker mount metadata and file metadata only; it never reads a token,
signing key, or revocation-file contents.
"""
from __future__ import annotations
import argparse
import json
import os
import stat
import subprocess
import sys
from pathlib import Path
from typing import Any
ROUTER_DIRECTORY = "/etc/itworx"
ROUTER_DESTINATION = ROUTER_DIRECTORY + "/demo-token-revocations.json"
def validate_mount(mounts: list[dict[str, Any]], expected_host_file: Path) -> dict[str, object]:
if not expected_host_file.is_absolute():
raise ValueError("expected host file must be an absolute path")
matching = [mount for mount in mounts if mount.get("Destination") == ROUTER_DIRECTORY]
if len(matching) != 1:
raise ValueError("router must have exactly one demo-token revocation mount")
source = Path(str(matching[0].get("Source", "")))
if source != expected_host_file.parent:
raise ValueError("router revocation directory mount does not match the operator host file")
try:
metadata = expected_host_file.stat()
except OSError as exc:
raise ValueError("operator revocation file is missing") from exc
if not stat.S_ISREG(metadata.st_mode):
raise ValueError("operator revocation file is not a regular file")
mode = stat.S_IMODE(metadata.st_mode)
# The operator command runs on Tower (Linux). Windows does not expose
# NTFS ACLs through POSIX mode bits, so local unit tests cannot make this
# assertion meaningful there.
if os.name != "nt" and mode != 0o600:
raise ValueError("operator revocation file mode must be exactly 0600")
if matching[0].get("RW") is not False:
raise ValueError("router revocation mount must be read-only")
return {
"status": "pass",
"containerDestination": ROUTER_DESTINATION,
"hostFile": str(expected_host_file),
"mode": f"{mode:04o}",
"readOnly": True,
}
def inspect_mounts(router_container: str) -> list[dict[str, Any]]:
result = subprocess.run( # noqa: S603
["docker", "inspect", router_container, "--format", "{{json .Mounts}}"], # noqa: S607
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
raise ValueError("could not inspect router container")
try:
mounts = json.loads(result.stdout)
except json.JSONDecodeError as exc:
raise ValueError("router mount metadata is invalid") from exc
if not isinstance(mounts, list) or not all(isinstance(item, dict) for item in mounts):
raise ValueError("router mount metadata is invalid")
return mounts
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--router-container", required=True)
parser.add_argument("--expected-host-file", type=Path, required=True)
args = parser.parse_args()
try:
report = validate_mount(inspect_mounts(args.router_container), args.expected_host_file)
except ValueError as exc:
print(json.dumps({"status": "fail", "reason": str(exc)}, sort_keys=True))
return 1
print(json.dumps(report, sort_keys=True))
return 0
if __name__ == "__main__":
sys.exit(main())