Files
Unraid-AppOps-Gateway-Public/backend/tests/test_forge_bootstrap_controller.py
T

284 lines
11 KiB
Python

from __future__ import annotations
import hashlib
import http.client
import importlib.util
import json
import subprocess
import sys
import threading
import types
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
STATUS_SERVER = ROOT / "ops" / "gitea-deploy-broker" / "status_server.py"
@pytest.fixture
def status_module():
if "fcntl" not in sys.modules:
fcntl = types.ModuleType("fcntl")
fcntl.LOCK_EX = 2
fcntl.LOCK_UN = 8
fcntl.flock = lambda *_args: None
sys.modules["fcntl"] = fcntl
spec = importlib.util.spec_from_file_location("forge_bootstrap_status_test", STATUS_SERVER)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _request(module, server, body: bytes, *, nonce: str, secret: bytes, signature: str | None = None):
path = module.FORGE_BOOTSTRAP_PREFLIGHT_PATH
timestamp = str(int(module.time.time()))
signature = signature or module._request_signature(
secret,
method="POST",
path=path,
timestamp=timestamp,
nonce=nonce,
raw_body=body,
)
connection = http.client.HTTPConnection(*server.server_address, timeout=2)
connection.request(
"POST",
path,
body=body,
headers={
"Content-Type": "application/json",
"Content-Length": str(len(body)),
"X-ITWorx-Deployment-Timestamp": timestamp,
"X-ITWorx-Deployment-Nonce": nonce,
"X-ITWorx-Deployment-Signature": signature,
},
)
response = connection.getresponse()
status, payload = response.status, json.loads(response.read())
connection.close()
return status, payload
def test_forge_preflight_http_is_signed_strict_replay_safe_and_read_only(
status_module, tmp_path, monkeypatch
):
secret = b"forge-bootstrap-test-secret-" + b"x" * 40
secret_path = tmp_path / "bootstrap"
secret_path.write_bytes(secret)
secret_path.chmod(0o600)
nonce_root = tmp_path / "nonces"
monkeypatch.setattr(status_module, "APPOPS_ENROLLMENT_BOOTSTRAP_SECRET_FILE", secret_path)
monkeypatch.setattr(status_module, "APPOPS_NONCE_ROOT", nonce_root)
calls = []
response = {
"apiVersion": status_module.FORGE_BOOTSTRAP_PREFLIGHT_VERSION,
"status": "preflight_unknown",
"revision": "a" * 40,
"workspaceImageDigest": "sha256:" + "b" * 64,
"frontdoorImageDigest": "sha256:" + "c" * 64,
"checks": {"exact_main": {"status": "unknown"}},
"evidence": {},
}
monkeypatch.setattr(
status_module,
"_forge_bootstrap_runtime_checks",
lambda profile: calls.append(profile) or response,
)
server = status_module.BoundedThreadingHTTPServer(
("127.0.0.1", 0), status_module.Handler
)
serving = threading.Thread(target=server.serve_forever, daemon=True)
serving.start()
profile = {
"api_version": status_module.FORGE_BOOTSTRAP_PREFLIGHT_VERSION,
"revision": "a" * 40,
"workspace_image_digest": "sha256:" + "b" * 64,
"frontdoor_image_digest": "sha256:" + "c" * 64,
}
body = json.dumps({"profile": profile}, sort_keys=True, separators=(",", ":")).encode()
try:
status, payload = _request(
status_module, server, body, nonce="a" * 32, secret=secret
)
assert status == 200
assert payload == response
assert calls == [profile]
assert secret.decode() not in json.dumps(payload)
assert len(list(nonce_root.iterdir())) == 1
replay_status, replay_payload = _request(
status_module, server, body, nonce="a" * 32, secret=secret
)
assert replay_status == 403
assert replay_payload == {"status": "authentication_rejected"}
assert calls == [profile]
hostile = dict(profile, host_path="/etc", endpoint="https://attacker.invalid")
hostile_body = json.dumps(
{"profile": hostile}, sort_keys=True, separators=(",", ":")
).encode()
hostile_status, hostile_payload = _request(
status_module, server, hostile_body, nonce="b" * 32, secret=secret
)
assert hostile_status == 400
assert hostile_payload == {"status": "invalid_forge_preflight_contract"}
assert calls == [profile]
assert len(list(nonce_root.iterdir())) == 1
bad_signature_status, _ = _request(
status_module,
server,
body,
nonce="c" * 32,
secret=secret,
signature="0" * 64,
)
assert bad_signature_status == 403
assert calls == [profile]
finally:
server.shutdown()
server.server_close()
serving.join(timeout=2)
def test_forge_host_preflight_compares_exact_main_and_reports_fixed_runtime_checks(
status_module, tmp_path, monkeypatch
):
revision = "a" * 40
manifest = b"fixed Forge compose candidate"
managed_root = tmp_path / "managed-root"
postgres_dir = managed_root / "itworx-forge" / "postgres"
postgres_dir.mkdir(parents=True)
(managed_root / "itworx-forge").chmod(0o700)
postgres_dir.chmod(0o700)
app_dir = managed_root / "itworx-forge"
monkeypatch.setattr(status_module, "MANAGED_APPDATA_ROOT", managed_root)
monkeypatch.setattr(status_module, "_refresh_appdata_root_selection", lambda: managed_root)
monkeypatch.setattr(status_module, "appdata_root_is_safe", lambda _root: True)
monkeypatch.setattr(
status_module, "os", types.SimpleNamespace(name="posix", geteuid=lambda: 0)
)
original_lstat = Path.lstat
def lstat_with_fixed_service_owners(path):
info = original_lstat(path)
if path == app_dir:
return types.SimpleNamespace(
st_mode=status_module.stat.S_IFDIR | 0o700, st_uid=0, st_gid=0
)
if path == postgres_dir:
return types.SimpleNamespace(
st_mode=status_module.stat.S_IFDIR | 0o700, st_uid=70, st_gid=70
)
return info
monkeypatch.setattr(Path, "lstat", lstat_with_fixed_service_owners)
monkeypatch.setattr(status_module, "_forge_gitea_main_revision", lambda: revision)
monkeypatch.setattr(status_module, "_forge_push_validation", lambda _revision: "unknown")
monkeypatch.setattr(
status_module,
"_load_forge_bootstrap_config",
lambda _path: {"private_package_owner": "itworx-forge-private"},
)
monkeypatch.setattr(
status_module, "FORGE_COMPOSE_SHA256", hashlib.sha256(manifest).hexdigest()
)
commands = []
def run(arguments, **_kwargs):
commands.append(arguments)
if arguments[:2] == ["git", "--git-dir"]:
if "rev-parse" in arguments:
return subprocess.CompletedProcess(arguments, 0, stdout=revision, stderr="")
if "show" in arguments:
return subprocess.CompletedProcess(arguments, 0, stdout=manifest, stderr=b"")
if arguments[:3] == ["docker", "network", "ls"]:
return subprocess.CompletedProcess(
arguments, 0, stdout="itworx-mcp-hub-npm\n", stderr=""
)
if arguments[:3] == ["docker", "network", "inspect"]:
return subprocess.CompletedProcess(
arguments,
0,
stdout=json.dumps(
{"Name": "itworx-mcp-hub-npm", "Driver": "bridge", "Scope": "local"}
),
stderr="",
)
if arguments[:3] == ["docker", "ps", "-a"]:
return subprocess.CompletedProcess(arguments, 0, stdout="", stderr="")
if arguments[:3] == ["docker", "image", "inspect"]:
return subprocess.CompletedProcess(
arguments, 0, stdout="sha256:" + "d" * 64, stderr=""
)
raise AssertionError(f"unexpected fixed read-only command: {arguments!r}")
monkeypatch.setattr(status_module.subprocess, "run", run)
profile = {
"revision": revision,
"workspace_image_digest": "sha256:" + "b" * 64,
"frontdoor_image_digest": "sha256:" + "c" * 64,
}
result = status_module._forge_bootstrap_runtime_checks(profile)
assert result["status"] == "preflight_blocked"
assert result["checks"]["exact_main"] == {"status": "passed"}
assert result["checks"]["source_manifest"] == {"status": "passed"}
assert result["checks"]["managed_appdata"] == {"status": "passed"}
assert result["checks"]["proxy_network"] == {"status": "passed"}
assert result["checks"]["resource_collisions"] == {"status": "passed"}
assert result["checks"]["push_validation"] == {"status": "unknown"}
assert result["checks"]["secret_delivery"] == {"status": "blocked"}
assert result["evidence"]["mainRevision"] == revision
assert result["evidence"]["sourceManifestSha256"] == hashlib.sha256(manifest).hexdigest()
assert result["evidence"]["workspaceImagePresent"] is True
assert result["evidence"]["frontdoorImagePresent"] is True
assert all(isinstance(command, list) for command in commands)
assert all("shell" not in " ".join(command).lower() for command in commands)
commands.clear()
non_head = dict(profile, revision="b" * 40)
result = status_module._forge_bootstrap_runtime_checks(non_head)
assert result["checks"]["exact_main"] == {"status": "blocked"}
assert result["status"] == "preflight_blocked"
postgres_dir.rmdir()
postgres_dir.parent.rmdir()
managed_root.rmdir()
result = status_module._forge_bootstrap_runtime_checks(profile)
assert result["checks"]["managed_appdata"] == {"status": "blocked"}
assert not managed_root.exists()
def test_forge_appdata_parent_requires_root_only_metadata(status_module, monkeypatch):
monkeypatch.setattr(status_module, "os", types.SimpleNamespace(name="posix"))
safe = types.SimpleNamespace(
st_mode=status_module.stat.S_IFDIR | 0o700, st_uid=0, st_gid=0
)
group_writable = types.SimpleNamespace(
st_mode=status_module.stat.S_IFDIR | 0o770, st_uid=0, st_gid=0
)
wrong_owner = types.SimpleNamespace(
st_mode=status_module.stat.S_IFDIR | 0o700, st_uid=1000, st_gid=0
)
assert status_module._forge_app_dir_metadata_is_safe(safe) is True
assert status_module._forge_app_dir_metadata_is_safe(group_writable) is False
assert status_module._forge_app_dir_metadata_is_safe(wrong_owner) is False
def test_forge_local_image_query_failure_is_unknown(status_module, monkeypatch):
monkeypatch.setattr(
status_module.subprocess,
"run",
lambda *_args, **_kwargs: subprocess.CompletedProcess(
[], 1, stdout="", stderr="daemon unavailable"
),
)
assert (
status_module._forge_local_image_present(
"gitea.example.com/jens/itworx-forge@sha256:" + "a" * 64
)
is None
)