284 lines
11 KiB
Python
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
|
|
)
|