Publish curated Unraid-AppOps-Gateway source from 4f99d8f6ea4fb8ae105b83570344b25fa05c31fd

This commit is contained in:
Public Source Publisher committed 2026-10-02 18:50:59 +00:00
1 parent 27a33107a3
commit f50d6e8c2c
14 files changed
+1178 -21

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export # Public source export
This source snapshot was generated from the private canonical repository at revision `f8f5bf0a29159bd69accd7e58f6427ec32140640`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. This source snapshot was generated from the private canonical repository at revision `4f99d8f6ea4fb8ae105b83570344b25fa05c31fd`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+37 -17
View File
@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"sourceRevision": "f8f5bf0a29159bd69accd7e58f6427ec32140640", "sourceRevision": "4f99d8f6ea4fb8ae105b83570344b25fa05c31fd",
"files": [ "files": [
{ {
"path": ".dockerignore", "path": ".dockerignore",
@@ -124,8 +124,8 @@
}, },
{ {
"path": "backend/app/api/routes_deployments.py", "path": "backend/app/api/routes_deployments.py",
"sha256": "8a7a2503c38fd1edea002cbb780844b6907830edf88bd74e9f4c6bbbdf94a43e", "sha256": "0441f74d508dde2ca0641cae32b8a7f386496c8f6f0f2df80ec619a6409bc9b3",
"bytes": 4241 "bytes": 4955
}, },
{ {
"path": "backend/app/api/routes_enrollments.py", "path": "backend/app/api/routes_enrollments.py",
@@ -404,8 +404,8 @@
}, },
{ {
"path": "backend/app/mcp_server.py", "path": "backend/app/mcp_server.py",
"sha256": "8eb186a7264fd602b019a06e49eae99d91dcce157ae538b30afc7e48320f8df8", "sha256": "75779ae0a366cca415c04fc6fe24f38d99c338939874acf17be6616cc12b4f25",
"bytes": 34436 "bytes": 35237
}, },
{ {
"path": "backend/app/schemas/ai_remediation.py", "path": "backend/app/schemas/ai_remediation.py",
@@ -432,6 +432,11 @@
"sha256": "cb81e13c924626cfd8d6c91a240d6299b92798e0f760a19aa906d021736e1f0b", "sha256": "cb81e13c924626cfd8d6c91a240d6299b92798e0f760a19aa906d021736e1f0b",
"bytes": 334 "bytes": 334
}, },
{
"path": "backend/app/schemas/forge_bootstrap.py",
"sha256": "12db5aa43594bbcc9e4306a173769afdeabb86d364e0939e637b80775ea73629",
"bytes": 1208
},
{ {
"path": "backend/app/services/__init__.py", "path": "backend/app/services/__init__.py",
"sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
@@ -489,8 +494,8 @@
}, },
{ {
"path": "backend/app/services/controller_artifact_promoter.py", "path": "backend/app/services/controller_artifact_promoter.py",
"sha256": "a193123dd59de03f19cc14e51e9551e0ab316b741cc8b417ed530f86c539dc13", "sha256": "6e740205449af2adc1fbbf9adf15989532fc6a92e252c8dcc81e7b4d644e58cd",
"bytes": 16370 "bytes": 16389
}, },
{ {
"path": "backend/app/services/controller_bootstrap_import.py", "path": "backend/app/services/controller_bootstrap_import.py",
@@ -499,8 +504,8 @@
}, },
{ {
"path": "backend/app/services/deployment_broker.py", "path": "backend/app/services/deployment_broker.py",
"sha256": "c8e58f76b1f245553c1a419e33ad919e3366cc54b7f9bdcc8698d46abbff0c6b", "sha256": "2feb43b65684f3f6a2dba688a47c18117d907ca34426919270f67e4de42db453",
"bytes": 22786 "bytes": 23266
}, },
{ {
"path": "backend/app/services/deployment_control_plane.py", "path": "backend/app/services/deployment_control_plane.py",
@@ -532,6 +537,11 @@
"sha256": "36cf235e02e4289b7b1851bb6240584bf8d6f6fbb9b2ae22ac521ec8012ba025", "sha256": "36cf235e02e4289b7b1851bb6240584bf8d6f6fbb9b2ae22ac521ec8012ba025",
"bytes": 2538 "bytes": 2538
}, },
{
"path": "backend/app/services/forge_bootstrap_preflight.py",
"sha256": "b5ced0a85cc1585386fec21cc1cfa5bc88b5ccecf211beff93f99d79736f413f",
"bytes": 14808
},
{ {
"path": "backend/app/services/gitea_project_auto_registration.py", "path": "backend/app/services/gitea_project_auto_registration.py",
"sha256": "0da4469d2b75d1d6c74704b9c402348ce246f3325c1726bd50dcf13b378e060b", "sha256": "0da4469d2b75d1d6c74704b9c402348ce246f3325c1726bd50dcf13b378e060b",
@@ -819,8 +829,8 @@
}, },
{ {
"path": "backend/tests/test_controller_artifact_promoter.py", "path": "backend/tests/test_controller_artifact_promoter.py",
"sha256": "590a7155052851e767a048d15f7f6624070a68c020e90b32ed8801ceab11e972", "sha256": "671f62d61ec1281644d0b49500141dd4a50ef75b7bf82accbe80dd0bf16d04ec",
"bytes": 3672 "bytes": 3922
}, },
{ {
"path": "backend/tests/test_controller_bootstrap_import_host.py", "path": "backend/tests/test_controller_bootstrap_import_host.py",
@@ -834,13 +844,13 @@
}, },
{ {
"path": "backend/tests/test_controller_hub_release_volume.py", "path": "backend/tests/test_controller_hub_release_volume.py",
"sha256": "0dddc78484ebc4a09bc75e3e71cdca5f48cb3b6afba91c3b36aa16703266d6e0", "sha256": "d527b5ac287a02bc5e9f4c89ddf69638b6f8a12c5a34ad2e65db2721b8e05303",
"bytes": 21814 "bytes": 28619
}, },
{ {
"path": "backend/tests/test_controller_root_procedure_contract.py", "path": "backend/tests/test_controller_root_procedure_contract.py",
"sha256": "1552a883e13a66e0f154481c6f3cf2b162ad867d6bbaaed7a84cedd45c5935ac", "sha256": "ff2c7b80c3bc71b1ab06fc5637fce0c2c73df78e6d6123a47a34b3036f3caa8c",
"bytes": 2793 "bytes": 3299
}, },
{ {
"path": "backend/tests/test_controller_state_reconciliation.py", "path": "backend/tests/test_controller_state_reconciliation.py",
@@ -897,6 +907,16 @@
"sha256": "ddd08c246eb0719556f307691ca231093584dc4b85e78bb849bee83ccbde8b1f", "sha256": "ddd08c246eb0719556f307691ca231093584dc4b85e78bb849bee83ccbde8b1f",
"bytes": 8374 "bytes": 8374
}, },
{
"path": "backend/tests/test_forge_bootstrap_controller.py",
"sha256": "442a57109bc2dc0e4d4b9eb50c84ae8bac5685f94c9ec41fcfdadd0911352e44",
"bytes": 10829
},
{
"path": "backend/tests/test_forge_bootstrap_preflight.py",
"sha256": "b9ef73cd20bcdb99eb4afe42d26604397e82258f24f4b5dbc25d182c1c6a95da",
"bytes": 8518
},
{ {
"path": "backend/tests/test_frontend_file_boundary.py", "path": "backend/tests/test_frontend_file_boundary.py",
"sha256": "beee4046574b89f6543d7a07040b983cfd7a1e561dbd9db4f81c9cd66ab77c32", "sha256": "beee4046574b89f6543d7a07040b983cfd7a1e561dbd9db4f81c9cd66ab77c32",
@@ -979,8 +999,8 @@
}, },
{ {
"path": "backend/tests/test_mcp_rag_n8n_integrations.py", "path": "backend/tests/test_mcp_rag_n8n_integrations.py",
"sha256": "0b2891cd4b545ced2e737e4caa1506ceedecb4600247223b3490749213348fba", "sha256": "89f557049d6e2c65fada94321503df65bf7c82bd567038241ea3bdad7c866ed1",
"bytes": 48408 "bytes": 50584
}, },
{ {
"path": "backend/tests/test_media_codec_dashboard_deployment_reconciliation.py", "path": "backend/tests/test_media_codec_dashboard_deployment_reconciliation.py",
+23 -1
View File
@@ -15,11 +15,12 @@ from sqlmodel import Session
from ..core.security import require_operator_request from ..core.security import require_operator_request
from ..db.session import get_session from ..db.session import get_session
from ..schemas.deployment import DeploymentPlanV1 from ..schemas.deployment import DeploymentPlanV1
from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from ..services.deployment_broker import DeploymentBrokerClient
from ..services.deployment_control_plane import ( from ..services.deployment_control_plane import (
DeploymentControlError, DeploymentControlError,
DeploymentControlPlane, DeploymentControlPlane,
) )
from ..services.deployment_broker import DeploymentBrokerClient
def _require_deployment_operator(request: Request) -> str: def _require_deployment_operator(request: Request) -> str:
@@ -95,6 +96,27 @@ def preflight_workmesh_promotion(
raise HTTPException(status_code=409, detail=str(exc)) from exc raise HTTPException(status_code=409, detail=str(exc)) from exc
@router.post("/forge-bootstrap/preflight")
def preflight_forge_bootstrap(
payload: ForgeBootstrapPreflightV1,
session: DeploymentSession,
idempotency_key: DeploymentIdempotencyKey = None,
):
from ..services.forge_bootstrap_preflight import (
ForgeBootstrapPreflightError,
ForgeBootstrapPreflightService,
)
try:
return ForgeBootstrapPreflightService().preflight(
session,
payload,
idempotency_key=_idempotency_header(idempotency_key),
)
except ForgeBootstrapPreflightError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
@router.get("/workmesh/promotions/{promotion_id}") @router.get("/workmesh/promotions/{promotion_id}")
def get_workmesh_promotion(promotion_id: str, session: DeploymentSession): def get_workmesh_promotion(promotion_id: str, session: DeploymentSession):
from ..services.workmesh_promotion import WorkMeshPromotionService from ..services.workmesh_promotion import WorkMeshPromotionService
+20 -1
View File
@@ -27,6 +27,7 @@ from .db.models import AppSetting, AuditEvent
from .db.session import engine from .db.session import engine
from .schemas.deployment import DeploymentPlanV1 from .schemas.deployment import DeploymentPlanV1
from .schemas.enrollment import EnrollmentPlanV1 from .schemas.enrollment import EnrollmentPlanV1
from .schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from .services import knowledge_service, mcp_operations, safety_center_service from .services import knowledge_service, mcp_operations, safety_center_service
from .services.action_execution import mcp_execution_supported from .services.action_execution import mcp_execution_supported
from .services.action_registry import ACTION_RISK, ACTIONS, action_capability from .services.action_registry import ACTION_RISK, ACTIONS, action_capability
@@ -653,8 +654,26 @@ def cancel_job(jobId: str) -> dict[str, Any]:
@appops_mcp.tool(name="deployment.preflight", annotations=_READ_ONLY_TOOL) @appops_mcp.tool(name="deployment.preflight", annotations=_READ_ONLY_TOOL)
@_tracked("deployment.preflight") @_tracked("deployment.preflight")
def deployment_preflight(plan: dict[str, Any], idempotencyKey: str) -> dict[str, Any]: def deployment_preflight(plan: dict[str, Any], idempotencyKey: str) -> dict[str, Any]:
"""Validate one strict DeploymentPlanV1 without touching application runtime state.""" """Validate a strict V1 plan or fixed Forge bootstrap profile read-only."""
try: try:
if plan.get("api_version") == "itworx.forge-bootstrap-preflight/v1":
from .services.forge_bootstrap_preflight import (
ForgeBootstrapPreflightService,
)
profile = ForgeBootstrapPreflightV1.model_validate(plan)
with Session(engine) as session:
return _json(
ForgeBootstrapPreflightService().preflight(
session, profile, idempotency_key=idempotencyKey
)
)
if "api_version" in plan:
return {
"status": "invalid",
"executed": False,
"reason": "Unsupported deployment.preflight profile discriminator.",
}
typed_plan = DeploymentPlanV1.model_validate(plan) typed_plan = DeploymentPlanV1.model_validate(plan)
with Session(engine) as session: with Session(engine) as session:
return _json( return _json(
+34
View File
@@ -0,0 +1,34 @@
from __future__ import annotations
import re
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
_REVISION = re.compile(r"^[0-9a-f]{40}$")
_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
class ForgeBootstrapPreflightV1(BaseModel):
"""Fixed read-only input for Forge production bootstrap readiness."""
model_config = ConfigDict(extra="forbid", strict=True)
api_version: Literal["itworx.forge-bootstrap-preflight/v1"]
revision: str = Field(min_length=40, max_length=40)
workspace_image_digest: str = Field(min_length=71, max_length=71)
frontdoor_image_digest: str = Field(min_length=71, max_length=71)
@field_validator("revision")
@classmethod
def full_lowercase_revision(cls, value: str) -> str:
if not _REVISION.fullmatch(value):
raise ValueError("revision must be a full lowercase Git SHA")
return value
@field_validator("workspace_image_digest", "frontdoor_image_digest")
@classmethod
def immutable_oci_digest(cls, value: str) -> str:
if not _DIGEST.fullmatch(value):
raise ValueError("image digest must be a lowercase sha256 OCI digest")
return value
@@ -60,6 +60,7 @@ git --git-dir="$repo" archive --format=tar "$revision" \
scripts/reconcile_controller_state.py \ scripts/reconcile_controller_state.py \
scripts/run_repopulse_adoption.py \ scripts/run_repopulse_adoption.py \
scripts/prepare_controller_hub_release_volume.py \ scripts/prepare_controller_hub_release_volume.py \
scripts/vendor \
scripts/upgrade_deployment_controller.sh > "$work/controller.tar" scripts/upgrade_deployment_controller.sh > "$work/controller.tar"
chmod 0600 "$work/controller.tar" chmod 0600 "$work/controller.tar"
archive_sha="$(sha256sum "$work/controller.tar" | cut -d ' ' -f 1)" archive_sha="$(sha256sum "$work/controller.tar" | cut -d ' ' -f 1)"
+12
View File
@@ -26,6 +26,7 @@ import httpx
from ..core.config import settings from ..core.config import settings
from ..core.redaction import redact_message from ..core.redaction import redact_message
from ..schemas.deployment import DeploymentPlanV1 from ..schemas.deployment import DeploymentPlanV1
from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
_REQUEST_ID = re.compile(r"^[0-9a-f]{64}$") _REQUEST_ID = re.compile(r"^[0-9a-f]{64}$")
_MAX_DELIVERY_STATUS_BYTES = 512 * 1024 _MAX_DELIVERY_STATUS_BYTES = 512 * 1024
@@ -302,6 +303,17 @@ class DeploymentBrokerClient:
payload={"enrollment": enrollment}, payload={"enrollment": enrollment},
) )
def forge_bootstrap_preflight(
self, profile: ForgeBootstrapPreflightV1, *, secret: str
) -> dict[str, Any]:
"""Read fixed Forge source and host prerequisites through bootstrap HMAC."""
return self._request(
"POST",
"/v1/appops/forge/bootstrap/preflight",
secret=secret,
payload={"profile": profile.model_dump(mode="json")},
)
def enrollment_capabilities(self, *, secret: str) -> dict[str, Any]: def enrollment_capabilities(self, *, secret: str) -> dict[str, Any]:
"""Read the bootstrap-protected enrollment controller capability.""" """Read the bootstrap-protected enrollment controller capability."""
return self._request( return self._request(
@@ -0,0 +1,329 @@
from __future__ import annotations
import hashlib
import json
import re
import uuid
from typing import Any
from sqlmodel import Session, select
from ..core.time import utc_now
from ..db.models import DeploymentJob, Policy
from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from .audit_logger import log_event
from .deployment_broker import DeploymentBrokerClient
from .enrollment_plane import EnrollmentPlane
from .policy_engine import evaluate_policy
from .secret_store import get_secret, has_secret
_API_VERSION = "itworx.forge-bootstrap-preflight/v1"
_APPLICATION_ID = "itworx-forge"
_REPOSITORY = "jens/itworx-forge"
_IDEMPOTENCY = re.compile(r"^[A-Za-z0-9._:-]{8,128}$")
_CONTROLLER_CHECKS = {
"exact_main", "source_manifest", "managed_appdata", "proxy_network",
"resource_collisions", "push_validation", "image_registry", "oidc",
"secret_delivery",
}
_DETAILS = {
"deployment_apply_policy": "An explicit allowed exact-target deployment policy is required before apply.",
"controller_trust": "Stored controller trust and bootstrap secret are required for host checks.",
"managed_appdata_root_selection": "The managed Docker-root backup acknowledgement and storage selection are required before setup.",
"exact_main": "Requested Forge revision must equal the current main head.",
"source_manifest": "The source manifest must match the fixed Forge six-service topology.",
"managed_appdata": "The fixed managed PostgreSQL directory must have safe owner-only metadata.",
"proxy_network": "The required external proxy network must be present.",
"resource_collisions": "Fixed Forge container names must be free for first installation.",
"push_validation": "Exact-main CI validation cannot be verified from trusted controller configuration.",
"image_registry": "Registry publication and pull authorization require external verification.",
"oidc": "Dedicated Forge Authentik provider, application, and role groups require external verification.",
"secret_delivery": "Forge database and application secret files require external verification.",
}
_STATUS_DETAILS = {
"exact_main": {
"passed": "Requested revision equals the current protected Gitea main head.",
"blocked": "Requested revision is not the current protected Gitea main head.",
"unknown": "Current protected Gitea main head could not be verified.",
},
"source_manifest": {
"passed": "Committed Forge manifest matches the pinned production topology contract.",
"blocked": "Committed Forge manifest differs from the pinned production topology contract.",
"unknown": "Committed Forge manifest could not be read from the controller mirror.",
},
"managed_appdata": {
"passed": "Selected managed root and fixed PostgreSQL directory have safe metadata.",
"blocked": "Managed root or fixed PostgreSQL directory is missing or unsafe.",
"unknown": "Managed storage metadata could not be verified.",
},
"proxy_network": {
"passed": "The required external proxy network exists.",
"blocked": "The required external proxy network is missing.",
"unknown": "Docker network inventory is unavailable.",
},
"resource_collisions": {
"passed": "No fixed Forge container-name collisions were found.",
"blocked": "One or more fixed Forge container names already exist.",
"unknown": "Docker container inventory is unavailable.",
},
"push_validation": {
"passed": "Configured required push validation contexts passed for this exact commit.",
"blocked": "A configured required push validation context rejected this exact commit.",
"unknown": "Exact-commit push validation is not configured or cannot be verified.",
},
}
class ForgeBootstrapPreflightError(ValueError):
"""A fixed-target Forge preflight request failed closed."""
def _canonical_digest(value: dict[str, Any]) -> str:
return hashlib.sha256(
json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
def _idempotency_digest(value: str) -> str:
normalized = value.strip()
if not _IDEMPOTENCY.fullmatch(normalized):
raise ForgeBootstrapPreflightError("Idempotency-Key must contain 8 to 128 safe characters.")
return hashlib.sha256(f"forge-bootstrap-preflight:{normalized}".encode()).hexdigest()
def _check(name: str, status: str) -> dict[str, str]:
detail = _STATUS_DETAILS.get(name, {}).get(status, _DETAILS[name])
return {"name": name, "status": status, "detail": detail}
def _checked_controller_result(
result: object, profile: ForgeBootstrapPreflightV1
) -> tuple[list[dict[str, str]], dict[str, Any]] | None:
if not isinstance(result, dict) or (
result.get("apiVersion") != _API_VERSION
or result.get("revision") != profile.revision
or result.get("workspaceImageDigest") != profile.workspace_image_digest
or result.get("frontdoorImageDigest") != profile.frontdoor_image_digest
):
return None
raw_checks = result.get("checks")
if not isinstance(raw_checks, dict) or set(raw_checks) != _CONTROLLER_CHECKS:
return None
checks: list[dict[str, str]] = []
for name in sorted(_CONTROLLER_CHECKS):
raw = raw_checks.get(name)
if not isinstance(raw, dict) or set(raw) != {"status"} or raw.get("status") not in {"passed", "blocked", "unknown"}:
return None
# CI, registry access, OIDC and secret delivery are intentionally
# unknown until a configured, source-authenticated proof exists.
if name in {"image_registry", "oidc", "secret_delivery"} and raw["status"] != "unknown":
return None
checks.append(_check(name, raw["status"]))
statuses = [item["status"] for item in checks]
expected_status = "preflight_blocked" if "blocked" in statuses else "preflight_unknown" if "unknown" in statuses else "preflight_ok"
if result.get("status") != expected_status:
return None
raw_evidence = result.get("evidence", {})
if not isinstance(raw_evidence, dict) or set(raw_evidence) - {
"mainRevision", "sourceManifestSha256", "resourceCollisionCount",
"workspaceImagePresent", "frontdoorImagePresent",
}:
return None
evidence: dict[str, Any] = {}
for name in ("mainRevision", "sourceManifestSha256"):
value = raw_evidence.get(name)
if value is not None:
pattern = r"^[0-9a-f]{40}$" if name == "mainRevision" else r"^[0-9a-f]{64}$"
if not isinstance(value, str) or not re.fullmatch(pattern, value):
return None
evidence[name] = value
count = raw_evidence.get("resourceCollisionCount")
if count is not None:
if isinstance(count, bool) or not isinstance(count, int) or not 0 <= count <= 1000:
return None
evidence["resourceCollisionCount"] = count
for name in ("workspaceImagePresent", "frontdoorImagePresent"):
value = raw_evidence.get(name)
if value is not None:
if not isinstance(value, bool):
return None
evidence[name] = value
return checks, evidence
class ForgeBootstrapPreflightService:
"""Read-only fixed Forge checks; this profile has no apply or rollback path."""
def __init__(self, *, broker: DeploymentBrokerClient | None = None):
self.broker = broker or DeploymentBrokerClient()
def preflight(
self,
session: Session,
profile: ForgeBootstrapPreflightV1,
*,
idempotency_key: str,
) -> dict[str, Any]:
payload = profile.model_dump(mode="json")
plan_digest = _canonical_digest(payload)
key_digest = _idempotency_digest(idempotency_key)
existing = session.exec(
select(DeploymentJob).where(DeploymentJob.idempotency_sha256 == key_digest)
).first()
if existing:
if existing.plan_sha256 != plan_digest:
raise ForgeBootstrapPreflightError(
"Idempotency-Key was already used for a different Forge profile."
)
return self.serialize_job(existing, replay=True)
exact = session.exec(
select(Policy).where(
(Policy.action == "deployment.apply") & (Policy.target == _APPLICATION_ID)
)
).all()
decision = evaluate_policy(
session, action="deployment.apply", target=_APPLICATION_ID, source="manual"
)
policy_ready = bool(
len(exact) == 1
and exact[0].manual == "allowed"
and exact[0].voice == "blocked"
and exact[0].risk == "high"
and decision.get("decision") == "allowed"
and decision.get("policy_id") == exact[0].id
)
checks = [_check("deployment_apply_policy", "passed" if policy_ready else "blocked")]
trust = EnrollmentPlane.get_trust_config(session)
expected_root = "/var/lib/docker/volumes/itworx-managed-appdata/_data"
secret = None
if trust is not None:
try:
if has_secret(session, trust.bootstrap_secret_ref):
secret = get_secret(session, trust.bootstrap_secret_ref, touch=False)
except Exception: # noqa: BLE001 - fail closed without exposing secret-store errors.
secret = None
bootstrap_ready = bool(
trust
and isinstance(secret, str)
and len(secret) >= 32
and secret.isascii()
and secret.isprintable()
)
checks.append(_check("controller_trust", "passed" if bootstrap_ready else "blocked"))
root_ready = bool(
trust
and trust.trusted_appdata_root == expected_root
and trust.managed_appdata_backup_acknowledged
)
checks.append(
_check("managed_appdata_root_selection", "passed" if root_ready else "blocked")
)
controller_result = None
if bootstrap_ready:
try:
controller_result = self.broker.forge_bootstrap_preflight(
profile, secret=secret or ""
)
except Exception: # noqa: BLE001 - transport details stay out of the preflight receipt.
controller_result = None
parsed_controller = _checked_controller_result(controller_result, profile)
if parsed_controller is None:
host_checks = [_check(name, "unknown") for name in sorted(_CONTROLLER_CHECKS)]
controller_evidence: dict[str, Any] = {}
else:
host_checks, controller_evidence = parsed_controller
checks.extend(host_checks)
blockers = [item["name"] for item in checks if item["status"] == "blocked"]
unknowns = [item["name"] for item in checks if item["status"] == "unknown"]
outcome = "blocked" if blockers else "unknown" if unknowns else "passed"
job_status = "preflight_succeeded" if outcome == "passed" else "preflight_failed"
now = utc_now()
job = DeploymentJob(
id=f"forge-preflight-{uuid.uuid4().hex[:16]}",
application_id=_APPLICATION_ID,
repository=_REPOSITORY,
revision=profile.revision,
plan_sha256=plan_digest,
idempotency_sha256=key_digest,
status=job_status,
stage="preflight_complete" if outcome == "passed" else "preflight_incomplete",
plan_json=payload,
preflight_json={
"apiVersion": _API_VERSION,
"status": outcome,
"blockers": blockers,
"unknowns": unknowns,
"checks": checks,
},
evidence_json={
"profile": "forge-production-bootstrap",
"workspaceImageDigest": profile.workspace_image_digest,
"frontdoorImageDigest": profile.frontdoor_image_digest,
"controller": controller_evidence,
},
result_summary=(
"Forge bootstrap checks passed; production apply remains separately gated."
if outcome == "passed"
else "Forge bootstrap preflight is incomplete; see blocked and unknown checks."
),
created_at=now,
updated_at=now,
completed_at=now,
)
session.add(job)
session.commit()
session.refresh(job)
log_event(
session,
source="deployment-control-plane",
identity="operator",
action_id="deployment.preflight",
target=_APPLICATION_ID,
target_type="deployment",
risk="high",
policy_decision="allowed" if policy_ready else "blocked",
reason="Fixed Forge bootstrap preflight recorded without requesting a deployment action.",
result="success" if outcome == "passed" else "blocked",
execution_plan={"planSha256": plan_digest, "profile": "forge-production-bootstrap"},
post_checks={"preflight": job.preflight_json},
)
return self.serialize_job(job)
@staticmethod
def serialize_job(job: DeploymentJob, *, replay: bool = False) -> dict[str, Any]:
return {
"id": job.id,
"applicationId": job.application_id,
"repository": job.repository,
"revision": job.revision,
"status": job.status,
"stage": job.stage,
"preflight": job.preflight_json,
"evidence": job.evidence_json,
"result": job.result_summary,
"brokerRequestId": job.broker_request_id,
"createdAt": job.created_at.isoformat(),
"updatedAt": job.updated_at.isoformat(),
"completedAt": job.completed_at.isoformat() if job.completed_at else None,
"idempotentReplay": replay,
}
@staticmethod
def refresh(session: Session, job_id: str) -> dict[str, Any] | None:
job = session.get(DeploymentJob, job_id)
if not job or not job.id.startswith("forge-preflight-"):
return None
return ForgeBootstrapPreflightService.serialize_job(job)
@staticmethod
def list_jobs(session: Session, *, limit: int = 100) -> list[dict[str, Any]]:
rows = session.exec(
select(DeploymentJob)
.where(DeploymentJob.id.startswith("forge-preflight-"))
.order_by(DeploymentJob.created_at.desc())
.limit(max(1, min(limit, 250)))
).all()
return [ForgeBootstrapPreflightService.serialize_job(row) for row in rows]
@@ -29,6 +29,7 @@ def test_controller_promoter_helper_spec_is_fixed_and_bounded():
spec = helper_container_spec(revision=revision, image_id=image_id) spec = helper_container_spec(revision=revision, image_id=image_id)
assert HELPER_NAME == "itworx-controller-artifact-promoter" assert HELPER_NAME == "itworx-controller-artifact-promoter"
assert CONTROLLER_VERSION == "2.1.13"
assert re.fullmatch(r"2\.1\.\d+", CONTROLLER_VERSION) assert re.fullmatch(r"2\.1\.\d+", CONTROLLER_VERSION)
assert spec["Image"] == image_id assert spec["Image"] == image_id
assert spec["HostConfig"]["Privileged"] is False assert spec["HostConfig"]["Privileged"] is False
@@ -53,6 +54,9 @@ def test_controller_promoter_helper_spec_is_fixed_and_bounded():
assert "rm -rf -- \"$work\"" in command assert "rm -rf -- \"$work\"" in command
assert "chmod 0600 \"$work/controller.tar\"" in command assert "chmod 0600 \"$work/controller.tar\"" in command
assert "scripts/prepare_controller_hub_release_volume.py" in command assert "scripts/prepare_controller_hub_release_volume.py" in command
archive_start = command.index('git --git-dir="$repo" archive')
archive_end = command.index('> "$work/controller.tar"', archive_start)
assert "scripts/vendor" in command[archive_start:archive_end]
assert "scripts/upgrade_deployment_controller.sh > \"$work/controller.tar\"" in command assert "scripts/upgrade_deployment_controller.sh > \"$work/controller.tar\"" in command
assert f"--image-id '{image_id}'" in command assert f"--image-id '{image_id}'" in command
@@ -264,6 +264,107 @@ def test_previous_private_tls_is_validated_from_the_attested_config_snapshot(
assert calls == [] assert calls == []
def test_staged_config_checks_run_new_artifact_with_copied_config_and_bounded_output(
tmp_path, monkeypatch
):
stage_root = tmp_path / "source"
stage_root.mkdir(mode=0o700)
release_tree = (
{
"status_server.py": (b"new status server", 0o644),
"controller-release.json": (b"new release identity", 0o644),
},
{"": 0o755},
)
config_tree = (
{
"projects.json": (b"{\"repo\":{}}", 0o600),
"broker-token": (b"private broker token", 0o600),
"gitea-validation.json": (b"{}", 0o600),
"gitea-validation-token": (b"private validation token", 0o600),
"private-tls.json": (b"private TLS config", 0o600),
"webhook-secrets/repo": (b"private webhook secret", 0o600),
},
{"": 0o755, "webhook-secrets": 0o700},
)
calls = []
validation_root = stage_root / "validation"
def run(arguments, *, data=None, max_output=module.MAX_BYTES):
calls.append((arguments, max_output))
assert (validation_root / "status_server.py").read_bytes() == b"new status server"
assert (validation_root / "projects.json").read_bytes() == b"{\"repo\":{}}"
assert (validation_root / "broker-token").read_bytes() == b"private broker token"
assert (validation_root / "webhook-secrets" / "repo").read_bytes() == b"private webhook secret"
return b"validated"
monkeypatch.setattr(module, "_write_private_tree", _write_test_tree)
monkeypatch.setattr(module, "_run", run)
validator = getattr(module, "_validate_staged_configuration", None)
assert callable(validator), "staged configuration validation helper is required"
validator(stage_root, release_tree, config_tree)
assert calls == [
(["python3", str(validation_root / "status_server.py"), "--check-artifact"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-config"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-validation-config"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-validation-api"], 4096),
(["python3", str(validation_root / "status_server.py"), "--check-private-tls-config"], 4096),
]
assert not validation_root.exists()
def test_staged_config_check_failure_is_sanitized_and_cleans_private_validation_tree(
tmp_path, monkeypatch
):
stage_root = tmp_path / "source"
stage_root.mkdir(mode=0o700)
release_tree = (
{
"status_server.py": (b"new status server", 0o644),
"controller-release.json": (b"new release identity", 0o644),
},
{"": 0o755},
)
config_tree = (
{
"projects.json": (b"{}", 0o600),
"broker-token": (b"top-secret-broker", 0o600),
"gitea-validation.json": (b"{}", 0o600),
"gitea-validation-token": (b"top-secret-validation", 0o600),
},
{"": 0o755},
)
class FailedProcess:
returncode = 1
stdout = b"token=top-secret-validation"
stderr = b"secret=top-secret-broker"
monkeypatch.setattr(module, "_write_private_tree", _write_test_tree)
monkeypatch.setattr(module.subprocess, "run", lambda *args, **kwargs: FailedProcess())
validator = getattr(module, "_validate_staged_configuration", None)
assert callable(validator), "staged configuration validation helper is required"
with pytest.raises(module.VolumeTransitionError, match="command_failed") as error:
validator(stage_root, release_tree, config_tree)
assert "top-secret" not in str(error.value)
assert not (stage_root / "validation").exists()
def _write_test_tree(path, entries, directories):
path.mkdir(mode=0o700)
for relative, _mode in sorted(directories.items(), key=lambda item: item[0].count("/")):
if relative:
(path / relative).mkdir(parents=True, exist_ok=True)
for relative, (contents, _mode) in entries.items():
destination = path / relative
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(contents)
@pytest.mark.skipif(os.name == "nt", reason="private TLS snapshot guard requires POSIX symlink semantics") @pytest.mark.skipif(os.name == "nt", reason="private TLS snapshot guard requires POSIX symlink semantics")
def test_previous_private_tls_rejects_symlinked_snapshot(tmp_path): def test_previous_private_tls_rejects_symlinked_snapshot(tmp_path):
previous = tmp_path / "previous-controller" previous = tmp_path / "previous-controller"
@@ -298,12 +399,67 @@ def test_promoted_compose_actions_use_only_stdin_and_fixed_service(compose):
module.compose_action(rendered, "stop") module.compose_action(rendered, "stop")
module.compose_action(rendered, "up") module.compose_action(rendered, "up")
assert calls[-2][0][-2:] == ["stop", module.SERVICE] assert calls[-2][0][-2:] == ["stop", module.SERVICE]
assert calls[-1][0][-5:] == ["up", "-d", "--force-recreate", "--pull", "never", module.SERVICE][-5:] up_index = calls[-1][0].index("up")
assert calls[-1][0][up_index:] == [
"up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE
]
assert calls[-2][1] == rendered assert calls[-2][1] == rendered
assert calls[-1][1] == rendered assert calls[-1][1] == rendered
assert baseline["name"] == "gitea-deploy-runners" assert baseline["name"] == "gitea-deploy-runners"
def test_rollback_uses_no_dependencies_when_recreating_prior_controller(compose, tmp_path, monkeypatch):
_baseline, calls = compose
previous = REVISION
target = "c" * 40
previous_compose = module.rendered_model(ORIGINAL_COMPOSE, previous, IMAGE_ID)
target_compose = module.rendered_model(ORIGINAL_COMPOSE, target, IMAGE_ID)
receipt = {
"phase": "activated",
"imageId": IMAGE_ID,
"previousRevision": previous,
"sourceRevision": target,
}
values = {
"controller-before.tar": module._release_tar_bytes(
previous,
({"controller-release.json": (b"previous release", 0o644)}, {"": 0o755}),
),
"controller-after.tar": b"unused by isolated rollback test",
"mirror-before.tar": b"unused by isolated rollback test",
"compose-rollback.json": previous_compose,
"compose-after.json": target_compose,
}
current = {
"Mounts": [{
"Destination": module.EXECUTABLE_TARGET,
"Source": module._expected_revision_path(previous).as_posix(),
}]
}
monkeypatch.setattr(module, "_load_transaction", lambda _backup: (1, receipt, values))
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(module, "_controller_image_inspect", lambda *args, **kwargs: current)
monkeypatch.setattr(module, "_verify_release_leaf", lambda *args, **kwargs: True)
monkeypatch.setattr(module, "_stop_current_controller", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_sync_controller_mirror", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_ensure_root_compose", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_verify_compose_image", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_verify_active_mount", lambda *args, **kwargs: None)
monkeypatch.setattr(module, "_set_promotion_phase", lambda *args, **kwargs: None)
module.rollback_promotion(tmp_path / "backup")
up_calls = [
arguments for arguments, _data in calls
if arguments[:2] == ["docker", "compose"] and "up" in arguments
]
assert len(up_calls) == 1
up_index = up_calls[0].index("up")
assert up_calls[0][up_index:] == [
"up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE
]
def test_installer_has_no_shared_executable_stage_or_compose_path(): def test_installer_has_no_shared_executable_stage_or_compose_path():
source = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8") source = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8")
assert "mktemp -d -p /tmp \"itworx-controller-upgrade-" in source assert "mktemp -d -p /tmp \"itworx-controller-upgrade-" in source
@@ -476,6 +632,11 @@ def test_source_compose_preflight_rejects_unresolvable_alias_keys():
module._preflight_source_compose(source) module._preflight_source_compose(source)
def test_source_compose_preflight_bounds_recursive_list_aliases():
source = b"services: {app: {image: fixed}}\nx-loop: &a [*a]\n"
module._preflight_source_compose(source)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"source", "source",
[ [
@@ -53,3 +53,14 @@ def test_windows_wrapper_supplies_private_archive_and_exact_image_identity():
assert "--image-id '$imageId'" in wrapper assert "--image-id '$imageId'" in wrapper
assert "--image-id) image_id=\"$2\"" in installer assert "--image-id) image_id=\"$2\"" in installer
assert '[[ "$archive_parent" =~ ^/tmp/itworx-controller-artifact-promoter-' in installer assert '[[ "$archive_parent" =~ ^/tmp/itworx-controller-artifact-promoter-' in installer
def test_state_reconciliation_is_committed_only_after_volume_verification():
installer = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8")
verify = installer.index('python3 "${work}/${PREPARER_NAME}" verify --backup "$backup"')
commit = installer.index(
'python3 "${work}/reconcile_controller_state.py" --journal-path "$migration_journal" --commit'
)
assert verify < commit
assert installer.index("reconciliation_active=0", commit) > commit
@@ -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
)
@@ -0,0 +1,214 @@
from __future__ import annotations
from sqlmodel import Session, SQLModel, create_engine
from backend.app.db.models import DeploymentJob
from backend.app.schemas.enrollment import EnrollmentTrustConfigV1
from backend.app.schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from backend.app.services.enrollment_plane import EnrollmentPlane
from backend.app.services.forge_bootstrap_preflight import (
ForgeBootstrapPreflightService,
)
def _profile(**overrides):
value = {
"api_version": "itworx.forge-bootstrap-preflight/v1",
"revision": "a" * 40,
"workspace_image_digest": "sha256:" + "b" * 64,
"frontdoor_image_digest": "sha256:" + "c" * 64,
}
value.update(overrides)
return ForgeBootstrapPreflightV1.model_validate(value)
def _session(tmp_path):
engine = create_engine(f"sqlite:///{tmp_path / 'forge-preflight.db'}")
SQLModel.metadata.create_all(engine)
return Session(engine)
def test_forge_profile_is_strict_and_cannot_select_runtime_inputs():
_profile()
for key, value in (
("host_path", "/etc"),
("endpoint", "https://attacker.invalid"),
("secret_ref", "OTHER_SECRET"),
("command", "docker compose up"),
("repository", "other/repo"),
("target", "other-host"),
):
try:
_profile(**{key: value})
except ValueError:
pass
else: # pragma: no cover - assertion clarity
raise AssertionError(f"caller-controlled {key} must be rejected")
for field, value in (
("revision", "a" * 39),
("workspace_image_digest", "latest"),
("frontdoor_image_digest", "sha256:" + "D" * 64),
):
try:
_profile(**{field: value})
except ValueError:
pass
else: # pragma: no cover - assertion clarity
raise AssertionError(f"invalid {field} must be rejected")
class _Broker:
def __init__(self, result):
self.result = result
self.calls = []
def forge_bootstrap_preflight(self, profile, *, secret):
self.calls.append((profile.model_dump(mode="json"), secret))
return self.result
def _trust(session, *, root="/var/lib/docker/volumes/itworx-managed-appdata/_data"):
EnrollmentPlane.set_trust_config(
session,
EnrollmentTrustConfigV1(
controller_identity="tower-deployment-controller",
bootstrap_secret_ref="CONTROLLER_BOOTSTRAP",
trusted_appdata_root=root,
managed_appdata_backup_acknowledged=(
root == "/var/lib/docker/volumes/itworx-managed-appdata/_data"
),
approved_networks=("itworx-mcp-hub-npm",),
),
)
def test_forge_preflight_uses_stored_trust_and_preserves_unknowns(tmp_path, monkeypatch):
from backend.app.services import forge_bootstrap_preflight as service_module
broker = _Broker(
{
"apiVersion": "itworx.forge-bootstrap-preflight/v1",
"status": "preflight_blocked",
"revision": "a" * 40,
"workspaceImageDigest": "sha256:" + "b" * 64,
"frontdoorImageDigest": "sha256:" + "c" * 64,
"checks": {
"exact_main": {"status": "passed"},
"source_manifest": {"status": "passed"},
"managed_appdata": {"status": "blocked"},
"proxy_network": {"status": "passed"},
"resource_collisions": {"status": "passed"},
"push_validation": {"status": "unknown"},
"image_registry": {"status": "unknown"},
"oidc": {"status": "unknown"},
"secret_delivery": {"status": "unknown"},
},
}
)
monkeypatch.setattr(service_module, "get_secret", lambda *_args, **_kwargs: "s" * 48)
monkeypatch.setattr(service_module, "has_secret", lambda *_args, **_kwargs: True)
with _session(tmp_path) as session:
_trust(session)
result = ForgeBootstrapPreflightService(broker=broker).preflight(
session, _profile(), idempotency_key="forge-preflight-test-0001"
)
assert result["status"] == "preflight_failed"
assert result["preflight"]["status"] == "blocked"
assert "push_validation" in {
item["name"] for item in result["preflight"]["checks"]
if item["status"] == "unknown"
}
assert broker.calls == [(_profile().model_dump(mode="json"), "s" * 48)]
job = session.get(DeploymentJob, result["id"])
assert job is not None
assert job.status == "preflight_failed"
assert ForgeBootstrapPreflightService().refresh(session, job.id)["id"] == job.id
assert any(item["id"] == job.id for item in ForgeBootstrapPreflightService.list_jobs(session))
def test_forge_preflight_idempotency_rejects_profile_change(tmp_path, monkeypatch):
from backend.app.services import forge_bootstrap_preflight as service_module
broker = _Broker(
{
"apiVersion": "itworx.forge-bootstrap-preflight/v1",
"status": "preflight_unknown",
"revision": "a" * 40,
"workspaceImageDigest": "sha256:" + "b" * 64,
"frontdoorImageDigest": "sha256:" + "c" * 64,
"checks": {
"exact_main": {"status": "unknown"},
"source_manifest": {"status": "unknown"},
"managed_appdata": {"status": "unknown"},
"proxy_network": {"status": "unknown"},
"resource_collisions": {"status": "unknown"},
"push_validation": {"status": "unknown"},
"image_registry": {"status": "unknown"},
"oidc": {"status": "unknown"},
"secret_delivery": {"status": "unknown"},
},
}
)
monkeypatch.setattr(service_module, "get_secret", lambda *_args, **_kwargs: "s" * 48)
monkeypatch.setattr(service_module, "has_secret", lambda *_args, **_kwargs: True)
with _session(tmp_path) as session:
_trust(session)
service = ForgeBootstrapPreflightService(broker=broker)
service.preflight(session, _profile(), idempotency_key="forge-preflight-test-0002")
replay = service.preflight(session, _profile(), idempotency_key="forge-preflight-test-0002")
assert replay["idempotentReplay"] is True
assert len(broker.calls) == 1
try:
service.preflight(
session,
_profile(revision="d" * 40),
idempotency_key="forge-preflight-test-0002",
)
except ValueError as exc:
assert "different Forge profile" in str(exc)
else: # pragma: no cover
raise AssertionError("idempotency key reuse with changed Forge profile must fail")
def test_legacy_storage_trust_still_collects_read_only_controller_blockers(tmp_path, monkeypatch):
from backend.app.services import forge_bootstrap_preflight as service_module
checks = {
name: {"status": "unknown"}
for name in (
"exact_main", "source_manifest", "managed_appdata", "proxy_network",
"resource_collisions", "push_validation", "image_registry", "oidc",
"secret_delivery",
)
}
checks["managed_appdata"] = {"status": "blocked"}
broker = _Broker(
{
"apiVersion": "itworx.forge-bootstrap-preflight/v1",
"status": "preflight_blocked",
"revision": "a" * 40,
"workspaceImageDigest": "sha256:" + "b" * 64,
"frontdoorImageDigest": "sha256:" + "c" * 64,
"checks": checks,
}
)
monkeypatch.setattr(service_module, "get_secret", lambda *_args, **_kwargs: "s" * 48)
monkeypatch.setattr(service_module, "has_secret", lambda *_args, **_kwargs: True)
with _session(tmp_path) as session:
_trust(session, root="/srv/example/appdata")
result = ForgeBootstrapPreflightService(broker=broker).preflight(
session, _profile(), idempotency_key="forge-legacy-trust-0001"
)
assert broker.calls == [(_profile().model_dump(mode="json"), "s" * 48)]
assert "managed_appdata_root_selection" in result["preflight"]["blockers"]
controller_checks = {
item["name"]: item["status"] for item in result["preflight"]["checks"]
}
assert controller_checks["managed_appdata"] == "blocked"
assert controller_checks["proxy_network"] == "unknown"
@@ -231,6 +231,59 @@ def test_n8n_uses_the_shared_grounded_knowledge_service(integration_client):
) )
def test_forge_bootstrap_preflight_is_read_only_and_rejects_generic_apply(integration_client):
client, _engine = integration_client
from backend.app import mcp_server
profile = {
"api_version": "itworx.forge-bootstrap-preflight/v1",
"revision": "a" * 40,
"workspace_image_digest": "sha256:" + "b" * 64,
"frontdoor_image_digest": "sha256:" + "c" * 64,
}
response = client.post(
"/api/deployments/forge-bootstrap/preflight",
headers={**human_headers(client), "Idempotency-Key": "forge-api-preflight-0001"},
json=profile,
)
assert response.status_code == 200
result = response.json()
assert result["status"] == "preflight_failed"
assert result["preflight"]["status"] == "blocked"
assert "deployment_apply_policy" in result["preflight"]["blockers"]
assert "controller_trust" in result["preflight"]["blockers"]
refreshed = client.get(
f"/api/deployments/{result['id']}", headers=human_headers(client)
)
assert refreshed.status_code == 200
assert refreshed.json()["preflight"]["status"] == "blocked"
listed = client.get("/api/deployments", headers=human_headers(client))
assert any(item["id"] == result["id"] for item in listed.json()["items"])
generic_apply = client.post(
"/api/deployments",
headers={**human_headers(client), "Idempotency-Key": "forge-generic-apply-0001"},
json={"operation": "apply", "plan": profile},
)
assert generic_apply.status_code == 422
async def exercise_mcp():
async with Client(mcp_server.appops_mcp) as mcp_client:
preflight = await mcp_client.call_tool(
"deployment.preflight",
{"plan": profile, "idempotencyKey": "forge-mcp-preflight-0001"},
)
assert preflight.structured_content["preflight"]["status"] == "blocked"
apply = await mcp_client.call_tool(
"deployment.apply",
{"plan": profile, "idempotencyKey": "forge-mcp-apply-0001"},
)
assert apply.structured_content["status"] == "invalid"
asyncio.run(exercise_mcp())
def test_local_rag_indexes_allowlisted_docs_and_redacts_secrets(tmp_path): def test_local_rag_indexes_allowlisted_docs_and_redacts_secrets(tmp_path):
from backend.app.services import knowledge_service from backend.app.services import knowledge_service