Publish curated Unraid-AppOps-Gateway source from 4f99d8f6ea4fb8ae105b83570344b25fa05c31fd
This commit is contained in:
1 parent
27a33107a3
commit
f50d6e8c2c
14 files changed
+1178
-21
No files matched your search
@@ -0,0 +1,277 @@
|
||||
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, "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_unknown"
|
||||
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["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
|
||||
)
|
||||
Reference in new issue
Block a user