Publish curated Unraid-AppOps-Gateway source from 451646fee806a47e3256731766980a05a262dcdd

This commit is contained in:
Public Source Publisher committed 2026-10-04 05:23:27 +00:00
1 parent 532f3d9f26
commit 0a82153d95
43 files changed
+17722 -93

No files matched your search

+32
View File
@@ -6,8 +6,40 @@ description = "Pydantic enrollment secrets container declaration, no secret valu
paths = ['backend/app/schemas/enrollment\.py$']
regexTarget = "line"
regexes = ['secrets: EnrollmentSecretsV1 = EnrollmentSecretsV1\(\)']
condition = "AND"
targetRules = ["generic-api-key"]
[[allowlists]]
description = "Synthetic noncredential test encryption key and idempotency/reason fixtures"
paths = ['backend/tests/test_(repopulse_adoption_runner|deployment_control_plane|enrollment_plane|mcp_rag_n8n_integrations)\.py$']
regexTarget = "line"
regexes = ['appops-test-key-with-at-least-32-bytes', 'apply-wsbc-(direct|evidence)-0001', 'enroll-wsbc-(interrupted|compensate)-0001', 'N8N-REASON-SYNTHETIC']
condition = "AND"
targetRules = ["generic-api-key"]
[[allowlists]]
description = "Synthetic forge bootstrap idempotency race fixture only"
paths = ['backend/tests/test_forge_bootstrap_apply\.py$']
regexTarget = "line"
regexes = ['^\s*key = "forge-integrity-race-0001"\r?$']
condition = "AND"
targetRules = ["generic-api-key"]
[[allowlists]]
description = "Synthetic Forge OIDC API secret-key setting fixture only"
paths = ['backend/tests/test_forge_oidc_api\.py$']
regexTarget = "line"
regexes = ['^\s*monkeypatch\.setattr\(settings, "secret_key", "forge-oidc-api-secret-key-material-0123456789"\)\r?$']
condition = "AND"
targetRules = ["generic-api-key"]
[[allowlists]]
description = "Synthetic Forge secret-delivery secret-key setting fixture only"
paths = ['backend/tests/test_forge_secret_delivery\.py$']
regexTarget = "line"
regexes = ['^\s*monkeypatch\.setattr\(settings, "secret_key", "delivery-test-secret-key-material-0123456789"\)\r?$']
condition = "AND"
targetRules = ["generic-api-key"]
[[allowlists]]
description = "Synthetic MCP apply idempotency fixture only"
paths = ['backend/tests/test_mcp_rag_n8n_integrations\.py$']
regexTarget = "line"
regexes = ['^\s*\{"plan": profile, "idempotencyKey": "forge-mcp-apply-0001"\},\r?$']
condition = "AND"
targetRules = ["generic-api-key"]
+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export
This source snapshot was generated from the private canonical repository at revision `e854334923cca574482d22b8424ba3ce46ec6aa1`. 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 `451646fee806a47e3256731766980a05a262dcdd`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+137 -47
View File
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"sourceRevision": "e854334923cca574482d22b8424ba3ce46ec6aa1",
"sourceRevision": "451646fee806a47e3256731766980a05a262dcdd",
"files": [
{
"path": ".dockerignore",
@@ -24,8 +24,8 @@
},
{
"path": ".gitleaks.toml",
"sha256": "48380b6edbe1185d5a3e2c95f065ae94c909a5993148cd1e6666ac52aa3e4ad2",
"bytes": 761
"sha256": "e631ebe31e0b4fc034379618fc0b16c2a239015b087eab4d3a57a81f74c384de",
"bytes": 2107
},
{
"path": "CHANGELOG.md",
@@ -104,8 +104,8 @@
},
{
"path": "backend/app/api/routes_authentik.py",
"sha256": "29834b61c6661af4cf6b0a7376e033f34b206170f7d16af774b6252a1319d387",
"bytes": 2105
"sha256": "5a723b8426167791377db18494e40e9d901a600d74068b44857118fc064a047a",
"bytes": 4569
},
{
"path": "backend/app/api/routes_connectors.py",
@@ -124,8 +124,8 @@
},
{
"path": "backend/app/api/routes_deployments.py",
"sha256": "0441f74d508dde2ca0641cae32b8a7f386496c8f6f0f2df80ec619a6409bc9b3",
"bytes": 4955
"sha256": "c77cea58701694af4ef519e351fd27a68856e45cd4e61dc4028c652359d6392a",
"bytes": 7717
},
{
"path": "backend/app/api/routes_enrollments.py",
@@ -279,8 +279,8 @@
},
{
"path": "backend/app/connectors/authentik.py",
"sha256": "72e21c055ff6344c90a4c6d74f920d340332e2559e0e59aebb2f2bc2c91d1604",
"bytes": 41931
"sha256": "2bac45ebbee05c3878ec6eb03e92145d971ae9ae875ed7d90ba609e1329e38b2",
"bytes": 92086
},
{
"path": "backend/app/connectors/base.py",
@@ -354,8 +354,8 @@
},
{
"path": "backend/app/core/config.py",
"sha256": "83441a676b4e6f415f80b283897bb613805ecbf82a6d405d767532e086dbc5fc",
"bytes": 25697
"sha256": "9a839001e681edb19926a45e859c5103b398cc97922d20042fa739cfcbf84126",
"bytes": 27341
},
{
"path": "backend/app/core/network_security.py",
@@ -384,8 +384,8 @@
},
{
"path": "backend/app/db/init_db.py",
"sha256": "0e68b799a282d33dae139308c40ab6ec10d9ca7aba7c0bd123e4b9adbdc57b8d",
"bytes": 69933
"sha256": "450abf3cdf8c8897938cffeef193d69f7fc0fb1d4cd7a713597db1ca5bb811a7",
"bytes": 70586
},
{
"path": "backend/app/db/models.py",
@@ -404,8 +404,8 @@
},
{
"path": "backend/app/mcp_server.py",
"sha256": "7cb748e5350231bfe938c91867b7eef408dd3589ba4e7ec078e07589338554c6",
"bytes": 35626
"sha256": "aa1246a23ba269a0e52cb5f95d0cd3bde8e4e5aa3943872a0a4059dbaa441f18",
"bytes": 38109
},
{
"path": "backend/app/runner_statistics_mcp.py",
@@ -442,6 +442,21 @@
"sha256": "12db5aa43594bbcc9e4306a173769afdeabb86d364e0939e637b80775ea73629",
"bytes": 1208
},
{
"path": "backend/app/schemas/forge_bootstrap_apply.py",
"sha256": "113f92c4571bd1733bb9b1fc39d49dea0b4a97d0ebef6af8379f29ecb31649cb",
"bytes": 1977
},
{
"path": "backend/app/schemas/forge_oidc.py",
"sha256": "8d55f7a8263191f07813d1689eb0697dabec99a1bbd5aff5a704e66e6bf86c47",
"bytes": 6045
},
{
"path": "backend/app/schemas/forge_secret_delivery.py",
"sha256": "5209fdfaabee4dbc56b7edd2f6080c35bbd3d679577a754dbd65ca353347b5e5",
"bytes": 3015
},
{
"path": "backend/app/services/__init__.py",
"sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
@@ -454,8 +469,8 @@
},
{
"path": "backend/app/services/action_registry.py",
"sha256": "11add43608abc05d51d89d29d772fe17f6d429a032c471376ad8f6d8400976b2",
"bytes": 21035
"sha256": "a5c575ee262ca721906a65156383ae2d1c2b08c52b2a5f9145c841894496c31f",
"bytes": 21316
},
{
"path": "backend/app/services/ai_remediation_planner.py",
@@ -514,8 +529,8 @@
},
{
"path": "backend/app/services/deployment_broker.py",
"sha256": "829fe26916967dad3c6907989a5a8825a0aabbc294aa2cf600e701bc76c5e1e3",
"bytes": 24904
"sha256": "34d6a4bf90d59604d51d34bc4ba7885ac6f5837d7015c80c8c0fe7eee9c060d4",
"bytes": 33010
},
{
"path": "backend/app/services/deployment_control_plane.py",
@@ -547,10 +562,30 @@
"sha256": "36cf235e02e4289b7b1851bb6240584bf8d6f6fbb9b2ae22ac521ec8012ba025",
"bytes": 2538
},
{
"path": "backend/app/services/forge_bootstrap_apply.py",
"sha256": "4928e08a887094efb9134fc2c6a40b707d5635c0f26d202c93773ba132b61f69",
"bytes": 37394
},
{
"path": "backend/app/services/forge_bootstrap_preflight.py",
"sha256": "b5ced0a85cc1585386fec21cc1cfa5bc88b5ccecf211beff93f99d79736f413f",
"bytes": 14808
"sha256": "b86dbfb578d881e60b6a147d7095c255a734b38ce35e1e940a24d27d02576640",
"bytes": 19371
},
{
"path": "backend/app/services/forge_controller_reader.py",
"sha256": "fbffc3974b399810c86e22160ae49a7325d296afc57fd451446f9fa74c78d204",
"bytes": 67909
},
{
"path": "backend/app/services/forge_oidc_provisioning.py",
"sha256": "25d32b9f1acfdf2a2f4032b242f8843ba0e8a48ab1c2c0cf4f048181906c0af1",
"bytes": 31091
},
{
"path": "backend/app/services/forge_secret_delivery.py",
"sha256": "a0f16bd7745f30d54781c10808ff076b45a9db0c432eeaa36ea6947bf654c17e",
"bytes": 23765
},
{
"path": "backend/app/services/gitea_project_auto_registration.py",
@@ -649,8 +684,8 @@
},
{
"path": "backend/app/services/policy_engine.py",
"sha256": "8213e8d213498b58c41bf7a9ae2a905e814b6ad9b6cfad2d7752df69d444634b",
"bytes": 10925
"sha256": "d00f8d1be911d49519791a5c517d261a666a9ff04bb037954501a7f79a1b4dcf",
"bytes": 12023
},
{
"path": "backend/app/services/portfolio_deployment_enrollment.py",
@@ -769,8 +804,8 @@
},
{
"path": "backend/app/services/setup_service.py",
"sha256": "d0589c176a3becba804885b2473eaa86312844faf9042f5b3c13c1987ff25383",
"bytes": 27766
"sha256": "deba3682594002b307c0a0c9a0c473c07ce6010036dc970109f4430458bf91e8",
"bytes": 30316
},
{
"path": "backend/app/services/unraid_vm_service.py",
@@ -854,8 +889,8 @@
},
{
"path": "backend/tests/test_controller_artifact_bootstrap.py",
"sha256": "09b4691f9505d561bc63e1383ea199d5dcb090dac4e42baac062c03fd4b09421",
"bytes": 48924
"sha256": "2694d9e601bb06aaabc9491c85fd7a17e796f20ea089792cb964c4a6fe93aaf6",
"bytes": 60349
},
{
"path": "backend/tests/test_controller_artifact_promoter.py",
@@ -879,18 +914,18 @@
},
{
"path": "backend/tests/test_controller_hub_release_volume.py",
"sha256": "704ed05a58455bc051ce598dfbaa4787b5337d8910e852621165bfc424c4c0e9",
"bytes": 45100
"sha256": "2b89660fe44b8c5a46a4cad6b9cb68ac34f0716b4e72ac171830dcf61b0ac2c8",
"bytes": 73499
},
{
"path": "backend/tests/test_controller_root_procedure_contract.py",
"sha256": "ff2c7b80c3bc71b1ab06fc5637fce0c2c73df78e6d6123a47a34b3036f3caa8c",
"bytes": 3299
"sha256": "964b3e21828a9d815d05ffb0e51acf6e9e45efe56af9fa3b70f39200d3a5c336",
"bytes": 5125
},
{
"path": "backend/tests/test_controller_state_reconciliation.py",
"sha256": "d9ddea9971414954a0b34738ef8de6bb330a867f7f41376063e473855ff5d17e",
"bytes": 10957
"sha256": "d1d90f6d4fe35e434797dccd2c0ec10357fc347900e6e148c0f85face9d12598",
"bytes": 11084
},
{
"path": "backend/tests/test_deluge_connector.py",
@@ -899,8 +934,8 @@
},
{
"path": "backend/tests/test_deploy_broker.py",
"sha256": "56dffec32ac7383b9d9cf0f82bfb7e586458cb2780bfefb0bfc5785b97bf4736",
"bytes": 381065
"sha256": "9aaefa351db5868436a6dc06ed7c42202e8d1a8568bcaa4d71501ec60446bb85",
"bytes": 381318
},
{
"path": "backend/tests/test_deployment_auto_rollback.py",
@@ -942,15 +977,70 @@
"sha256": "ddd08c246eb0719556f307691ca231093584dc4b85e78bb849bee83ccbde8b1f",
"bytes": 8374
},
{
"path": "backend/tests/test_forge_backup_paths.py",
"sha256": "9e8a95f438e9863be4a9e84e0a189bebd84eea2e3991e9e6099a2b79920e9898",
"bytes": 12018
},
{
"path": "backend/tests/test_forge_bootstrap_apply.py",
"sha256": "f7be48b418a9a09fbbaea7f6e1765c7ba4bac949994435b423885c3b4b2c6d8e",
"bytes": 43558
},
{
"path": "backend/tests/test_forge_bootstrap_controller.py",
"sha256": "442a57109bc2dc0e4d4b9eb50c84ae8bac5685f94c9ec41fcfdadd0911352e44",
"bytes": 10829
"sha256": "eff595a5af1453fb428243ef8a9727caa748eb2f8f6023c2601abdc8be48a937",
"bytes": 11068
},
{
"path": "backend/tests/test_forge_bootstrap_preflight.py",
"sha256": "b9ef73cd20bcdb99eb4afe42d26604397e82258f24f4b5dbc25d182c1c6a95da",
"bytes": 8518
"sha256": "e92678fe83679ac1614e4bb48ab77ac565a0d1da8a265ea30f9760a2b64592aa",
"bytes": 11186
},
{
"path": "backend/tests/test_forge_controller_reader.py",
"sha256": "e32672659a12a1389220f1725a79f250e94e8dd410ef194c5fdf4c6de4a1c03d",
"bytes": 42939
},
{
"path": "backend/tests/test_forge_database_credentials.py",
"sha256": "fdab22c57d546a7a1bd912abc63777ca4a1e03c3826ddcebb9301553130c42a3",
"bytes": 17599
},
{
"path": "backend/tests/test_forge_first_install_controller.py",
"sha256": "9c4abc31c564e2974b125ce0c0f9bd581af499ab70dcc2b21dc2824a331522f2",
"bytes": 84984
},
{
"path": "backend/tests/test_forge_oidc_api.py",
"sha256": "4d902a3bb967de92156019d00ca65cfb7120935f669c61d255cab630b3081100",
"bytes": 11095
},
{
"path": "backend/tests/test_forge_oidc_provisioning.py",
"sha256": "a0fff62f8855dafebd49e2d91abdf8f211b7a4df452c05d91ed8305fe74d3b0a",
"bytes": 82116
},
{
"path": "backend/tests/test_forge_reader_api.py",
"sha256": "dc4e9d176a9b4c33fd24e27b2cb83919040babe412d021109a22de41c54edb8b",
"bytes": 6424
},
{
"path": "backend/tests/test_forge_secret_bundle.py",
"sha256": "ee3555f9711b23005f8a2970537217ffed9b06b1fbe96424180a26c379440cd2",
"bytes": 25851
},
{
"path": "backend/tests/test_forge_secret_delivery.py",
"sha256": "29f138ab1df97248cd3393aac84e50967e4cf10b2194a8db03820dd7973a30fb",
"bytes": 37715
},
{
"path": "backend/tests/test_forge_secret_delivery_controller.py",
"sha256": "1695383ed99777000bec2df4ae97d252dc242b690430f102284fa101679465ed",
"bytes": 31582
},
{
"path": "backend/tests/test_frontend_file_boundary.py",
@@ -1074,8 +1164,8 @@
},
{
"path": "backend/tests/test_mcp_rag_n8n_integrations.py",
"sha256": "89f557049d6e2c65fada94321503df65bf7c82bd567038241ea3bdad7c866ed1",
"bytes": 50584
"sha256": "0c90431bb77eab27f0da21afd02dd5fb232141483062f7a19c3340d1d91f6c9d",
"bytes": 52814
},
{
"path": "backend/tests/test_media_codec_dashboard_deployment_reconciliation.py",
@@ -1309,8 +1399,8 @@
},
{
"path": "backend/tests/test_runner_capacity_controller.py",
"sha256": "fbc1025db04ab98f5aaeee7c94bb6b5dfb1a5652bfe696d92f2ac5f631620448",
"bytes": 18051
"sha256": "f1c12a7799f11b7dcb78e79820303a5dce6828dded26b258898e4c80f80a3ed8",
"bytes": 21587
},
{
"path": "backend/tests/test_runner_capacity_policy.py",
@@ -1339,8 +1429,8 @@
},
{
"path": "backend/tests/test_runner_fleet_specialize.py",
"sha256": "e2b142ed5656e118156d44baf6f1f8568ba25297ea9af13b817861ce3c4fd4da",
"bytes": 107149
"sha256": "3329a6ac547afa73b0804caad9001a0cf7de0755b6d355e3139cb46d550fccf5",
"bytes": 111208
},
{
"path": "backend/tests/test_runner_fleet_v2_execution_surface.py",
@@ -1399,8 +1489,8 @@
},
{
"path": "backend/tests/test_setup_onboarding.py",
"sha256": "c6160e0e163e23b640886bdbbe21c8d6ec4cebf4b102e00f0e78072c85586464",
"bytes": 12566
"sha256": "8b52ec3202d4c99a06d7610fef5b2b418c68e29a3ed36cf1dbb5335ba07c04f3",
"bytes": 14465
},
{
"path": "backend/tests/test_setup_reconciliation.py",
+63 -16
View File
@@ -1,13 +1,23 @@
from __future__ import annotations
from typing import Any
from typing import Annotated, Any
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Request
from sqlmodel import Session
from ..connectors.authentik import AuthentikConnector
from ..core.security import require_operator_request
from ..db.session import get_session
from ..schemas.forge_oidc import ForgeOidcProvisionRequestV1, ForgeOidcProvisionResultV1
from ..schemas.forge_secret_delivery import (
ForgeControllerReaderProvisionRequestV1,
ForgeControllerReaderProvisionResultV1,
)
from ..services.forge_controller_reader import provision_forge_controller_reader
from ..services.forge_oidc_provisioning import provision_forge_oidc
from .connector_runtime import load_connector_runtime
router = APIRouter(prefix='/api/apps/authentik', dependencies=[Depends(load_connector_runtime)])
router = APIRouter(prefix='/api/apps/authentik')
def _shape(payload: dict[str, Any]) -> dict[str, Any]:
@@ -27,66 +37,103 @@ def _connector() -> AuthentikConnector:
return AuthentikConnector()
@router.get('/health')
def _require_forge_oidc_operator(request: Request) -> str:
return require_operator_request(request)
@router.get('/health', dependencies=[Depends(load_connector_runtime)])
def health():
return _shape(_connector().health())
@router.get('/system')
@router.get('/system', dependencies=[Depends(load_connector_runtime)])
def system_status():
return _shape(_connector().system_status())
@router.get('/applications')
@router.get('/applications', dependencies=[Depends(load_connector_runtime)])
def applications():
return _shape(_connector().applications())
@router.get('/providers')
@router.get('/providers', dependencies=[Depends(load_connector_runtime)])
def providers():
return _shape(_connector().providers())
@router.get('/providers/proxy')
@router.get('/providers/proxy', dependencies=[Depends(load_connector_runtime)])
def proxy_providers():
return _shape(_connector().proxy_providers())
@router.get('/providers/oauth2')
@router.get('/providers/oauth2', dependencies=[Depends(load_connector_runtime)])
def oauth2_providers():
return _shape(_connector().oauth2_providers())
@router.get('/outposts')
@router.get('/outposts', dependencies=[Depends(load_connector_runtime)])
def outposts():
return _shape(_connector().outposts())
@router.get('/outposts/health')
@router.get('/outposts/health', dependencies=[Depends(load_connector_runtime)])
def outpost_health():
return _shape(_connector().outpost_health())
@router.get('/flows')
@router.get('/flows', dependencies=[Depends(load_connector_runtime)])
def flows_summary():
return _shape(_connector().flows_summary())
@router.get('/policies')
@router.get('/policies', dependencies=[Depends(load_connector_runtime)])
def policies_summary():
return _shape(_connector().policies_summary())
@router.get('/events')
@router.get('/events', dependencies=[Depends(load_connector_runtime)])
def events(limit: int = 25):
return _shape(_connector().events(limit))
@router.get('/auth-coverage')
@router.get('/auth-coverage', dependencies=[Depends(load_connector_runtime)])
def auth_coverage():
return _shape(_connector().auth_coverage())
@router.get('/security-warnings')
@router.get('/security-warnings', dependencies=[Depends(load_connector_runtime)])
def security_warnings():
return _shape(_connector().security_warnings())
@router.post(
'/forge/oidc/provision',
response_model=ForgeOidcProvisionResultV1,
)
def provision_forge_oidc_application(
payload: ForgeOidcProvisionRequestV1,
request: Request,
session: Annotated[Session, Depends(get_session)],
actor_identity: Annotated[str, Depends(_require_forge_oidc_operator)],
):
"""Run the fixed, manually authorized Forge OIDC provisioning action."""
# The payload is intentionally empty: callers cannot select a provider,
# redirect URI, mapping, groups, or secret. The service repeats the exact
# policy check before making any connector request.
_ = payload
return provision_forge_oidc(session, actor_identity=actor_identity)
@router.post(
'/forge/controller-reader/provision',
response_model=ForgeControllerReaderProvisionResultV1,
)
def provision_forge_controller_reader_account(
payload: ForgeControllerReaderProvisionRequestV1,
request: Request,
session: Annotated[Session, Depends(get_session)],
actor_identity: Annotated[str, Depends(_require_forge_oidc_operator)],
):
"""Provision only the fixed read-only Forge controller service account."""
_ = payload, request
return provision_forge_controller_reader(session, actor_identity=actor_identity)
+76
View File
@@ -16,6 +16,11 @@ from ..core.security import require_operator_request
from ..db.session import get_session
from ..schemas.deployment import DeploymentPlanV1
from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from ..schemas.forge_bootstrap_apply import ForgeBootstrapApplyV1
from ..schemas.forge_secret_delivery import (
ForgeSecretDeliveryRequestV1,
ForgeSecretDeliveryResultV1,
)
from ..services.deployment_broker import DeploymentBrokerClient
from ..services.deployment_control_plane import (
DeploymentControlError,
@@ -117,6 +122,62 @@ def preflight_forge_bootstrap(
raise HTTPException(status_code=409, detail=str(exc)) from exc
@router.post(
"/forge-bootstrap/secrets",
response_model=ForgeSecretDeliveryResultV1,
)
def prepare_forge_bootstrap_secrets(
payload: ForgeSecretDeliveryRequestV1,
actor_identity: Annotated[str, Depends(_require_deployment_operator)],
session: DeploymentSession,
idempotency_key: DeploymentIdempotencyKey = None,
):
"""Deliver only the verified protected Forge inputs to the fixed controller."""
from ..services.forge_secret_delivery import prepare_forge_secret_delivery
try:
return prepare_forge_secret_delivery(
session,
profile=payload.profile,
idempotency_key=_idempotency_header(idempotency_key),
actor_identity=actor_identity,
)
except ValueError as exc:
# Service messages are fixed safe codes; never surface upstream text.
raise HTTPException(status_code=409, detail="forge_secret_delivery_failed") from exc
@router.post("/forge-bootstrap/first-install")
def apply_forge_first_install(
payload: ForgeBootstrapApplyV1,
session: DeploymentSession,
idempotency_key: DeploymentIdempotencyKey = None,
):
from ..services.forge_bootstrap_apply import (
ForgeBootstrapApplyError,
ForgeBootstrapApplyService,
)
try:
return ForgeBootstrapApplyService().apply(
session,
payload,
idempotency_key=_idempotency_header(idempotency_key),
)
except ForgeBootstrapApplyError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
@router.post("/forge-bootstrap/first-install/{job_id}/resume")
def resume_forge_first_install(job_id: str, session: DeploymentSession):
from ..services.forge_bootstrap_apply import ForgeBootstrapApplyService
result = ForgeBootstrapApplyService().resume(session, job_id)
if result is None:
raise HTTPException(status_code=404, detail="Forge first-install request not found.")
return result
@router.get("/workmesh/promotions/{promotion_id}")
def get_workmesh_promotion(promotion_id: str, session: DeploymentSession):
from ..services.workmesh_promotion import WorkMeshPromotionService
@@ -137,6 +198,16 @@ def list_workmesh_promotions(
return {"items": WorkMeshPromotionService.list_promotions(session, limit=limit)}
@router.get("/forge-bootstrap/first-install/{job_id}")
def get_forge_first_install(job_id: str, session: DeploymentSession):
from ..services.forge_bootstrap_apply import ForgeBootstrapApplyService
result = ForgeBootstrapApplyService().status(session, job_id)
if result is None:
raise HTTPException(status_code=404, detail="Forge first-install request not found.")
return result
@router.get("")
def list_deployments(session: DeploymentSession, limit: DeploymentLimit = 100):
return {"items": DeploymentControlPlane().list_jobs(session, limit=limit)}
@@ -150,6 +221,11 @@ def get_delivery_status():
@router.get("/{job_id}")
def get_deployment(job_id: str, session: DeploymentSession):
if job_id.startswith("forge-install-"):
raise HTTPException(
status_code=404,
detail="Use the fixed Forge first-install status route for this job.",
)
try:
return DeploymentControlPlane().refresh(session, job_id)
except DeploymentControlError as exc:
File diff suppressed because it is too large. Load diff
+19
View File
@@ -289,6 +289,18 @@ class Settings:
# The reviewed group mapping must emit this exact claim. The
# enrollment schema only accepts its bounded role contract.
"AUTHENTIK_OIDC_ROLE_CLAIM": _env_value("AUTHENTIK_OIDC_ROLE_CLAIM", None),
# Forge OIDC requires a pre-existing RSA keypair selected by ID;
# the key material remains managed inside Authentik.
"AUTHENTIK_FORGE_OIDC_SIGNING_KEY_ID": _env_value("AUTHENTIK_FORGE_OIDC_SIGNING_KEY_ID", None),
# These IDs are loaded only from the encrypted Forge OIDC binding.
# They deliberately have no environment or generic setup input.
"AUTHENTIK_FORGE_OIDC_BINDING_VALID": False,
"AUTHENTIK_FORGE_OIDC_PROVIDER_ID": None,
"AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING": None,
"AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING": None,
"AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING": None,
# The trusted Forge namespace owner is explicit and has no guess/default.
"FORGE_PRIVATE_PACKAGE_OWNER": _env_value("FORGE_PRIVATE_PACKAGE_OWNER", None),
"JELLYFIN_URL": _env_value("JELLYFIN_URL", None),
"JELLYFIN_API_KEY": _env_value("JELLYFIN_API_KEY", None),
"WIDEFROG_URL": _env_value("WIDEFROG_URL", None),
@@ -396,6 +408,13 @@ class Settings:
"AUTHENTIK_OIDC_INVALIDATION_FLOW": bool(self.apis["AUTHENTIK_OIDC_INVALIDATION_FLOW"]),
"AUTHENTIK_OIDC_GROUP_CLAIM_MAPPING": bool(self.apis["AUTHENTIK_OIDC_GROUP_CLAIM_MAPPING"]),
"AUTHENTIK_OIDC_ROLE_CLAIM": bool(self.apis["AUTHENTIK_OIDC_ROLE_CLAIM"]),
"AUTHENTIK_FORGE_OIDC_SIGNING_KEY_ID": bool(self.apis["AUTHENTIK_FORGE_OIDC_SIGNING_KEY_ID"]),
"AUTHENTIK_FORGE_OIDC_BINDING_VALID": bool(self.apis["AUTHENTIK_FORGE_OIDC_BINDING_VALID"]),
"AUTHENTIK_FORGE_OIDC_PROVIDER_ID": bool(self.apis["AUTHENTIK_FORGE_OIDC_PROVIDER_ID"]),
"AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING": bool(self.apis["AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING"]),
"AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING": bool(self.apis["AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING"]),
"AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING": bool(self.apis["AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING"]),
"FORGE_PRIVATE_PACKAGE_OWNER": bool(self.apis["FORGE_PRIVATE_PACKAGE_OWNER"]),
"JELLYFIN_URL": bool(self.apis["JELLYFIN_URL"]),
"WIDEFROG_URL": bool(self.apis["WIDEFROG_URL"]),
"WIDEFROG_VERIFY_TLS": bool(self.apis["WIDEFROG_VERIFY_TLS"]),
+15 -4
View File
@@ -2,13 +2,22 @@ from datetime import UTC, datetime
from sqlalchemy import inspect, text
from sqlmodel import SQLModel, select
from . import session as db_session
from .session import get_session
from ..core.config import Settings
from ..services.app_classifier import classify_container
from ..services.app_registry import category_label
from .models import App, AppContainerMapping, Connector, DiscoveredContainer, NotificationChannel, NotificationEvent, NotificationRule, Policy, WorkMeshPromotion
from . import session as db_session
from .models import (
App,
AppContainerMapping,
Connector,
DiscoveredContainer,
NotificationChannel,
NotificationEvent,
NotificationRule,
Policy,
)
from .session import get_session
settings = Settings()
@@ -939,4 +948,6 @@ def _seed_policies() -> list[Policy]:
Policy(id="policy-authentik-force-sync", action="authentik.force_sync", target="authentik", voice="blocked", manual="blocked", risk="high", reason="Force sync is a mutating operation and blocked in Connector V1."),
Policy(id="policy-authentik-mass-edit", action="authentik.mass_edit", target="authentik", voice="blocked", manual="blocked", risk="critical", reason="Bulk Authentik mutations are blocked."),
Policy(id="policy-authentik-unknown-mutation", action="authentik.unknown_mutation", target="authentik", voice="blocked", manual="blocked", risk="critical", reason="Unknown Authentik mutations fail closed."),
Policy(id="policy-forge-oidc-provision", action="forge.oidc.provision", target="itworx-forge", voice="blocked", manual="allowed", risk="critical", reason="The fixed Forge OIDC client may be provisioned only by an authenticated manual operator route; voice and generic Authentik mutations remain blocked."),
Policy(id="policy-forge-secret-prepare", action="forge.secrets.prepare", target="itworx-forge", voice="blocked", manual="blocked", risk="critical", reason="Preparing the fixed Forge bootstrap secret bundle is default-denied and requires an explicit exact-target manual operator policy; voice remains blocked."),
]
+59
View File
@@ -28,6 +28,7 @@ from .db.session import engine
from .schemas.deployment import DeploymentPlanV1
from .schemas.enrollment import EnrollmentPlanV1
from .schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from .schemas.forge_bootstrap_apply import ForgeBootstrapApplyV1
from .services import knowledge_service, mcp_operations, safety_center_service
from .services.action_execution import mcp_execution_supported
from .services.action_registry import ACTION_RISK, ACTIONS, action_capability
@@ -696,6 +697,59 @@ def deployment_preflight(plan: dict[str, Any], idempotencyKey: str) -> dict[str,
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
@appops_mcp.tool(name="forge.bootstrap.apply", annotations=_OPERATION_RUN_TOOL)
@_tracked("forge.bootstrap.apply")
def forge_bootstrap_apply(profile: dict[str, Any], idempotencyKey: str) -> dict[str, Any]:
"""Apply one passed, exact-profile Forge first install through its fixed broker route."""
if not _MCP_EXECUTION_LOCK.acquire(blocking=False):
raise RuntimeError("server_operation_capacity_busy")
try:
typed_profile = ForgeBootstrapApplyV1.model_validate(profile)
from .services.forge_bootstrap_apply import (
ForgeBootstrapApplyService,
)
with Session(engine) as session:
return _json(
ForgeBootstrapApplyService().apply(
session,
typed_profile,
idempotency_key=idempotencyKey,
)
)
except ValueError as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally:
_MCP_EXECUTION_LOCK.release()
@appops_mcp.tool(name="forge.bootstrap.status", annotations=_READ_ONLY_TOOL)
@_tracked("forge.bootstrap.status")
def forge_bootstrap_status(jobId: str) -> dict[str, Any]:
"""Reconcile one existing Forge first-install job using the signed read-only route."""
from .services.forge_bootstrap_apply import ForgeBootstrapApplyService
with Session(engine) as session:
result = ForgeBootstrapApplyService().status(session, jobId)
return _json(result or {"status": "not_found"})
@appops_mcp.tool(name="forge.bootstrap.resume", annotations=_OPERATION_RUN_TOOL)
@_tracked("forge.bootstrap.resume")
def forge_bootstrap_resume(jobId: str) -> dict[str, Any]:
"""Resume only a signed, pre-migration Forge database-credentials pause."""
if not _MCP_EXECUTION_LOCK.acquire(blocking=False):
raise RuntimeError("server_operation_capacity_busy")
try:
from .services.forge_bootstrap_apply import ForgeBootstrapApplyService
with Session(engine) as session:
result = ForgeBootstrapApplyService().resume(session, jobId)
return _json(result or {"status": "not_found"})
finally:
_MCP_EXECUTION_LOCK.release()
@appops_mcp.tool(name="deployment.capabilities", annotations=_READ_ONLY_TOOL)
@_tracked("deployment.capabilities")
def deployment_capabilities() -> dict[str, Any]:
@@ -770,6 +824,11 @@ def deployment_apply(plan: dict[str, Any], idempotencyKey: str) -> dict[str, Any
@_tracked("deployment.status")
def deployment_status(jobId: str) -> dict[str, Any]:
"""Read durable deployment state and refresh a bounded broker request."""
if jobId.startswith("forge-install-"):
return {
"status": "invalid",
"reason": "Use forge.bootstrap.status for Forge first-install jobs.",
}
with Session(engine) as session:
try:
return _json(DeploymentControlPlane().refresh(session, jobId))
@@ -0,0 +1,52 @@
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}$")
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_DIGEST = re.compile(r"^sha256:[0-9a-f]{64}$")
_PREFLIGHT_JOB = re.compile(r"^forge-preflight-[0-9a-f]{16}$")
class ForgeBootstrapApplyV1(BaseModel):
"""Fixed first-install input; callers cannot choose runtime or secret inputs."""
model_config = ConfigDict(extra="forbid", strict=True)
api_version: Literal["itworx.forge-first-install/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)
preflight_job_id: str = Field(min_length=32, max_length=32)
preflight_sha256: str = Field(min_length=64, max_length=64)
@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
@field_validator("preflight_job_id")
@classmethod
def fixed_preflight_job_id(cls, value: str) -> str:
if not _PREFLIGHT_JOB.fullmatch(value):
raise ValueError("preflight_job_id must reference a fixed Forge preflight job")
return value
@field_validator("preflight_sha256")
@classmethod
def fixed_preflight_digest(cls, value: str) -> str:
if not _SHA256.fullmatch(value):
raise ValueError("preflight_sha256 must be a lowercase SHA-256 digest")
return value
+135
View File
@@ -0,0 +1,135 @@
from __future__ import annotations
import re
from typing import Any, Literal
from uuid import RFC_4122, UUID
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, model_validator
FORGE_OIDC_ACTION = "forge.oidc.provision"
FORGE_OIDC_TARGET = "itworx-forge"
FORGE_OIDC_CLIENT_ID = "itworx-forge"
FORGE_OIDC_APPLICATION_NAME = "ITWorx Forge"
FORGE_OIDC_PROVIDER_NAME = "ITWorx Forge OIDC"
FORGE_OIDC_LAUNCH_URL = "https://forge.example.com"
FORGE_OIDC_CALLBACK_URL = "https://forge.example.com/auth/callback"
FORGE_OIDC_ISSUER = "https://auth.example-host.com/application/o/itworx-forge/"
FORGE_OIDC_DISCOVERY_URL = (
"https://auth.example-host.com/application/o/itworx-forge/.well-known/openid-configuration"
)
FORGE_OIDC_SECRET_REF = "ITWORX_FORGE_OIDC_CLIENT_SECRET"
FORGE_OIDC_BINDING_SECRET_REF = "AUTHENTIK_FORGE_OIDC_BINDING"
FORGE_OIDC_GROUPS = ("forge-viewers", "forge-operators", "forge-owners")
FORGE_OIDC_MANAGED_DESCRIPTION = "Managed by AppOps Forge OIDC v1."
FORGE_OIDC_PROFILE_MAPPING_NAME = "ITWorx Forge profile and groups claim v1"
FORGE_OIDC_PROFILE_MAPPING_EXPRESSION = '''allowed_groups = {"forge-viewers", "forge-operators", "forge-owners"}
return delete_none_values({
"name": request.user.name,
"preferred_username": request.user.username,
"groups": sorted(group.name for group in request.user.ak_groups.all() if group.name in allowed_groups),
})'''
FORGE_OIDC_PROFILE_MAPPING_EXPRESSION_SHA256 = (
"648b3f6dbf2212940309a581b239c9996562ca017a89be22bcfa93f9aa2c26ae"
)
FORGE_OIDC_OPENID_MAPPING_MANAGED = "goauthentik.io/providers/oauth2/scope-openid"
FORGE_OIDC_EMAIL_MAPPING_MANAGED = "goauthentik.io/providers/oauth2/scope-email"
FORGE_OIDC_OPENID_MAPPING_EXPRESSION_SHA256 = (
"38e58da331cc5378d4f269389242aecb80adf30093c405524dc1ab5aab9d452f"
)
FORGE_OIDC_EMAIL_MAPPING_EXPRESSION_SHA256 = (
"d1200eabeffc3b7f379e9c1636367b69befb0b1ac2b5d82b7be20e78667f2b94"
)
class ForgeOidcProvisionRequestV1(BaseModel):
"""Empty, strict request contract; the target and resource fields are fixed."""
model_config = ConfigDict(extra="forbid", frozen=True)
class ForgeOidcScopeMappingIdsV1(BaseModel):
"""The only three scopes attached to the fixed Forge provider."""
model_config = ConfigDict(extra="forbid", frozen=True)
openid: UUID
email: UUID
profile: UUID
@model_validator(mode="before")
@classmethod
def mapping_ids_are_canonical_rfc_uuids(cls, value: Any) -> Any:
if not isinstance(value, dict) or set(value) != {"openid", "email", "profile"}:
raise ValueError("forge_oidc_scope_mapping_ids_invalid")
for field in ("openid", "email", "profile"):
raw = value[field]
if not isinstance(raw, str):
# Pydantic converts ValueError into structured ValidationError output.
raise ValueError("forge_oidc_scope_mapping_id_invalid") # noqa: TRY004
try:
parsed = UUID(raw)
except (TypeError, ValueError, AttributeError):
raise ValueError("forge_oidc_scope_mapping_id_invalid") from None
if (
str(parsed) != raw
or parsed.version not in {1, 2, 3, 4, 5, 6, 7, 8}
or parsed.variant != RFC_4122
):
raise ValueError("forge_oidc_scope_mapping_id_invalid")
return value
@model_validator(mode="after")
def mapping_ids_are_distinct(self) -> ForgeOidcScopeMappingIdsV1:
values = (self.openid, self.email, self.profile)
if len(set(values)) != len(values):
raise ValueError("forge_oidc_scope_mapping_ids_must_be_distinct")
return self
class ForgeOidcBindingV1(BaseModel):
"""Protected, encrypted reference to the remote resources owned by Forge OIDC."""
model_config = ConfigDict(extra="forbid", frozen=True, populate_by_name=True)
version: StrictInt
provider_id: StrictStr | None = Field(default=None, alias="providerId")
scope_mapping_ids: ForgeOidcScopeMappingIdsV1 = Field(alias="scopeMappingIds")
@model_validator(mode="after")
def provider_id_is_positive_decimal(self) -> ForgeOidcBindingV1:
if self.version != 1:
raise ValueError("forge_oidc_binding_version_unsupported")
if self.provider_id is not None and not self.provider_id.isascii():
raise ValueError("forge_oidc_provider_id_must_be_positive_decimal")
if self.provider_id is not None and (
not re.fullmatch(r"[1-9][0-9]{0,9}", self.provider_id)
):
raise ValueError("forge_oidc_provider_id_must_be_positive_decimal")
return self
class ForgeOidcProvisionResultV1(BaseModel):
"""Safe result projection. It intentionally has no client-secret field."""
model_config = ConfigDict(extra="forbid", frozen=True, populate_by_name=True)
success: bool
status: Literal["created", "already_configured", "blocked", "conflict", "failed", "partial_failure"]
message: str
client_id: Literal["itworx-forge"] = Field(default=FORGE_OIDC_CLIENT_ID, alias="clientId")
callback_url: Literal["https://forge.example.com/auth/callback"] = Field(
default=FORGE_OIDC_CALLBACK_URL,
alias="callbackUrl",
)
issuer: Literal["https://auth.example-host.com/application/o/itworx-forge/"] = FORGE_OIDC_ISSUER
discovery_url: Literal[
"https://auth.example-host.com/application/o/itworx-forge/.well-known/openid-configuration"
] = Field(default=FORGE_OIDC_DISCOVERY_URL, alias="discoveryUrl")
groups: tuple[Literal["forge-viewers", "forge-operators", "forge-owners"], ...] = FORGE_OIDC_GROUPS
secret_ref: Literal["ITWORX_FORGE_OIDC_CLIENT_SECRET"] = Field(
default=FORGE_OIDC_SECRET_REF,
alias="secretRef",
)
secret_created: bool = Field(default=False, alias="secretCreated")
application_id: str | None = Field(default=None, alias="applicationId")
provider_id: str | None = Field(default=None, alias="providerId")
@@ -0,0 +1,71 @@
from __future__ import annotations
import re
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
from .forge_bootstrap import ForgeBootstrapPreflightV1
_SAFE_CODE = re.compile(r"^[a-z][a-z0-9_]{0,95}$")
_REVISION = re.compile(r"^[0-9a-f]{40}$")
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_CONTROLLER_VERSION = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$")
class ForgeControllerReaderProvisionRequestV1(BaseModel):
"""Empty request for the single fixed Forge controller-reader provisioner."""
model_config = ConfigDict(extra="forbid", frozen=True, strict=True)
class ForgeControllerReaderProvisionResultV1(BaseModel):
"""Safe response projection; the held reader token is never returned."""
model_config = ConfigDict(extra="forbid", frozen=True, populate_by_name=True)
success: StrictBool
status: Literal["ready", "blocked", "conflict", "failed", "partial_failure"]
message: StrictStr = Field(pattern=_SAFE_CODE.pattern)
ready: StrictBool
secret_ref: Literal["ITWORX_FORGE_AUTHENTIK_READ_TOKEN"] = Field(alias="secretRef")
class ForgeSecretDeliveryRequestV1(BaseModel):
"""Caller supplies only the existing exact-revision Forge profile."""
model_config = ConfigDict(extra="forbid", frozen=True, strict=True)
profile: ForgeBootstrapPreflightV1
class ForgeSecretDeliveryControllerArtifactV1(BaseModel):
"""Strict safe identity projection from the independently guarded controller."""
model_config = ConfigDict(extra="forbid", frozen=True, populate_by_name=True, strict=True)
controller_version: StrictStr = Field(alias="controllerVersion", pattern=_CONTROLLER_VERSION.pattern)
source_repository: Literal["jens/unraid-appops-gateway"] = Field(alias="sourceRepository")
source_revision: StrictStr = Field(alias="sourceRevision", pattern=_REVISION.pattern)
source_tree: StrictStr = Field(alias="sourceTree", pattern=_REVISION.pattern)
artifact_sha256: StrictStr = Field(alias="artifactSha256", pattern=_SHA256.pattern)
class ForgeSecretDeliveryResultV1(BaseModel):
"""Safe operator result; it contains no bundle material or token hashes."""
model_config = ConfigDict(extra="forbid", frozen=True, populate_by_name=True, strict=True)
success: StrictBool
status: Literal["bundle_ready", "blocked", "conflict", "failed", "outcome_unknown"]
message: StrictStr = Field(pattern=_SAFE_CODE.pattern)
target: Literal["itworx-forge"]
revision: StrictStr = Field(pattern=_REVISION.pattern)
controller_artifact: ForgeSecretDeliveryControllerArtifactV1 | None = Field(
default=None, alias="controllerArtifact"
)
secret_files_ready: StrictInt | None = Field(default=None, alias="secretFilesReady")
master_key_backup_verified: StrictBool | None = Field(
default=None, alias="masterKeyBackupVerified"
)
apply_preflight: Literal["not_evaluated"] | None = Field(default=None, alias="applyPreflight")
+8
View File
@@ -176,6 +176,8 @@ ACTIONS = [
'authentik.force_sync',
'authentik.mass_edit',
'authentik.unknown_mutation',
'forge.oidc.provision',
'forge.secrets.prepare',
'tautulli.terminate_session',
'tautulli.delete_newsletter',
'tautulli.delete_notification_log',
@@ -386,6 +388,8 @@ ACTION_RISK = {
'authentik.force_sync': 'high',
'authentik.mass_edit': 'critical',
'authentik.unknown_mutation': 'critical',
'forge.oidc.provision': 'critical',
'forge.secrets.prepare': 'critical',
'tautulli.terminate_session': 'high',
'tautulli.delete_newsletter': 'critical',
'tautulli.delete_notification_log': 'critical',
@@ -531,6 +535,10 @@ def action_category(action: str) -> str:
def action_default_target(action: str) -> str:
if action == 'forge.oidc.provision':
return 'itworx-forge'
if action == 'forge.secrets.prepare':
return 'itworx-forge'
if action == 'controller.artifact.promote':
return 'tower/deployment-controller'
if action == 'controller.managed-storage.initialize':
+194
View File
@@ -8,6 +8,7 @@ runtime topology before it can mutate production.
from __future__ import annotations
import base64
import hashlib
import hmac
import ipaddress
@@ -27,6 +28,7 @@ from ..core.config import settings
from ..core.redaction import redact_message
from ..schemas.deployment import DeploymentPlanV1
from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from ..schemas.forge_bootstrap_apply import ForgeBootstrapApplyV1
_REQUEST_ID = re.compile(r"^[0-9a-f]{64}$")
_MAX_DELIVERY_STATUS_BYTES = 512 * 1024
@@ -35,6 +37,18 @@ _MAX_CONTROLLER_HEALTH_BYTES = 64 * 1024
_WORKMESH_REVISION = re.compile(r"^[0-9a-f]{40}$")
_WORKMESH_PREFLIGHT_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_WORKMESH_IDEMPOTENCY = re.compile(r"^[A-Za-z0-9._:-]{8,128}$")
_FORGE_SECRET_DELIVERY_PATH = "/v1/appops/forge/bootstrap/secrets"
_MAX_FORGE_SECRET_DELIVERY_BODY_BYTES = 16 * 1024
_MAX_FORGE_SECRET_DELIVERY_RESPONSE_BYTES = 64 * 1024
def _unique_json_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise ValueError("duplicate_json_key")
result[key] = value
return result
class DeploymentBrokerClient:
@@ -197,6 +211,8 @@ class DeploymentBrokerClient:
value = decoded.get(field)
if isinstance(value, str) and len(value) <= 256:
rejected[field] = value
elif field == "noMutation" and isinstance(value, bool):
rejected[field] = value
return rejected
return {"status": str(decoded.get("status") or "ok"), **decoded}
@@ -314,6 +330,184 @@ class DeploymentBrokerClient:
payload={"profile": profile.model_dump(mode="json")},
)
def forge_bootstrap_apply(
self,
profile: ForgeBootstrapApplyV1,
*,
idempotency_key: str,
secret: str,
) -> dict[str, Any]:
"""Submit only the fixed, preflight-bound Forge first-install profile."""
result = self._request(
"POST",
"/v1/appops/forge/bootstrap/apply",
secret=secret,
payload={
"profile": profile.model_dump(mode="json"),
"idempotencyKey": idempotency_key,
},
error_response_fields=frozenset({
"apiVersion", "request", "repository", "revision", "operation",
"workspaceImageDigest", "frontdoorImageDigest", "noMutation",
}),
)
if result.get("status") == "preflight_changed":
result.pop("reason", None)
return result
def forge_secret_delivery(
self,
profile: ForgeBootstrapPreflightV1,
*,
config_bytes: bytes,
oidc_client_secret: bytes,
authentik_read_token: bytes,
secret: str,
) -> dict[str, Any]:
"""Send the single fixed Forge credential bundle request.
Any response after transmission is treated as an unknown outcome unless
it is a bounded, strict JSON object. Caller-side validation still owns
the complete success contract and never echoes this internal response.
"""
configuration_error = self.configuration_error()
if configuration_error is not None:
return {
"status": "missing_config",
"reason": "forge_secret_delivery_broker_configuration_invalid",
}
if (
not isinstance(secret, str)
or len(secret) < 32
or not secret.isascii()
or not secret.isprintable()
or not isinstance(config_bytes, bytes)
or not 1 <= len(config_bytes) <= 8192
or not isinstance(oidc_client_secret, bytes)
or not 32 <= len(oidc_client_secret) <= 255
or any(byte < 0x20 or byte > 0x7E for byte in oidc_client_secret)
or not isinstance(authentik_read_token, bytes)
or not 32 <= len(authentik_read_token) <= 256
or any(byte < 0x21 or byte > 0x7E for byte in authentik_read_token)
):
return {"status": "missing_secret", "reason": "forge_secret_delivery_inputs_invalid"}
payload = {
"apiVersion": "itworx.forge-secret-delivery/v1",
"target": "itworx-forge",
"profile": profile.model_dump(mode="json"),
"configBase64": base64.b64encode(config_bytes).decode("ascii"),
"oidcClientSecretBase64": base64.b64encode(oidc_client_secret).decode("ascii"),
"authentikReadTokenBase64": base64.b64encode(authentik_read_token).decode("ascii"),
}
body = self._canonical_payload(payload)
payload.clear()
if len(body) > _MAX_FORGE_SECRET_DELIVERY_BODY_BYTES:
return {"status": "missing_secret", "reason": "forge_secret_delivery_request_too_large"}
timestamp = str(int(time.time()))
nonce = uuid.uuid4().hex
path = _FORGE_SECRET_DELIVERY_PATH
headers = {
"Accept": "application/json",
"Accept-Encoding": "identity",
"Content-Type": "application/json",
"X-ITWorx-Deployment-Timestamp": timestamp,
"X-ITWorx-Deployment-Nonce": nonce,
"X-ITWorx-Deployment-Signature": self._signature(
secret, "POST", path, timestamp, nonce, body
),
}
response_body = bytearray()
try:
with httpx.Client(
timeout=self.timeout_seconds,
verify=self._verify_option(),
follow_redirects=False,
) as client, client.stream(
"POST",
f"{self.base_url}{path}",
content=body,
headers=headers,
) as response:
if response.status_code != 200:
return {
"status": "outcome_unknown",
"reason": "forge_secret_delivery_controller_rejected",
}
content_length = response.headers.get("content-length")
if content_length is not None and (
not content_length.isascii()
or not content_length.isdecimal()
or len(content_length) > 10
or int(content_length) > _MAX_FORGE_SECRET_DELIVERY_RESPONSE_BYTES
):
return {
"status": "outcome_unknown",
"reason": "forge_secret_delivery_response_invalid",
}
for chunk in response.iter_bytes(chunk_size=16 * 1024):
response_body.extend(chunk)
if len(response_body) > _MAX_FORGE_SECRET_DELIVERY_RESPONSE_BYTES:
response_body.clear()
return {
"status": "outcome_unknown",
"reason": "forge_secret_delivery_response_invalid",
}
except Exception: # noqa: BLE001 - exception text can contain held credentials.
response_body.clear()
return {
"status": "outcome_unknown",
"reason": "forge_secret_delivery_transport_unknown",
}
try:
decoded = json.loads(
bytes(response_body).decode("utf-8"),
object_pairs_hook=_unique_json_object,
)
except (UnicodeError, ValueError, TypeError):
response_body.clear()
return {
"status": "outcome_unknown",
"reason": "forge_secret_delivery_response_invalid",
}
response_body.clear()
if not isinstance(decoded, dict):
return {
"status": "outcome_unknown",
"reason": "forge_secret_delivery_response_invalid",
}
return {"status": "response", "response": decoded}
def forge_bootstrap_resume(self, request_id: str, *, secret: str) -> dict[str, Any]:
"""Resume only one recorded pre-migration credentials blocker."""
if not _REQUEST_ID.fullmatch(request_id):
return {"status": "invalid_request_id"}
result = self._request(
"POST",
f"/v1/appops/forge/bootstrap/resume/{request_id}",
secret=secret,
payload={},
error_response_fields=frozenset({
"apiVersion", "request", "repository", "revision", "operation",
"workspaceImageDigest", "frontdoorImageDigest", "noMutation",
}),
)
if result.get("status") == "preflight_changed":
result.pop("reason", None)
return result
def forge_bootstrap_status(self, request_id: str, *, secret: str) -> dict[str, Any]:
"""Read one fixed Forge first-install request; never submits mutations."""
if not _REQUEST_ID.fullmatch(request_id):
return {"status": "invalid_request_id"}
return self._request(
"GET",
f"/v1/appops/forge/bootstrap/status/{request_id}",
secret=secret,
)
def enrollment_capabilities(self, *, secret: str) -> dict[str, Any]:
"""Read the bootstrap-protected enrollment controller capability."""
return self._request(
@@ -0,0 +1,923 @@
from __future__ import annotations
import base64
import hashlib
import json
import re
from datetime import datetime
from typing import Any
from sqlalchemy import update
from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select
from ..core.config import settings
from ..core.time import utc_now
from ..db.models import DeploymentJob, Policy
from ..schemas.enrollment import MANAGED_DOCKER_APPDATA_ROOT
from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from ..schemas.forge_bootstrap_apply import ForgeBootstrapApplyV1
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
_API_VERSION = "itworx.forge-first-install/v1"
_PREFLIGHT_API_VERSION = "itworx.forge-bootstrap-preflight/v1"
_APPLICATION_ID = "itworx-forge"
_REPOSITORY = "jens/itworx-forge"
_OPERATION = "forge_first_install"
_SECRET_REF = "FORGE_DEPLOYMENT_BROKER"
_FORGE_SOURCE_URL = "https://gitea.example.com/Jens/itworx-forge"
_FORGE_PRIVATE_PACKAGE_OWNER_SETTING = "FORGE_PRIVATE_PACKAGE_OWNER"
_FORGE_PRIVATE_PACKAGE_OWNER_PATTERN = re.compile(r"[a-z0-9][a-z0-9_-]{0,38}\Z")
_FORGE_OIDC_ISSUER = "https://auth.example-host.com/application/o/itworx-forge/"
_FORGE_OIDC_CLIENT_ID = "itworx-forge"
_FORGE_OIDC_CALLBACK = "https://forge.example.com/auth/callback"
_FORGE_OIDC_GROUPS = ["forge-owners", "forge-operators", "forge-viewers"]
_FORGE_OIDC_SCOPES = ["email", "openid", "profile"]
_FORGE_OIDC_SCOPE_MAPPING_EXPRESSION_SHA256 = {
"openid": "38e58da331cc5378d4f269389242aecb80adf30093c405524dc1ab5aab9d452f",
"email": "d1200eabeffc3b7f379e9c1636367b69befb0b1ac2b5d82b7be20e78667f2b94",
"profile": "648b3f6dbf2212940309a581b239c9996562ca017a89be22bcfa93f9aa2c26ae",
}
_FORGE_OIDC_PROFILE_MAPPING_NAME = "ITWorx Forge profile and groups claim v1"
_FORGE_OIDC_OPENID_MAPPING_MANAGED = "goauthentik.io/providers/oauth2/scope-openid"
_FORGE_OIDC_EMAIL_MAPPING_MANAGED = "goauthentik.io/providers/oauth2/scope-email"
_OIDC_ATTESTATION_FIELDS = {
"issuer", "audience", "clientId", "clientType", "callback", "applicationId",
"providerId", "clientSecretMatched", "authorizationFlowId", "invalidationFlowId",
"roleGroups", "scopeMappingIds", "scopeMappingExpressionSha256",
"profileMappingName", "standardMappingsManaged", "supportedScopes",
"authorizationCodeFlowSupported", "signingKeyId", "signingKeyType",
"signingKeyPrivateAvailable", "signingKeyFingerprintSha256", "signingKeyKid",
"tokenSigningAlgorithm", "jwksKid",
}
_AUTHENTIK_FORGE_OIDC_BINDING_VALID_SETTING = "AUTHENTIK_FORGE_OIDC_BINDING_VALID"
_AUTHENTIK_FORGE_OIDC_PROVIDER_ID_SETTING = "AUTHENTIK_FORGE_OIDC_PROVIDER_ID"
_AUTHENTIK_FORGE_OIDC_SCOPE_MAPPING_SETTINGS = {
"openid": "AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING",
"email": "AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING",
"profile": "AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING",
}
_AUTHENTIK_SIGNING_KEY_ID_SETTING = "AUTHENTIK_FORGE_OIDC_SIGNING_KEY_ID"
_AUTHENTIK_AUTHORIZATION_FLOW_SETTING = "AUTHENTIK_OIDC_AUTHORIZATION_FLOW"
_AUTHENTIK_INVALIDATION_FLOW_SETTING = "AUTHENTIK_OIDC_INVALIDATION_FLOW"
_IDEMPOTENCY = re.compile(r"^[A-Za-z0-9._:-]{8,128}$")
_HEALTHY_SERVICES = ["api", "dashboard", "itworx-forge", "postgres", "worker"]
_PROFILE_CHECKS = {
"deployment_apply_policy",
"controller_trust",
"managed_appdata_root_selection",
"exact_main",
"source_manifest",
"managed_appdata",
"proxy_network",
"resource_collisions",
"push_validation",
"image_registry",
"oidc",
"secret_delivery",
}
_FAILED_CODES = {"migration_failed", "service_start_failed", "healthcheck_failed"}
_ACTIVE_STATUSES = {"applying", "outcome_unknown", "needs_database_credentials"}
_TERMINAL_STATUSES = {"deployed", "failed", "preflight_changed"}
class ForgeBootstrapApplyError(ValueError):
"""A fail-closed error in the fixed Forge first-install contract."""
def _canonical_json(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
def _sha256(value: Any) -> str:
return hashlib.sha256(_canonical_json(value)).hexdigest()
def canonical_preflight_receipt(job: DeploymentJob) -> str:
"""Hash the immutable preflight receipt as stored in AppOps, not caller data."""
receipt = {
"apiVersion": _PREFLIGHT_API_VERSION,
"jobId": job.id,
"applicationId": job.application_id,
"repository": job.repository,
"revision": job.revision,
"jobStatus": job.status,
"planSha256": job.plan_sha256,
"profile": dict(job.plan_json or {}),
"preflight": dict(job.preflight_json or {}),
"evidence": dict(job.evidence_json or {}),
}
return _sha256(receipt)
def _idempotency_key(value: str) -> str:
normalized = value.strip()
if not _IDEMPOTENCY.fullmatch(normalized):
raise ForgeBootstrapApplyError("Idempotency-Key must contain 8 to 128 safe characters.")
return normalized
def _unknown_summary() -> str:
return "The fixed Forge controller result is unknown; reconcile its status before retrying."
def _preflight_ready(
session: Session, profile: ForgeBootstrapApplyV1
) -> tuple[DeploymentJob, str]:
job = session.get(DeploymentJob, profile.preflight_job_id)
if (
not job
or job.application_id != _APPLICATION_ID
or job.repository != _REPOSITORY
or job.revision != profile.revision
or job.status != "preflight_succeeded"
or not job.completed_at
or job.plan_sha256 != _sha256(dict(job.plan_json or {}))
):
raise ForgeBootstrapApplyError(
"A matching passed Forge preflight receipt is required before first install."
)
try:
stored_profile = ForgeBootstrapPreflightV1.model_validate(job.plan_json)
except ValueError as exc:
raise ForgeBootstrapApplyError("The stored Forge preflight receipt is invalid.") from exc
if (
stored_profile.revision != profile.revision
or stored_profile.workspace_image_digest != profile.workspace_image_digest
or stored_profile.frontdoor_image_digest != profile.frontdoor_image_digest
):
raise ForgeBootstrapApplyError(
"The stored Forge preflight receipt does not match this profile."
)
preflight = dict(job.preflight_json or {})
if (
set(preflight) != {"apiVersion", "status", "blockers", "unknowns", "checks"}
or preflight.get("apiVersion") != _PREFLIGHT_API_VERSION
or preflight.get("status") != "passed"
or preflight.get("blockers") != []
or preflight.get("unknowns") != []
):
raise ForgeBootstrapApplyError(
"Forge first install requires a passed preflight with no unknown checks."
)
checks = preflight.get("checks")
if not isinstance(checks, list) or len(checks) != len(_PROFILE_CHECKS):
raise ForgeBootstrapApplyError("The stored Forge preflight checks are incomplete.")
seen: set[str] = set()
for check in checks:
if (
not isinstance(check, dict)
or set(check) != {"name", "status", "detail"}
or not isinstance(check.get("name"), str)
or check["name"] not in _PROFILE_CHECKS
or check["name"] in seen
or check.get("status") != "passed"
or not isinstance(check.get("detail"), str)
or len(check["detail"]) > 256
):
raise ForgeBootstrapApplyError(
"Forge first install requires every fixed preflight check to pass."
)
seen.add(check["name"])
if seen != _PROFILE_CHECKS:
raise ForgeBootstrapApplyError("The stored Forge preflight checks are incomplete.")
evidence = dict(job.evidence_json or {})
if (
set(evidence)
!= {"profile", "policyId", "workspaceImageDigest", "frontdoorImageDigest", "controller"}
or evidence.get("profile") != "forge-production-bootstrap"
or not isinstance(evidence.get("policyId"), str)
or evidence.get("workspaceImageDigest") != profile.workspace_image_digest
or evidence.get("frontdoorImageDigest") != profile.frontdoor_image_digest
or not isinstance(evidence.get("controller"), dict)
):
raise ForgeBootstrapApplyError(
"The stored Forge preflight evidence does not match this profile."
)
current_policy_id = _allowed_policy_id(session)
if current_policy_id is None or current_policy_id != evidence["policyId"]:
raise ForgeBootstrapApplyError(
"The exact-target Forge deployment policy changed after preflight."
)
if not _attestations_ready(evidence["controller"], profile):
raise ForgeBootstrapApplyError(
"The stored Forge preflight lacks verified controller attestations."
)
receipt_sha = canonical_preflight_receipt(job)
if profile.preflight_sha256 != receipt_sha:
raise ForgeBootstrapApplyError(
"The supplied preflight receipt digest does not match the stored preflight receipt."
)
return job, receipt_sha
def _allowed_policy_id(session: Session) -> str | None:
policies = session.exec(
select(Policy).where(
(Policy.action == "deployment.apply") & (Policy.target == _APPLICATION_ID)
)
).all()
if len(policies) != 1:
return None
policy = policies[0]
decision = evaluate_policy(
session, action="deployment.apply", target=_APPLICATION_ID, source="manual"
)
allowed = bool(
policy.manual == "allowed"
and policy.voice == "blocked"
and policy.risk == "high"
and decision.get("decision") == "allowed"
and decision.get("policy_id") == policy.id
)
return policy.id if allowed else None
def _attestations_ready(controller: dict[str, Any], profile: ForgeBootstrapApplyV1) -> bool:
attestations = controller.get("attestations")
if not isinstance(attestations, dict) or set(attestations) != {
"push_validation", "image_registry", "oidc", "secret_delivery",
}:
return False
push = attestations.get("push_validation")
registry = attestations.get("image_registry")
oidc = attestations.get("oidc")
secret_delivery = attestations.get("secret_delivery")
private_owner = _protected_forge_private_package_owner()
return bool(
isinstance(push, dict)
and set(push) == {"context", "conclusion", "revision"}
and push == {
"context": "Managed validation / full (push)",
"conclusion": "success",
"revision": profile.revision,
}
and isinstance(registry, dict)
and private_owner is not None
and registry
== {
"owner": private_owner,
"ownerVisibility": "private",
"workspaceDigest": profile.workspace_image_digest,
"frontdoorDigest": profile.frontdoor_image_digest,
"workspacePullable": True,
"frontdoorPullable": True,
"workspaceSource": _FORGE_SOURCE_URL,
"workspaceRevision": profile.revision,
"frontdoorSource": _FORGE_SOURCE_URL,
"frontdoorRevision": profile.revision,
}
and forge_oidc_attestation_matches(oidc)
and isinstance(secret_delivery, dict)
and set(secret_delivery) == {"requiredFilesReady", "metadataSha256"}
and secret_delivery.get("requiredFilesReady") is True
and isinstance(secret_delivery.get("metadataSha256"), str)
and re.fullmatch(r"[0-9a-f]{64}", secret_delivery["metadataSha256"])
)
def _protected_forge_private_package_owner() -> str | None:
owner = settings.apis.get(_FORGE_PRIVATE_PACKAGE_OWNER_SETTING)
if not isinstance(owner, str) or _FORGE_PRIVATE_PACKAGE_OWNER_PATTERN.fullmatch(owner) is None:
return None
return owner
def forge_oidc_attestation_matches(value: object) -> bool:
"""Validate the fixed controller proof against protected AppOps Authentik pins."""
if not isinstance(value, dict) or set(value) != _OIDC_ATTESTATION_FIELDS:
return False
if settings.apis.get(_AUTHENTIK_FORGE_OIDC_BINDING_VALID_SETTING) is not True:
return False
provider_id = settings.apis.get(_AUTHENTIK_FORGE_OIDC_PROVIDER_ID_SETTING)
mapping_ids = {
scope: settings.apis.get(setting)
for scope, setting in _AUTHENTIK_FORGE_OIDC_SCOPE_MAPPING_SETTINGS.items()
}
signing_key_id = settings.apis.get(_AUTHENTIK_SIGNING_KEY_ID_SETTING)
authorization_flow = settings.apis.get(_AUTHENTIK_AUTHORIZATION_FLOW_SETTING)
invalidation_flow = settings.apis.get(_AUTHENTIK_INVALIDATION_FLOW_SETTING)
uuid_pattern = r"^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
if (
not isinstance(provider_id, str)
or not re.fullmatch(r"[1-9][0-9]{0,19}", provider_id)
or any(not isinstance(item, str) or not re.fullmatch(uuid_pattern, item) for item in mapping_ids.values())
or len(set(mapping_ids.values())) != len(_AUTHENTIK_FORGE_OIDC_SCOPE_MAPPING_SETTINGS)
or not isinstance(signing_key_id, str)
or not re.fullmatch(uuid_pattern, signing_key_id)
or not isinstance(authorization_flow, str)
or not authorization_flow.strip()
or not isinstance(invalidation_flow, str)
or not invalidation_flow.strip()
or value.get("providerId") != provider_id
or value.get("scopeMappingIds") != mapping_ids
or value.get("signingKeyId") != signing_key_id
or value.get("authorizationFlowId") != authorization_flow
or value.get("invalidationFlowId") != invalidation_flow
):
return False
application_id = value.get("applicationId")
if (
not isinstance(application_id, str)
or not re.fullmatch(uuid_pattern, application_id)
or value.get("providerId") != provider_id
):
return False
fingerprint = value.get("signingKeyFingerprintSha256")
if not isinstance(fingerprint, str) or not re.fullmatch(r"[0-9a-f]{64}", fingerprint):
return False
jwks_kid = value.get("jwksKid")
keypair_kid = value.get("signingKeyKid")
return bool(
value.get("issuer") == _FORGE_OIDC_ISSUER
and value.get("audience") == _FORGE_OIDC_CLIENT_ID
and value.get("clientId") == _FORGE_OIDC_CLIENT_ID
and value.get("clientType") == "confidential"
and value.get("callback") == _FORGE_OIDC_CALLBACK
and value.get("clientSecretMatched") is True
and isinstance(value.get("authorizationFlowId"), str)
and isinstance(value.get("invalidationFlowId"), str)
and value.get("roleGroups") == _FORGE_OIDC_GROUPS
and value.get("profileMappingName") == _FORGE_OIDC_PROFILE_MAPPING_NAME
and isinstance(value.get("scopeMappingExpressionSha256"), dict)
and value.get("scopeMappingExpressionSha256") == _FORGE_OIDC_SCOPE_MAPPING_EXPRESSION_SHA256
and value.get("standardMappingsManaged") is True
and value.get("supportedScopes") == _FORGE_OIDC_SCOPES
and value.get("authorizationCodeFlowSupported") is True
and value.get("signingKeyType") == "RSA"
and value.get("signingKeyPrivateAvailable") is True
and value.get("tokenSigningAlgorithm") == "RS256"
and _forge_oidc_kid_is_valid(keypair_kid)
and jwks_kid == keypair_kid
)
def _forge_oidc_kid_is_valid(value: object) -> bool:
if not isinstance(value, str):
return False
if re.fullmatch(r"[0-9a-f]{32}", value):
return True
if re.fullmatch(r"[A-Za-z0-9_-]{86}", value) is None:
return False
try:
decoded = base64.b64decode(value + "==", altchars=b"-_", validate=True)
except (TypeError, ValueError):
return False
return (
len(decoded) == 64
and base64.urlsafe_b64encode(decoded).decode("ascii").rstrip("=") == value
)
def _controller_secret(session: Session) -> str | None:
trust = EnrollmentPlane.get_trust_config(session)
if (
trust is None
# The fixed Tower controller shares the existing enrollment identity
# with RepoPulse; changing global trust would invalidate that binding.
or trust.controller_identity != "unraid-controller"
or trust.trusted_appdata_root != MANAGED_DOCKER_APPDATA_ROOT
or trust.managed_appdata_backup_acknowledged is not True
):
return None
try:
secret = get_secret(session, _SECRET_REF, touch=False)
except Exception: # noqa: BLE001 - never expose secret store errors.
return None
if (
not isinstance(secret, str)
or len(secret) < 32
or not secret.isascii()
or not secret.isprintable()
):
return None
return secret
def _response_identity_valid(
result: dict[str, Any], *, request_id: str, profile: ForgeBootstrapApplyV1
) -> bool:
return bool(
result.get("apiVersion") == _API_VERSION
and result.get("request") == request_id
and result.get("repository") == _REPOSITORY
and result.get("revision") == profile.revision
and result.get("operation") == _OPERATION
and result.get("workspaceImageDigest") == profile.workspace_image_digest
and result.get("frontdoorImageDigest") == profile.frontdoor_image_digest
)
def _classify_controller_result(
result: object,
*,
request_id: str,
profile: ForgeBootstrapApplyV1,
) -> tuple[str, str, str, dict[str, Any]]:
"""Return status/stage/summary and strictly projected controller evidence."""
if not isinstance(result, dict) or not _response_identity_valid(
result, request_id=request_id, profile=profile
):
return (
"outcome_unknown",
"controller_response_unverified",
_unknown_summary(),
{},
)
status = result.get("status")
base_fields = {
"apiVersion",
"status",
"request",
"repository",
"revision",
"operation",
"workspaceImageDigest",
"frontdoorImageDigest",
}
identity = {key: result[key] for key in sorted(base_fields)}
if status in {"accepted", "running"} and set(result) == base_fields:
return (
"applying",
"controller_accepted" if status == "accepted" else "controller_running",
"Forge first install is running on the fixed controller.",
identity,
)
if status == "needs_database_credentials" and set(result) == base_fields:
return (
"needs_database_credentials",
"controller_needs_database_credentials",
"Forge first install is paused before migration until fixed database credentials are provisioned.",
identity,
)
if (
status == "preflight_changed"
and set(result) == base_fields | {"noMutation"}
and result.get("noMutation") is True
):
return (
"preflight_changed",
"controller_preflight_changed",
"Controller rejected changed Forge first-install preflight before mutation.",
{**identity, "noMutation": True},
)
if status == "deployed" and set(result) == base_fields | {"receipt"}:
receipt = result.get("receipt")
if not isinstance(receipt, dict) or set(receipt) != {
"revision",
"workspaceImageDigest",
"frontdoorImageDigest",
"migration",
"health",
}:
return (
"outcome_unknown",
"controller_receipt_unverified",
_unknown_summary(),
{},
)
migration = receipt.get("migration")
health = receipt.get("health")
if (
receipt.get("revision") != profile.revision
or receipt.get("workspaceImageDigest") != profile.workspace_image_digest
or receipt.get("frontdoorImageDigest") != profile.frontdoor_image_digest
or not isinstance(migration, dict)
or set(migration) != {"service", "status", "result"}
or migration
!= {"service": "migrate", "status": "completed", "result": "succeeded"}
or not isinstance(health, dict)
or set(health) != {"status", "services"}
or health.get("status") != "healthy"
or health.get("services") != _HEALTHY_SERVICES
):
return (
"outcome_unknown",
"controller_receipt_unverified",
_unknown_summary(),
{},
)
safe_receipt = {
"revision": profile.revision,
"workspaceImageDigest": profile.workspace_image_digest,
"frontdoorImageDigest": profile.frontdoor_image_digest,
"migration": dict(migration),
"health": {"status": "healthy", "services": list(_HEALTHY_SERVICES)},
}
return (
"deployed",
"controller_deployed",
"Forge first install completed with exact migration and health receipts.",
{**identity, "receipt": safe_receipt},
)
if status == "failed" and set(result) == base_fields | {"failure"}:
failure = result.get("failure")
if (
isinstance(failure, dict)
and set(failure) == {"code", "preservedData"}
and failure.get("code") in _FAILED_CODES
and failure.get("preservedData") is True
):
return (
"failed",
"controller_failed_preserving_data",
"Controller reported a first-install failure and confirmed PostgreSQL data preservation.",
{
**identity,
"failure": {
"code": failure["code"],
"preservedData": True,
},
},
)
return (
"outcome_unknown",
"controller_response_unverified",
_unknown_summary(),
{},
)
def _serialize(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": dict(job.preflight_json or {}),
"evidence": dict(job.evidence_json or {}),
"result": job.result_summary,
"request": 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,
}
class ForgeBootstrapApplyService:
"""One fixed Forge first-install operation with durable, non-replaying apply."""
def __init__(self, *, broker: DeploymentBrokerClient | None = None):
self.broker = broker or DeploymentBrokerClient()
@staticmethod
def request_id(profile: ForgeBootstrapApplyV1, idempotency_key: str) -> str:
normalized = _idempotency_key(idempotency_key)
payload = {
"repository": _REPOSITORY,
"operation": _OPERATION,
"profile": profile.model_dump(mode="json"),
"idempotencyKey": normalized,
}
return _sha256(payload)
def apply(
self,
session: Session,
profile: ForgeBootstrapApplyV1,
*,
idempotency_key: str,
) -> dict[str, Any]:
normalized_key = _idempotency_key(idempotency_key)
idempotency_digest = hashlib.sha256(
f"forge-first-install:{normalized_key}".encode()
).hexdigest()
profile_json = profile.model_dump(mode="json")
plan_sha = _sha256(profile_json)
existing = session.exec(
select(DeploymentJob).where(
DeploymentJob.idempotency_sha256 == idempotency_digest
)
).first()
if existing:
if (
existing.application_id != _APPLICATION_ID
or existing.repository != _REPOSITORY
):
raise ForgeBootstrapApplyError(
"Idempotency-Key already belongs to a different application or repository."
)
if existing.plan_sha256 != plan_sha:
raise ForgeBootstrapApplyError(
"Idempotency-Key was already used for a different Forge first-install profile."
)
return self._refresh_job(session, existing, replay=True)
preflight_job, receipt_sha = _preflight_ready(session, profile)
if _allowed_policy_id(session) is None:
raise ForgeBootstrapApplyError(
"An allowed exact-target deployment.apply policy is required for Forge first install."
)
secret = _controller_secret(session)
if secret is None:
raise ForgeBootstrapApplyError(
"Stored managed-root trust and the fixed Forge deployment secret are required."
)
request_id = self.request_id(profile, normalized_key)
job_id = f"forge-install-{idempotency_digest}"
now = utc_now()
job = DeploymentJob(
id=job_id,
application_id=_APPLICATION_ID,
repository=_REPOSITORY,
revision=profile.revision,
plan_sha256=plan_sha,
idempotency_sha256=idempotency_digest,
status="applying",
stage="controller_request_pending",
plan_json=profile_json,
preflight_json={
"apiVersion": _PREFLIGHT_API_VERSION,
"jobId": preflight_job.id,
"sha256": receipt_sha,
},
evidence_json={"profile": "forge-first-install", "controller": {}},
broker_request_id=request_id,
result_summary="Forge first install is awaiting the fixed controller response.",
created_at=now,
updated_at=now,
)
session.add(job)
try:
# The durable request intent must exist before the only POST attempt.
session.commit()
session.refresh(job)
except IntegrityError:
session.rollback()
raced = session.get(DeploymentJob, job_id)
if raced and (
raced.application_id != _APPLICATION_ID
or raced.repository != _REPOSITORY
):
raise ForgeBootstrapApplyError(
"Idempotency-Key already belongs to a different application or repository."
) from None
if raced and raced.plan_sha256 == plan_sha:
return self._refresh_job(session, raced, replay=True)
raise ForgeBootstrapApplyError(
"The Forge first-install request could not be durably claimed."
) from None
log_event(
session,
source="deployment-control-plane",
identity="operator",
action_id="deployment.apply",
target=_APPLICATION_ID,
target_type="deployment",
risk="high",
policy_decision="allowed",
reason="Submitted one fixed Forge first-install request after exact preflight validation.",
result="submitted",
execution_plan={
"request": request_id,
"planSha256": plan_sha,
"preflightSha256": receipt_sha,
},
redacted_metadata={"profile": "forge-first-install", "jobId": job_id},
)
try:
response = self.broker.forge_bootstrap_apply(
profile,
idempotency_key=normalized_key,
secret=secret,
)
except Exception: # noqa: BLE001 - durable intent is reconciled with GET only.
response = {"status": "unreachable"}
return self._record_response(
session,
job,
response,
profile,
expected_updated_at=job.updated_at,
)
def status(self, session: Session, job_id: str) -> dict[str, Any] | None:
if not re.fullmatch(r"forge-install-[0-9a-f]{64}", job_id):
return None
job = session.get(DeploymentJob, job_id)
if (
not job
or job.application_id != _APPLICATION_ID
or job.repository != _REPOSITORY
or not re.fullmatch(r"[0-9a-f]{64}", job.broker_request_id or "")
):
return None
return self._refresh_job(session, job)
def resume(self, session: Session, job_id: str) -> dict[str, Any] | None:
"""Explicitly resume only after a signed read proves pre-migration pause."""
if not re.fullmatch(r"forge-install-[0-9a-f]{64}", job_id):
return None
job = session.get(DeploymentJob, job_id)
if (
not job
or job.application_id != _APPLICATION_ID
or job.repository != _REPOSITORY
or not re.fullmatch(r"[0-9a-f]{64}", job.broker_request_id or "")
):
return None
# A caller cannot turn an old pending/unknown record into another POST.
# First reconcile with the controller, then proceed only from its exact
# safe pre-migration credential state.
refreshed = self._refresh_job(session, job)
if refreshed.get("status") != "needs_database_credentials":
return refreshed
job = session.get(DeploymentJob, job_id)
if job is None:
return refreshed
session.refresh(job)
try:
profile = ForgeBootstrapApplyV1.model_validate(job.plan_json)
except ValueError:
return refreshed
preflight_receipt = dict(job.preflight_json or {})
if (
set(preflight_receipt) != {"apiVersion", "jobId", "sha256"}
or preflight_receipt.get("apiVersion") != _PREFLIGHT_API_VERSION
or preflight_receipt.get("jobId") != profile.preflight_job_id
or preflight_receipt.get("sha256") != profile.preflight_sha256
):
return refreshed
try:
_preflight_ready(session, profile)
except ForgeBootstrapApplyError:
return refreshed
secret = _controller_secret(session)
if not secret:
return refreshed
expected_updated_at = job.updated_at
resume_pending = "Forge database credentials passed the signed pre-migration gate; resume is pending."
claimed_at = utc_now()
# Compare-and-swap the observed, signed pre-migration state. Concurrent
# callers can both reconcile it, but only one can persist the resume claim.
claim = session.exec(
update(DeploymentJob)
.where(
DeploymentJob.id == job.id,
DeploymentJob.application_id == _APPLICATION_ID,
DeploymentJob.repository == _REPOSITORY,
DeploymentJob.status == "needs_database_credentials",
DeploymentJob.stage == "controller_needs_database_credentials",
DeploymentJob.updated_at == expected_updated_at,
)
.values(
status="applying",
stage="resume_request_pending",
result_summary=resume_pending,
updated_at=claimed_at,
)
)
if claim.rowcount != 1:
session.rollback()
current = session.get(DeploymentJob, job_id)
if current is None:
return refreshed
session.refresh(current)
return _serialize(current)
# The one-shot intent is durable before the only resume POST attempt.
session.commit()
session.refresh(job)
resume_intent_version = job.updated_at
log_event(
session,
source="deployment-control-plane",
identity="operator",
action_id="deployment.apply",
target=_APPLICATION_ID,
target_type="deployment",
risk="high",
policy_decision="allowed",
reason="Requested one explicit Forge first-install resume after signed pre-migration credential status.",
result="resume_submitted",
execution_plan={"request": job.broker_request_id, "jobId": job.id},
redacted_metadata={"profile": "forge-first-install"},
)
try:
response = self.broker.forge_bootstrap_resume(
job.broker_request_id or "", secret=secret
)
except Exception: # noqa: BLE001 - durable resume intent is reconciled with GET only.
response = {"status": "unreachable"}
return self._record_response(
session,
job,
response,
profile,
expected_updated_at=resume_intent_version,
resume_claimed=True,
)
def _refresh_job(
self,
session: Session,
job: DeploymentJob,
*,
replay: bool = False,
) -> dict[str, Any]:
if job.status not in _ACTIVE_STATUSES:
return _serialize(job, replay=replay)
try:
profile = ForgeBootstrapApplyV1.model_validate(job.plan_json)
except ValueError:
return _serialize(job, replay=replay)
secret = _controller_secret(session)
expected_updated_at = job.updated_at
resume_claimed = job.stage.startswith("resume")
if not secret:
return self._record_response(
session,
job,
{"status": "unreachable"},
profile,
replay=replay,
expected_updated_at=expected_updated_at,
resume_claimed=resume_claimed,
)
try:
response = self.broker.forge_bootstrap_status(
job.broker_request_id or "", secret=secret
)
except Exception: # noqa: BLE001 - reconciliation failures remain unknown.
response = {"status": "unreachable"}
return self._record_response(
session,
job,
response,
profile,
replay=replay,
expected_updated_at=expected_updated_at,
resume_claimed=resume_claimed,
)
@staticmethod
def _record_response(
session: Session,
job: DeploymentJob,
response: object,
profile: ForgeBootstrapApplyV1,
*,
replay: bool = False,
expected_updated_at: datetime | None = None,
resume_claimed: bool = False,
) -> dict[str, Any]:
expected_updated_at = expected_updated_at or job.updated_at
status, stage, result, evidence = _classify_controller_result(
response,
request_id=job.broker_request_id or "",
profile=profile,
)
resume_claimed = resume_claimed or job.stage.startswith("resume")
if resume_claimed:
if status == "needs_database_credentials":
status = "outcome_unknown"
stage = "resume_status_unresolved"
result = (
"Resume was already claimed but the controller still reports its pre-migration pause; "
"reconcile by status without submitting another resume."
)
else:
stage = f"resume_{stage}"
next_updated_at = utc_now()
next_completed_at = (
utc_now() if status in _TERMINAL_STATUSES else job.completed_at
)
values = {
"status": status,
"stage": stage,
"result_summary": result,
"evidence_json": {"profile": "forge-first-install", "controller": evidence},
"updated_at": next_updated_at,
"completed_at": next_completed_at,
}
statement = update(DeploymentJob).where(
DeploymentJob.id == job.id,
DeploymentJob.updated_at == expected_updated_at,
)
if status == "needs_database_credentials" and not resume_claimed:
# A status GET started after the local resume claim may still race a
# controller that has not processed the POST. It cannot reopen the gate.
statement = statement.where(DeploymentJob.stage.not_like("resume%"))
result_update = session.exec(statement.values(**values))
if result_update.rowcount != 1:
session.rollback()
current = session.get(DeploymentJob, job.id)
if current is None:
return _serialize(job, replay=replay)
session.refresh(current)
return _serialize(current, replay=replay)
session.commit()
session.refresh(job)
return _serialize(job, replay=replay)
@@ -14,6 +14,12 @@ from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from .audit_logger import log_event
from .deployment_broker import DeploymentBrokerClient
from .enrollment_plane import EnrollmentPlane
from .forge_bootstrap_apply import (
_FORGE_SOURCE_URL,
_protected_forge_private_package_owner,
canonical_preflight_receipt,
forge_oidc_attestation_matches,
)
from .policy_engine import evaluate_policy
from .secret_store import get_secret, has_secret
@@ -71,6 +77,21 @@ _STATUS_DETAILS = {
"blocked": "A configured required push validation context rejected this exact commit.",
"unknown": "Exact-commit push validation is not configured or cannot be verified.",
},
"image_registry": {
"passed": "The fixed private Forge image owner and both immutable image digests are authenticated and pullable.",
"blocked": "One or both immutable Forge image digests are unavailable from the registry.",
"unknown": "Registry publication or pull authorization could not be verified.",
},
"oidc": {
"passed": "The fixed Forge OIDC provider, application, and role groups are ready.",
"blocked": "The Forge OIDC proof is missing, mismatched, or differs from the protected AppOps mapping and signing-key IDs.",
"unknown": "The fixed Forge OIDC configuration could not be verified.",
},
"secret_delivery": {
"passed": "The fixed Forge secret files have the required safe metadata.",
"blocked": "One or more fixed Forge secret files are missing or unsafe.",
"unknown": "The fixed Forge secret-file metadata could not be verified.",
},
}
@@ -114,10 +135,6 @@ def _checked_controller_result(
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"
@@ -126,7 +143,7 @@ def _checked_controller_result(
raw_evidence = result.get("evidence", {})
if not isinstance(raw_evidence, dict) or set(raw_evidence) - {
"mainRevision", "sourceManifestSha256", "resourceCollisionCount",
"workspaceImagePresent", "frontdoorImagePresent",
"workspaceImagePresent", "frontdoorImagePresent", "attestations",
}:
return None
evidence: dict[str, Any] = {}
@@ -148,6 +165,84 @@ def _checked_controller_result(
if not isinstance(value, bool):
return None
evidence[name] = value
attestations = raw_evidence.get("attestations")
oidc_rejected = False
validated_attestations: dict[str, dict[str, Any]] = {}
if attestations is not None:
if not isinstance(attestations, dict) or set(attestations) - {
"push_validation", "image_registry", "oidc", "secret_delivery",
}:
return None
push = attestations.get("push_validation")
if push is not None and (
not isinstance(push, dict)
or set(push) != {"context", "conclusion", "revision"}
or push.get("context") != "Managed validation / full (push)"
or push.get("conclusion") != "success"
or push.get("revision") != profile.revision
):
return None
if push is not None:
validated_attestations["push_validation"] = dict(push)
registry = attestations.get("image_registry")
private_owner = _protected_forge_private_package_owner()
if registry is not None and (
not isinstance(registry, dict)
or private_owner is None
or registry.get("workspacePullable") is not True
or registry.get("frontdoorPullable") is not True
or registry != {
"owner": private_owner,
"ownerVisibility": "private",
"workspaceDigest": profile.workspace_image_digest,
"frontdoorDigest": profile.frontdoor_image_digest,
"workspacePullable": True,
"frontdoorPullable": True,
"workspaceSource": _FORGE_SOURCE_URL,
"workspaceRevision": profile.revision,
"frontdoorSource": _FORGE_SOURCE_URL,
"frontdoorRevision": profile.revision,
}
):
return None
if registry is not None:
validated_attestations["image_registry"] = dict(registry)
oidc = attestations.get("oidc")
if oidc is not None:
if raw_checks["oidc"]["status"] != "passed":
return None
if forge_oidc_attestation_matches(oidc):
validated_attestations["oidc"] = dict(oidc)
else:
oidc_rejected = True
elif raw_checks["oidc"]["status"] == "passed":
oidc_rejected = True
secret_delivery = attestations.get("secret_delivery")
if secret_delivery is not None and (
not isinstance(secret_delivery, dict)
or set(secret_delivery) != {"requiredFilesReady", "metadataSha256"}
or secret_delivery.get("requiredFilesReady") is not True
or not isinstance(secret_delivery.get("metadataSha256"), str)
or not re.fullmatch(r"^[0-9a-f]{64}$", secret_delivery["metadataSha256"])
):
return None
if secret_delivery is not None:
validated_attestations["secret_delivery"] = dict(secret_delivery)
if any(
raw_checks[name]["status"] == "passed" and
(not isinstance(attestations, dict) or name not in attestations)
for name in ("push_validation", "image_registry", "secret_delivery")
):
return None
if raw_checks["oidc"]["status"] == "passed" and "oidc" not in validated_attestations:
oidc_rejected = True
if oidc_rejected:
for check in checks:
if check["name"] == "oidc":
check.update(_check("oidc", "blocked"))
break
if validated_attestations:
evidence["attestations"] = validated_attestations
return checks, evidence
@@ -260,6 +355,7 @@ class ForgeBootstrapPreflightService:
},
evidence_json={
"profile": "forge-production-bootstrap",
"policyId": exact[0].id if policy_ready else None,
"workspaceImageDigest": profile.workspace_image_digest,
"frontdoorImageDigest": profile.frontdoor_image_digest,
"controller": controller_evidence,
@@ -302,6 +398,7 @@ class ForgeBootstrapPreflightService:
"status": job.status,
"stage": job.stage,
"preflight": job.preflight_json,
"preflightSha256": canonical_preflight_receipt(job),
"evidence": job.evidence_json,
"result": job.result_summary,
"brokerRequestId": job.broker_request_id,
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,834 @@
from __future__ import annotations
import json
import os
import secrets
import stat
import time
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from threading import Lock
from typing import Any
from sqlmodel import Session, select
from ..connectors.authentik import AuthentikConnector
from ..core.config import settings
from ..db.models import Policy
from ..schemas.forge_oidc import (
FORGE_OIDC_ACTION,
FORGE_OIDC_BINDING_SECRET_REF,
FORGE_OIDC_CLIENT_ID,
FORGE_OIDC_SECRET_REF,
FORGE_OIDC_TARGET,
ForgeOidcBindingV1,
ForgeOidcProvisionResultV1,
ForgeOidcScopeMappingIdsV1,
)
from .audit_logger import log_event
from .policy_engine import evaluate_policy
from .secret_store import get_secret, has_secret, secret_storage_status, set_secret
_SECRET_LOCK = Lock()
_PROVISION_LOCK_FILENAME = ".forge-oidc-provision.lock"
_PROVISION_LOCK_TIMEOUT_SECONDS = 30.0
_PROVISION_LOCK_POLL_INTERVAL_SECONDS = 0.05
_FORGE_OIDC_LOCK_ROOT = Path("/run/itworx-appops/forge-oidc-provision")
class _ProvisionLockUnavailable(RuntimeError):
pass
def _is_reparse_point(stat_result: os.stat_result) -> bool:
attributes = getattr(stat_result, "st_file_attributes", 0)
reparse_flag = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
return bool(attributes & reparse_flag)
def _windows_current_user_sid() -> bytes:
"""Return the effective Windows token user SID without shelling out."""
import ctypes
from ctypes import wintypes
advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
advapi32.OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE)]
advapi32.OpenProcessToken.restype = wintypes.BOOL
advapi32.GetTokenInformation.argtypes = [
wintypes.HANDLE,
wintypes.DWORD,
ctypes.c_void_p,
wintypes.DWORD,
ctypes.POINTER(wintypes.DWORD),
]
advapi32.GetTokenInformation.restype = wintypes.BOOL
advapi32.GetLengthSid.argtypes = [ctypes.c_void_p]
advapi32.GetLengthSid.restype = wintypes.DWORD
kernel32.GetCurrentProcess.argtypes = []
kernel32.GetCurrentProcess.restype = wintypes.HANDLE
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
kernel32.CloseHandle.restype = wintypes.BOOL
token = wintypes.HANDLE()
if not advapi32.OpenProcessToken(
kernel32.GetCurrentProcess(), 0x0008, ctypes.byref(token)
):
raise OSError(ctypes.get_last_error(), "OpenProcessToken failed")
try:
required = wintypes.DWORD()
advapi32.GetTokenInformation(token, 1, None, 0, ctypes.byref(required))
if not required.value:
raise OSError(ctypes.get_last_error(), "GetTokenInformation failed")
token_user = ctypes.create_string_buffer(required.value)
if not advapi32.GetTokenInformation(
token, 1, token_user, required.value, ctypes.byref(required)
):
raise OSError(ctypes.get_last_error(), "GetTokenInformation failed")
sid = ctypes.cast(token_user, ctypes.POINTER(ctypes.c_void_p)).contents.value
sid_length = advapi32.GetLengthSid(ctypes.c_void_p(sid))
if not sid or not sid_length:
raise OSError("invalid current-user SID")
return ctypes.string_at(sid, sid_length)
finally:
kernel32.CloseHandle(token)
def _windows_private_path(path: Path, *, require_current_owner: bool) -> None:
"""Reject Windows paths whose owner/DACL lets an untrusted principal mutate them."""
import ctypes
from ctypes import wintypes
advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
advapi32.GetNamedSecurityInfoW.argtypes = [
wintypes.LPWSTR,
wintypes.DWORD,
wintypes.DWORD,
ctypes.POINTER(ctypes.c_void_p),
ctypes.POINTER(ctypes.c_void_p),
ctypes.POINTER(ctypes.c_void_p),
ctypes.POINTER(ctypes.c_void_p),
ctypes.POINTER(ctypes.c_void_p),
]
advapi32.GetNamedSecurityInfoW.restype = wintypes.DWORD
advapi32.GetLengthSid.argtypes = [ctypes.c_void_p]
advapi32.GetLengthSid.restype = wintypes.DWORD
advapi32.ConvertStringSidToSidW.argtypes = [wintypes.LPWSTR, ctypes.POINTER(ctypes.c_void_p)]
advapi32.ConvertStringSidToSidW.restype = wintypes.BOOL
advapi32.GetAclInformation.argtypes = [
ctypes.c_void_p,
ctypes.c_void_p,
wintypes.DWORD,
wintypes.DWORD,
]
advapi32.GetAclInformation.restype = wintypes.BOOL
advapi32.GetAce.argtypes = [ctypes.c_void_p, wintypes.DWORD, ctypes.POINTER(ctypes.c_void_p)]
advapi32.GetAce.restype = wintypes.BOOL
kernel32.LocalFree.argtypes = [ctypes.c_void_p]
kernel32.LocalFree.restype = ctypes.c_void_p
owner = ctypes.c_void_p()
dacl = ctypes.c_void_p()
descriptor = ctypes.c_void_p()
# SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
result = advapi32.GetNamedSecurityInfoW(
str(path),
1,
0x1 | 0x4,
ctypes.byref(owner),
None,
ctypes.byref(dacl),
None,
ctypes.byref(descriptor),
)
if result != 0 or not owner.value or not dacl.value:
raise OSError(int(result or ctypes.get_last_error()), "security descriptor unavailable")
try:
current_sid = _windows_current_user_sid()
sid_size = advapi32.GetLengthSid(owner)
owner_sid = ctypes.string_at(owner.value, sid_size)
if require_current_owner and owner_sid != current_sid:
raise OSError("path owner is not the current user")
# SYSTEM and local Administrators are trusted operating-system principals.
trusted_sids = {current_sid}
for sid_text in (
"S-1-5-18", # LocalSystem
"S-1-5-32-544", # Builtin Administrators
"S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464", # TrustedInstaller
):
sid_buffer = ctypes.c_void_p()
if not advapi32.ConvertStringSidToSidW(sid_text, ctypes.byref(sid_buffer)):
raise OSError(ctypes.get_last_error(), "trusted SID unavailable")
try:
sid_size = advapi32.GetLengthSid(sid_buffer)
trusted_sids.add(ctypes.string_at(sid_buffer.value, sid_size))
finally:
kernel32.LocalFree(sid_buffer)
# Reject any explicit or inherited allow ACE outside the trusted set that
# can create/replace/delete this path or write to a lock file.
class _AclSizeInformation(ctypes.Structure):
_fields_ = [
("AceCount", wintypes.DWORD),
("AclBytesInUse", wintypes.DWORD),
("AclBytesFree", wintypes.DWORD),
]
acl_info = _AclSizeInformation()
if not advapi32.GetAclInformation(
dacl,
ctypes.byref(acl_info),
ctypes.sizeof(acl_info),
2,
):
raise OSError(ctypes.get_last_error(), "ACL unavailable")
write_rights = (
0x00000002 # FILE_WRITE_DATA / FILE_ADD_FILE
| 0x00000004 # FILE_APPEND_DATA / FILE_ADD_SUBDIRECTORY
| 0x00000010 # FILE_WRITE_EA
| 0x00000040 # FILE_DELETE_CHILD
| 0x00000100 # FILE_WRITE_ATTRIBUTES
| 0x00010000 # DELETE
| 0x00040000 # WRITE_DAC
| 0x00080000 # WRITE_OWNER
| 0x10000000 # GENERIC_ALL
| 0x40000000 # GENERIC_WRITE
)
for index in range(acl_info.AceCount):
ace = ctypes.c_void_p()
if not advapi32.GetAce(dacl, index, ctypes.byref(ace)):
raise OSError(ctypes.get_last_error(), "ACL entry unavailable")
header = ctypes.cast(ace, ctypes.POINTER(ctypes.c_ubyte * 2)).contents
ace_type = int(header[0])
if ace_type != 0: # ACCESS_ALLOWED_ACE
if ace_type not in {1, 2, 3, 6, 7, 8, 10, 12, 13, 14}:
# Deny and audit entries do not grant rights. Unknown ACEs
# could grant access and therefore make this path untrusted.
raise OSError("unsupported ACL entry")
continue
mask = ctypes.cast(
ctypes.c_void_p(ace.value + 4), ctypes.POINTER(wintypes.DWORD)
).contents.value
sid_address = ace.value + 8
sid_size = advapi32.GetLengthSid(ctypes.c_void_p(sid_address))
ace_sid = ctypes.string_at(sid_address, sid_size)
if mask & write_rights and ace_sid not in trusted_sids:
raise OSError("path ACL grants write access to an untrusted principal")
finally:
if descriptor.value:
kernel32.LocalFree(descriptor)
def _lock_root() -> Path:
# Linux containers share this private runtime path among their worker
# processes. Windows keeps the equivalent lock beside its protected data.
return Path(settings.data_dir) if os.name == "nt" else _FORGE_OIDC_LOCK_ROOT
def _validate_lock_data_dir(data_dir: Path) -> None:
if not data_dir.is_absolute():
raise OSError("lock directory must be absolute")
# Inspect each existing path component without following symlinks/reparse points.
components = [Path(data_dir.anchor)]
for part in data_dir.parts[1:]:
components.append(components[-1] / part)
private_components = {data_dir, data_dir.parent}
for index, component in enumerate(components):
try:
component_stat = os.lstat(component)
except FileNotFoundError:
if os.name == "nt" or component not in private_components:
raise OSError("lock directory path is missing") from None
try:
os.mkdir(component, 0o700)
except FileExistsError:
# Another worker may have created the same fixed private child;
# validate its metadata below before using it.
pass
component_stat = os.lstat(component)
if _is_reparse_point(component_stat) or not stat.S_ISDIR(component_stat.st_mode):
raise OSError("lock directory contains an unsafe path component")
is_private_component = component in private_components
if os.name == "nt":
_windows_private_path(component, require_current_owner=component == data_dir)
continue
effective_uid = os.geteuid()
if component_stat.st_uid not in {effective_uid, 0}:
raise OSError("lock directory path has an untrusted owner")
mode = stat.S_IMODE(component_stat.st_mode)
if is_private_component:
if component_stat.st_uid != effective_uid or mode != 0o700:
raise OSError("lock directory is not private to its owner")
elif mode & 0o022 and not (
component_stat.st_uid == 0
and mode & stat.S_ISVTX
and mode & 0o002
):
# Root-owned sticky directories such as /tmp safely contain private,
# owner-checked children; other writable ancestors are not trusted.
raise OSError("lock directory has a writable untrusted ancestor")
def _validate_lock_file(lock_fd: int, lock_path: Path) -> None:
opened = os.fstat(lock_fd)
named = os.lstat(lock_path)
if (
_is_reparse_point(named)
or not stat.S_ISREG(opened.st_mode)
or not stat.S_ISREG(named.st_mode)
or not os.path.samestat(opened, named)
or opened.st_nlink != 1
or opened.st_size > 1
):
raise OSError("lock file is not a private regular file")
if os.name == "nt":
_windows_private_path(lock_path, require_current_owner=True)
return
if opened.st_uid != os.geteuid() or stat.S_IMODE(opened.st_mode) & 0o077:
raise OSError("lock file is not private to its owner")
@contextmanager
def _forge_oidc_process_lock() -> Iterator[None]:
"""Serialize provisioning across workers in one AppOps runtime."""
deadline = time.monotonic() + _PROVISION_LOCK_TIMEOUT_SECONDS
thread_locked = _SECRET_LOCK.acquire(
timeout=max(0.0, deadline - time.monotonic())
)
if not thread_locked:
raise _ProvisionLockUnavailable from None
lock_fd: int | None = None
locked = False
try:
try:
lock_root = _lock_root()
_validate_lock_data_dir(lock_root)
lock_path = lock_root / _PROVISION_LOCK_FILENAME
flags = os.O_RDWR
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
if hasattr(os, "O_CLOEXEC"):
flags |= os.O_CLOEXEC
try:
lock_fd = os.open(str(lock_path), flags | os.O_CREAT | os.O_EXCL, 0o600)
except FileExistsError:
lock_fd = os.open(str(lock_path), flags)
_validate_lock_file(lock_fd, lock_path)
if os.name == "nt":
import msvcrt
if os.fstat(lock_fd).st_size == 0:
os.write(lock_fd, b"\0")
_validate_lock_file(lock_fd, lock_path)
while True:
os.lseek(lock_fd, 0, os.SEEK_SET)
try:
msvcrt.locking(lock_fd, msvcrt.LK_NBLCK, 1)
break
except OSError:
if time.monotonic() >= deadline:
raise TimeoutError("provision_lock_timeout") from None
time.sleep(
min(
_PROVISION_LOCK_POLL_INTERVAL_SECONDS,
max(0.0, deadline - time.monotonic()),
)
)
else:
import fcntl
while True:
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except BlockingIOError:
if time.monotonic() >= deadline:
raise TimeoutError("provision_lock_timeout") from None
time.sleep(
min(
_PROVISION_LOCK_POLL_INTERVAL_SECONDS,
max(0.0, deadline - time.monotonic()),
)
)
locked = True
except Exception: # noqa: BLE001 - all lock setup errors fail closed.
# Keep platform-specific path/ACL errors internal and fail closed
# with the stable service error emitted by the caller.
if lock_fd is not None:
try:
os.close(lock_fd)
except OSError:
pass
lock_fd = None
raise _ProvisionLockUnavailable from None
yield
finally:
if lock_fd is not None:
if locked:
try:
if os.name == "nt":
import msvcrt
os.lseek(lock_fd, 0, os.SEEK_SET)
msvcrt.locking(lock_fd, msvcrt.LK_UNLCK, 1)
else:
import fcntl
fcntl.flock(lock_fd, fcntl.LOCK_UN)
except OSError:
# Closing the descriptor also releases its OS lock.
pass
try:
os.close(lock_fd)
except OSError:
pass
if thread_locked:
_SECRET_LOCK.release()
def _new_client_secret() -> str:
return secrets.token_urlsafe(48)
def _result(
*,
status: str,
message: str,
success: bool = False,
secret_created: bool = False,
application_id: str | None = None,
provider_id: str | None = None,
) -> dict[str, Any]:
return ForgeOidcProvisionResultV1(
success=success,
status=status,
message=message,
secret_created=secret_created,
application_id=application_id,
provider_id=provider_id,
).model_dump(mode="json", by_alias=True)
def _exact_policy_allowed(session: Session) -> bool:
rows = session.exec(
select(Policy).where(
(Policy.action == FORGE_OIDC_ACTION)
& (Policy.target == FORGE_OIDC_TARGET)
)
).all()
if len(rows) != 1:
return False
policy = rows[0]
if policy.manual != "allowed" or policy.voice != "blocked" or policy.risk != "critical":
return False
decision = evaluate_policy(
session,
action=FORGE_OIDC_ACTION,
target=FORGE_OIDC_TARGET,
source="manual",
)
return (
decision.get("decision") == "allowed"
and decision.get("scope_decision") == "allowed"
and decision.get("policy_id") == policy.id
)
def _safe_connector_id(value: Any) -> str | None:
candidate = str(value or "")
if not candidate or len(candidate) > 128:
return None
if not all(character.isalnum() or character in "._:-" for character in candidate):
return None
return candidate
def _audit(
session: Session,
*,
identity: str,
decision: str,
reason: str,
result: str,
secret_created: bool = False,
) -> None:
log_event(
session,
source="manual",
identity=identity,
action_id=FORGE_OIDC_ACTION,
target=FORGE_OIDC_TARGET,
target_type="authentik_application",
risk="critical",
policy_decision=decision,
reason=reason,
result=result,
redacted_metadata={
"clientId": FORGE_OIDC_CLIENT_ID,
"secretRef": FORGE_OIDC_SECRET_REF,
"secretCreated": secret_created,
},
)
def _blocked(
session: Session,
*,
identity: str,
message: str,
status: str = "blocked",
decision: str = "blocked",
secret_created: bool = False,
) -> dict[str, Any]:
_audit(
session,
identity=identity,
decision=decision,
reason=message,
result=status,
secret_created=secret_created,
)
return _result(status=status, message=message, secret_created=secret_created)
def _existing_secret(session: Session) -> str | None:
if not has_secret(session, FORGE_OIDC_SECRET_REF):
return None
return get_secret(session, FORGE_OIDC_SECRET_REF, touch=False)
def _persist_forge_oidc_binding(
session: Session,
*,
scope_mapping_ids: Any,
provider_id: str | None = None,
) -> bool:
"""Persist one validated mapping tuple while preserving a known provider ID."""
from .setup_service import _apply_forge_oidc_binding_to_runtime
try:
mappings = ForgeOidcScopeMappingIdsV1.model_validate(scope_mapping_ids)
except Exception: # noqa: BLE001 - invalid binding data is never serialized.
return False
binding_exists = has_secret(session, FORGE_OIDC_BINDING_SECRET_REF)
binding_valid = _apply_forge_oidc_binding_to_runtime(session)
if binding_exists and not binding_valid:
return False
current_ids = {
"openid": settings.apis.get("AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING"),
"email": settings.apis.get("AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING"),
"profile": settings.apis.get("AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING"),
}
new_ids = {key: str(getattr(mappings, key)) for key in ("openid", "email", "profile")}
if binding_valid and current_ids != new_ids:
return False
current_provider_id = settings.apis.get("AUTHENTIK_FORGE_OIDC_PROVIDER_ID") if binding_valid else None
if current_provider_id is not None and provider_id is not None and current_provider_id != provider_id:
return False
selected_provider_id = provider_id if provider_id is not None else current_provider_id
try:
binding = ForgeOidcBindingV1(
version=1,
providerId=selected_provider_id,
scopeMappingIds=new_ids,
)
set_secret(
session,
FORGE_OIDC_BINDING_SECRET_REF,
json.dumps(binding.model_dump(mode="json", by_alias=True), separators=(",", ":"), sort_keys=True),
used_by=["authentik:itworx-forge"],
metadata={"managedBy": "appops-forge-oidc-binding/v1"},
)
except Exception: # noqa: BLE001 - storage and serialization errors stay private.
session.rollback()
return False
return _apply_forge_oidc_binding_to_runtime(session)
def _connector_outcome(
session: Session,
*,
identity: str,
outcome: Any,
secret_created: bool,
) -> dict[str, Any]:
if not isinstance(outcome, dict):
status, message = "failed", "forge_oidc_connector_result_invalid"
success = False
else:
raw_status = outcome.get("status")
mappings = {
"created": ("created", "forge_oidc_created", True),
"already_configured": ("already_configured", "forge_oidc_already_configured", True),
"blocked": ("blocked", "forge_oidc_connector_blocked", False),
"conflict": ("conflict", "forge_oidc_resource_conflict", False),
"failed": ("failed", "forge_oidc_provisioning_failed", False),
"partial_failure": ("partial_failure", "forge_oidc_partial_failure", False),
}
status, message, success = mappings.get(
raw_status,
("failed", "forge_oidc_connector_result_invalid", False),
)
if outcome.get("success") is not True:
success = False
elif status not in {"created", "already_configured"}:
status, message, success = "failed", "forge_oidc_connector_result_invalid", False
application_id = _safe_connector_id(outcome.get("applicationId")) if isinstance(outcome, dict) else None
provider_id = _safe_connector_id(outcome.get("providerId")) if isinstance(outcome, dict) else None
audit_result = "success" if success else status
_audit(
session,
identity=identity,
decision="allowed",
reason=message,
result=audit_result,
secret_created=secret_created,
)
return _result(
status=status,
message=message,
success=success,
secret_created=secret_created,
application_id=application_id,
provider_id=provider_id,
)
def provision_forge_oidc(
session: Session,
*,
actor_identity: str,
authentik: Any | None = None,
) -> dict[str, Any]:
"""Provision only the fixed Forge OIDC client after exact-target governance."""
identity = actor_identity.strip() if isinstance(actor_identity, str) else ""
if not identity:
return _blocked(
session,
identity="system",
message="forge_oidc_operator_identity_required",
)
try:
with _forge_oidc_process_lock():
# Re-evaluate governance after entering the runtime lock so concurrent
# Forge provisioning attempts cannot interleave preflight and writes.
if not _exact_policy_allowed(session):
return _blocked(
session,
identity=identity,
message="forge_oidc_exact_target_policy_required",
)
if secret_storage_status().get("productionReady") is not True:
return _blocked(
session,
identity=identity,
message="forge_oidc_secret_store_not_production_ready",
)
return _provision_forge_oidc_locked(
session,
identity=identity,
authentik=authentik,
)
except _ProvisionLockUnavailable:
return _blocked(
session,
identity=identity,
message="forge_oidc_provision_lock_unavailable",
status="failed",
)
def _provision_forge_oidc_locked(
session: Session,
*,
identity: str,
authentik: Any | None,
) -> dict[str, Any]:
if authentik is None:
try:
from .setup_service import apply_stored_connector_settings
apply_stored_connector_settings(session, {"authentik"})
except Exception: # noqa: BLE001 - never expose connector or stored-secret errors.
return _blocked(
session,
identity=identity,
message="forge_oidc_connector_configuration_unavailable",
status="failed",
decision="allowed",
)
connector = authentik or AuthentikConnector()
# A present but unreadable or tampered binding must never be repaired from
# live discovery: its IDs are protected state, not caller-selected inputs.
from .setup_service import _apply_forge_oidc_binding_to_runtime
binding_exists = has_secret(session, FORGE_OIDC_BINDING_SECRET_REF)
binding_valid = _apply_forge_oidc_binding_to_runtime(session)
if binding_exists and not binding_valid:
return _blocked(
session,
identity=identity,
message="forge_oidc_binding_invalid",
status="conflict",
decision="allowed",
)
secret_created = False
client_secret = _existing_secret(session)
if client_secret is None and has_secret(session, FORGE_OIDC_SECRET_REF):
return _blocked(
session,
identity=identity,
message="forge_oidc_existing_client_secret_unavailable",
status="conflict",
decision="allowed",
)
if settings.apis.get("AUTHENTIK_FORGE_OIDC_PROVIDER_ID") is not None and client_secret is None:
return _blocked(
session,
identity=identity,
message="forge_oidc_existing_client_secret_missing",
status="conflict",
decision="allowed",
)
try:
prerequisites = connector.ensure_forge_oidc_prerequisites(
expected_client_secret=client_secret,
)
except Exception: # noqa: BLE001 - remote connector errors are returned only as a safe status.
prerequisites = {"success": False, "status": "failed"}
if not isinstance(prerequisites, dict) or prerequisites.get("success") is not True:
return _connector_outcome(
session,
identity=identity,
outcome=prerequisites,
secret_created=False,
)
mapping_ids = prerequisites.get("scopeMappingIds")
try:
validated_mapping_ids = ForgeOidcScopeMappingIdsV1.model_validate(mapping_ids)
except Exception: # noqa: BLE001 - connector output is treated as untrusted.
return _connector_outcome(
session,
identity=identity,
outcome={"success": False, "status": "conflict", "message": "forge_oidc_scope_mapping_id_invalid"},
secret_created=False,
)
serialized_mapping_ids = {
key: str(getattr(validated_mapping_ids, key))
for key in ("openid", "email", "profile")
}
if not _persist_forge_oidc_binding(session, scope_mapping_ids=serialized_mapping_ids):
return _connector_outcome(
session,
identity=identity,
outcome={
"success": False,
"status": "partial_failure",
"message": "forge_oidc_binding_persist_failed",
},
secret_created=False,
)
try:
preflight = connector.inspect_forge_oidc_application(
expected_client_secret=client_secret,
)
except Exception: # noqa: BLE001 - remote connector errors are returned only as a safe status.
preflight = {"success": False, "status": "failed"}
if not isinstance(preflight, dict) or preflight.get("success") is not True:
return _connector_outcome(
session,
identity=identity,
outcome=preflight,
secret_created=False,
)
if preflight.get("status") not in {"ready", "provider_orphan", "already_configured"}:
return _connector_outcome(
session,
identity=identity,
outcome={"success": False, "status": "failed"},
secret_created=False,
)
if preflight.get("status") != "ready" and client_secret is None:
return _blocked(
session,
identity=identity,
message="forge_oidc_existing_client_secret_missing",
status="conflict",
decision="allowed",
)
if client_secret is None:
client_secret = _new_client_secret()
if not 32 <= len(client_secret) <= 255 or not client_secret.isascii() or not client_secret.isprintable():
return _blocked(
session,
identity=identity,
message="forge_oidc_client_secret_generation_failed",
status="failed",
decision="allowed",
)
try:
set_secret(
session,
FORGE_OIDC_SECRET_REF,
client_secret,
used_by=["authentik:itworx-forge"],
metadata={"managedBy": "appops-forge-oidc/v1"},
)
except Exception: # noqa: BLE001 - secret-store errors are intentionally sanitized.
session.rollback()
return _blocked(
session,
identity=identity,
message="forge_oidc_client_secret_store_failed",
status="failed",
decision="allowed",
)
secret_created = True
elif (
not isinstance(client_secret, str)
or not 32 <= len(client_secret) <= 255
or not client_secret.isascii()
or not client_secret.isprintable()
):
return _blocked(
session,
identity=identity,
message="forge_oidc_existing_client_secret_unavailable",
status="conflict",
decision="allowed",
)
try:
outcome = connector.ensure_forge_oidc_application(
client_secret=client_secret,
persist_provider_id=lambda provider_id: _persist_forge_oidc_binding(
session,
scope_mapping_ids=serialized_mapping_ids,
provider_id=provider_id,
),
)
except Exception: # noqa: BLE001 - no connector response/body crosses the route boundary.
outcome = {"success": False, "status": "failed"}
return _connector_outcome(
session,
identity=identity,
outcome=outcome,
secret_created=secret_created,
)
@@ -0,0 +1,599 @@
from __future__ import annotations
import hashlib
import hmac
import json
import re
from typing import Any
from uuid import RFC_4122, UUID
from sqlmodel import Session, select
from ..connectors.authentik import AuthentikConnector
from ..core.config import settings
from ..db.models import AppSetting, Policy
from ..schemas.enrollment import MANAGED_DOCKER_APPDATA_ROOT
from ..schemas.forge_bootstrap import ForgeBootstrapPreflightV1
from ..schemas.forge_oidc import FORGE_OIDC_ISSUER, FORGE_OIDC_LAUNCH_URL
from ..schemas.forge_secret_delivery import (
ForgeSecretDeliveryControllerArtifactV1,
ForgeSecretDeliveryResultV1,
)
from .audit_logger import log_event
from .deployment_broker import DeploymentBrokerClient
from .enrollment_plane import EnrollmentPlane
from .forge_oidc_provisioning import _forge_oidc_process_lock
from .policy_engine import evaluate_policy
from .secret_store import get_secret, has_secret, secret_storage_status
_ACTION = "forge.secrets.prepare"
_TARGET = "itworx-forge"
_BROKER_SECRET_REF = "FORGE_DEPLOYMENT_BROKER"
_FORGE_OIDC_BINDING_REF = "AUTHENTIK_FORGE_OIDC_BINDING"
_FORGE_OIDC_CLIENT_SECRET_REF = "ITWORX_FORGE_OIDC_CLIENT_SECRET"
_FORGE_READER_BINDING_REF = "AUTHENTIK_FORGE_READER_BINDING"
_FORGE_READER_TOKEN_REF = "ITWORX_FORGE_AUTHENTIK_READ_TOKEN"
_FLOW = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\Z")
_PROVIDER_ID = re.compile(r"[1-9][0-9]{0,9}\Z")
_UUID = re.compile(
r"[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\Z"
)
_IDEMPOTENCY = re.compile(r"[A-Za-z0-9._:-]{8,128}\Z")
_CONFIG_KEYS = {
"schema",
"issuer",
"audience",
"client_id",
"authentik_api_url",
"launch_url",
"authorization_flow",
"invalidation_flow",
"authentik_openid_mapping_id",
"authentik_email_mapping_id",
"authentik_profile_mapping_id",
"authentik_provider_id",
"authentik_signing_key_id",
"private_package_owner",
}
_RESPONSE_KEYS = {
"apiVersion",
"target",
"status",
"profile",
"controllerArtifact",
"secretFilesReady",
"masterKeyBackupVerified",
"applyPreflight",
}
_ARTIFACT_KEYS = {
"controllerVersion",
"sourceRepository",
"sourceRevision",
"sourceTree",
"artifactSha256",
}
def _exact_secret_prepare_policy_allowed(session: Session) -> str | None:
rows = session.exec(select(Policy).where(Policy.action == _ACTION)).all()
if len(rows) != 1:
return None
policy = rows[0]
if (
policy.target != _TARGET
or policy.manual != "allowed"
or policy.voice != "blocked"
or policy.risk != "critical"
):
return None
decision = evaluate_policy(
session,
action=_ACTION,
target=_TARGET,
source="manual",
)
if (
decision.get("decision") != "allowed"
or decision.get("scope_decision") != "allowed"
or decision.get("policy_id") != policy.id
):
return None
return policy.id
def _secret_store_ready() -> bool:
try:
status = secret_storage_status()
except Exception: # noqa: BLE001 - store diagnostics never enter delivery errors.
return False
return isinstance(status, dict) and status.get("productionReady") is True
def _canonical_uuid(value: Any) -> bool:
if not isinstance(value, str) or _UUID.fullmatch(value) is None:
return False
try:
parsed = UUID(value)
except (TypeError, ValueError, AttributeError):
return False
return str(parsed) == value and parsed.variant == RFC_4122 and parsed.version in range(1, 9)
def _protected_config(context: dict[str, Any]) -> bytes | None:
if not isinstance(context, dict) or set(context) != {
"applicationId", "providerId", "scopeMappingIds", "groupIds", "signingKeyId"
}:
return None
provider_id = context.get("providerId")
signing_key_id = context.get("signingKeyId")
mappings = context.get("scopeMappingIds")
if (
_PROVIDER_ID.fullmatch(provider_id or "") is None
or not _canonical_uuid(signing_key_id)
or not isinstance(mappings, dict)
or set(mappings) != {"openid", "email", "profile"}
or any(not _canonical_uuid(item) for item in mappings.values())
or len(set(mappings.values())) != 3
):
return None
if not _canonical_uuid(context.get("applicationId")):
return None
owner = _protected_forge_private_package_owner()
if owner is None:
return None
authorization_flow = settings.apis.get("AUTHENTIK_OIDC_AUTHORIZATION_FLOW")
invalidation_flow = settings.apis.get("AUTHENTIK_OIDC_INVALIDATION_FLOW")
if (
not isinstance(authorization_flow, str)
or _FLOW.fullmatch(authorization_flow) is None
or not isinstance(invalidation_flow, str)
or _FLOW.fullmatch(invalidation_flow) is None
):
return None
values = {
"schema": "itworx.forge-first-install-config/v4",
"issuer": FORGE_OIDC_ISSUER,
"audience": "itworx-forge",
"client_id": "itworx-forge",
"authentik_api_url": "https://auth.example-host.com",
"launch_url": FORGE_OIDC_LAUNCH_URL,
"authorization_flow": authorization_flow,
"invalidation_flow": invalidation_flow,
"authentik_openid_mapping_id": mappings["openid"],
"authentik_email_mapping_id": mappings["email"],
"authentik_profile_mapping_id": mappings["profile"],
"authentik_provider_id": provider_id,
"authentik_signing_key_id": signing_key_id,
"private_package_owner": owner,
}
if set(values) != _CONFIG_KEYS or any(
not isinstance(value, str) or not value or len(value) > 512
for value in values.values()
):
return None
encoded = json.dumps(values, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
return encoded if len(encoded) <= 8192 else None
def _checked_controller_response(
value: object,
*,
profile: ForgeBootstrapPreflightV1,
) -> ForgeSecretDeliveryControllerArtifactV1 | None:
if not isinstance(value, dict) or set(value) != _RESPONSE_KEYS:
return None
if (
value.get("apiVersion") != "itworx.forge-secret-delivery/v1"
or value.get("target") != _TARGET
or value.get("status") != "bundle_ready"
or value.get("profile") != profile.model_dump(mode="json")
or type(value.get("secretFilesReady")) is not int
or value.get("secretFilesReady") != 7
or value.get("masterKeyBackupVerified") is not True
or value.get("applyPreflight") != "not_evaluated"
):
return None
artifact = value.get("controllerArtifact")
if not isinstance(artifact, dict) or set(artifact) != _ARTIFACT_KEYS:
return None
try:
return ForgeSecretDeliveryControllerArtifactV1.model_validate(artifact)
except Exception: # noqa: BLE001 - controller response data is untrusted.
return None
def _result(
*,
profile: ForgeBootstrapPreflightV1,
status: str,
message: str,
artifact: ForgeSecretDeliveryControllerArtifactV1 | None = None,
) -> dict[str, Any]:
ready = status == "bundle_ready"
payload = ForgeSecretDeliveryResultV1(
success=ready,
status=status,
message=message,
target=_TARGET,
revision=profile.revision,
controllerArtifact=artifact,
secretFilesReady=7 if ready else None,
masterKeyBackupVerified=True if ready else None,
applyPreflight="not_evaluated" if ready else None,
)
return payload.model_dump(mode="json", by_alias=True, exclude_none=True)
def _audit(
session: Session,
*,
identity: str,
decision: str,
result: dict[str, Any],
policy_id: str | None,
idempotency_key: str,
) -> bool:
try:
artifact = result.get("controllerArtifact")
log_event(
session,
source="manual",
identity=identity,
action_id=_ACTION,
target=_TARGET,
target_type="forge_secret_delivery",
risk="critical",
policy_decision=decision,
reason=result["message"],
result=result["status"],
after_state={
"status": result["status"],
"revision": result["revision"],
"secretFilesReady": result.get("secretFilesReady"),
"masterKeyBackupVerified": result.get("masterKeyBackupVerified"),
"applyPreflight": result.get("applyPreflight"),
"controllerArtifact": artifact,
},
redacted_metadata={
"policyId": policy_id,
"revision": result["revision"],
"idempotencyKeySha256": hashlib.sha256(idempotency_key.encode("ascii")).hexdigest(),
"secretRefs": [
_BROKER_SECRET_REF,
_FORGE_OIDC_CLIENT_SECRET_REF,
_FORGE_READER_TOKEN_REF,
],
},
)
except Exception: # noqa: BLE001 - an audit exception may carry held secrets.
try:
session.rollback()
except Exception: # noqa: BLE001 - keep DB error detail out of the response.
pass
return False
return True
def _protected_forge_private_package_owner() -> str | None:
from .forge_bootstrap_apply import (
_protected_forge_private_package_owner as protected_owner,
)
return protected_owner()
def _fixed_authentik_connector(session: Session) -> AuthentikConnector | None:
"""Use only already-loaded, fixed Authentik runtime configuration.
The generic setup loader also migrates legacy plaintext connector secrets.
Secret delivery must not trigger that write as a side effect, so this path
verifies the existing runtime against protected/read-only inputs instead.
"""
try:
legacy_secret = session.get(AppSetting, "connector:authentik:secret")
if legacy_secret is not None:
if legacy_secret.is_secret is not True or not isinstance(legacy_secret.value_json, dict):
return None
legacy_value = legacy_secret.value_json.get("value")
if isinstance(legacy_value, str) and legacy_value.strip():
return None
if legacy_secret.value_json not in (
{},
{"secretRef": "AUTHENTIK_TOKEN", "hasSecret": True},
{"secretRef": "AUTHENTIK_TOKEN", "hasSecret": True, "migrated": True},
):
return None
connector_config = session.get(AppSetting, "connector:authentik:config")
if connector_config is not None:
if connector_config.is_secret is not False or not isinstance(connector_config.value_json, dict):
return None
if not set(connector_config.value_json).issubset(
{"url", "verifyTls", "outpostUrl", "expectEmbeddedOutpost"}
):
return None
connector = AuthentikConnector()
config = connector._config()
if (
config is None
or config.url.rstrip("/") != "https://auth.example-host.com"
or config.verify_tls is not True
or not isinstance(config.token, str)
or not config.token
):
return None
stored_admin_token = get_secret(session, "AUTHENTIK_TOKEN", touch=False)
if not isinstance(stored_admin_token, str) or not stored_admin_token:
return None
normalized = stored_admin_token.strip()
if normalized.lower().startswith("bearer "):
normalized = normalized[7:].strip()
if not hmac.compare_digest(normalized, config.token):
return None
if connector_config is not None:
stored_config = connector_config.value_json
runtime_values = {
"url": config.url,
"verifyTls": config.verify_tls,
"outpostUrl": config.outpost_url,
"expectEmbeddedOutpost": config.expect_embedded_outpost,
}
if any(stored_config.get(key) != runtime_values[key] for key in stored_config):
return None
return connector
except Exception: # noqa: BLE001 - config/store failures remain a fixed denial.
return None
def prepare_forge_secret_delivery(
session: Session,
*,
profile: ForgeBootstrapPreflightV1,
idempotency_key: str,
actor_identity: str,
) -> dict[str, Any]:
"""Prepare only the fixed Forge credential bundle through the trusted broker."""
identity = actor_identity.strip() if isinstance(actor_identity, str) else ""
request_key = idempotency_key.strip() if isinstance(idempotency_key, str) else ""
if not identity:
return _result(
profile=profile,
status="blocked",
message="forge_secret_operator_identity_required",
)
if _IDEMPOTENCY.fullmatch(request_key) is None:
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_idempotency_key_invalid",
)
_audit(
session,
identity=identity,
decision="blocked",
result=outcome,
policy_id=None,
idempotency_key="invalid-key",
)
return outcome
policy_id = _exact_secret_prepare_policy_allowed(session)
if policy_id is None:
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_exact_target_policy_required",
)
_audit(
session,
identity=identity,
decision="blocked",
result=outcome,
policy_id=None,
idempotency_key=request_key,
)
return outcome
if not _secret_store_ready():
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_store_not_production_ready",
)
audit_ok = _audit(
session,
identity=identity,
decision="allowed",
result=outcome,
policy_id=policy_id,
idempotency_key=request_key,
)
return outcome if audit_ok else _result(
profile=profile, status="failed", message="forge_secret_audit_persist_failed"
)
attempted_delivery = False
try:
with _forge_oidc_process_lock():
if _exact_secret_prepare_policy_allowed(session) != policy_id:
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_exact_target_policy_required",
)
elif not _secret_store_ready():
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_store_not_production_ready",
)
else:
trust = EnrollmentPlane.get_trust_config(session)
if (
trust is None
or trust.trusted_appdata_root != MANAGED_DOCKER_APPDATA_ROOT
or trust.managed_appdata_backup_acknowledged is not True
):
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_controller_trust_required",
)
elif not all(
has_secret(session, reference)
for reference in (
_FORGE_OIDC_BINDING_REF,
_FORGE_OIDC_CLIENT_SECRET_REF,
_FORGE_READER_BINDING_REF,
_FORGE_READER_TOKEN_REF,
)
):
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_verified_reader_required",
)
else:
from .forge_bootstrap_apply import _controller_secret
from .forge_controller_reader import (
_ReaderFailure,
_verify_forge_delivery_inputs,
)
broker_secret = _controller_secret(session)
if not isinstance(broker_secret, str) or not broker_secret:
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_controller_trust_required",
)
else:
try:
connector = _fixed_authentik_connector(session)
if connector is None:
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_authentik_configuration_required",
)
connector = None
if connector is None:
verified = None
else:
verified = _verify_forge_delivery_inputs(session, connector)
if verified is None:
pass
else:
config_bytes = _protected_config(verified.context)
if config_bytes is None:
outcome = _result(
profile=profile,
status="conflict",
message="forge_secret_protected_config_invalid",
)
elif (
verified.oidc_client_secret in config_bytes
or verified.authentik_read_token in config_bytes
):
outcome = _result(
profile=profile,
status="conflict",
message="forge_secret_config_contains_secret",
)
else:
attempted_delivery = True
broker_result = DeploymentBrokerClient().forge_secret_delivery(
profile,
config_bytes=config_bytes,
oidc_client_secret=verified.oidc_client_secret,
authentik_read_token=verified.authentik_read_token,
secret=broker_secret,
)
if (
isinstance(broker_result, dict)
and broker_result.get("status") in {"missing_config", "missing_secret"}
):
outcome = _result(
profile=profile,
status="blocked",
message="forge_secret_controller_delivery_unavailable",
)
attempted_delivery = False
elif (
isinstance(broker_result, dict)
and broker_result.get("status") == "response"
):
artifact = _checked_controller_response(
broker_result.get("response"),
profile=profile,
)
if artifact is None:
outcome = _result(
profile=profile,
status="outcome_unknown",
message="forge_secret_controller_outcome_unknown",
)
else:
outcome = _result(
profile=profile,
status="bundle_ready",
message="forge_secret_bundle_ready",
artifact=artifact,
)
else:
outcome = _result(
profile=profile,
status="outcome_unknown",
message="forge_secret_controller_outcome_unknown",
)
except _ReaderFailure as exc:
outcome = _result(
profile=profile,
status="blocked" if exc.status == "blocked" else "conflict",
message="forge_secret_reader_proof_invalid",
)
except Exception: # noqa: BLE001 - never surface connector or held input details.
outcome = _result(
profile=profile,
status=("outcome_unknown" if attempted_delivery else "failed"),
message=(
"forge_secret_controller_outcome_unknown"
if attempted_delivery
else "forge_secret_input_verification_failed"
),
)
except Exception: # noqa: BLE001 - lock and DB errors are fixed safe codes.
outcome = _result(
profile=profile,
status="outcome_unknown" if attempted_delivery else "failed",
message=(
"forge_secret_controller_outcome_unknown"
if attempted_delivery
else "forge_secret_preparation_unavailable"
),
)
audit_ok = _audit(
session,
identity=identity,
decision="allowed",
result=outcome,
policy_id=policy_id,
idempotency_key=request_key,
)
if not audit_ok:
if attempted_delivery or outcome["status"] == "bundle_ready":
return _result(
profile=profile,
status="outcome_unknown",
message="forge_secret_delivery_audit_persist_failed",
)
return _result(
profile=profile,
status="failed",
message="forge_secret_audit_persist_failed",
)
return outcome
+23
View File
@@ -164,6 +164,29 @@ def evaluate_policy(
"scope_decision": "allowed" if allowed else None,
}
if action == "forge.oidc.provision":
# This is the only manual Authentik mutation exposed by AppOps. Requiring
# exactly one row for the action prevents a wildcard or duplicate row
# from becoming an alternate authorization path.
policies = session.exec(select(Policy).where(Policy.action == action)).all()
policy = policies[0] if len(policies) == 1 else None
allowed = bool(
source == "manual"
and target == "itworx-forge"
and policy is not None
and policy.target == "itworx-forge"
and policy.manual == "allowed"
and policy.voice == "blocked"
and policy.risk == "critical"
)
return {
"decision": "allowed" if allowed else "blocked",
"reason": "Forge OIDC provisioning requires one exact-target manual policy; voice is always blocked.",
"risk": "critical",
"policy_id": policy.id if allowed and policy is not None else None,
"scope_decision": "allowed" if allowed else None,
}
if action in _DEFAULT_DECISIONS:
reason = _DEFAULT_REASONS.get(action, 'Action uses default governance policy.')
return {
+57
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import json
from typing import Any
from sqlmodel import Session, select
@@ -11,6 +12,7 @@ from ..core.redaction import redact_message
from ..core.time import utc_now
from ..core.network_security import validate_connector_url
from ..db.models import AppContainerMapping, AppSetting, AuditEvent, Connector, DiscoveryRun, Secret
from ..schemas.forge_oidc import FORGE_OIDC_BINDING_SECRET_REF, ForgeOidcBindingV1
from .audit_logger import log_event
from .secret_store import empty_secret_status, get_secret, has_secret, secret_status, secret_storage_status, set_secret
from . import safety_center_service
@@ -219,6 +221,61 @@ def _apply_connector_config_to_runtime(session: Session, connector_id: str) -> N
settings.apis[secret_env] = stored_secret
elif secret_row and secret_row.value_json.get('value'):
settings.apis[secret_env] = secret_row.value_json.get('value')
if connector_id == 'authentik':
_apply_forge_oidc_binding_to_runtime(session)
def _unique_json_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise ValueError('duplicate_json_key')
result[key] = value
return result
def _apply_forge_oidc_binding_to_runtime(session: Session) -> bool:
"""Load protected Forge resource IDs atomically and fail closed on bad state.
This fixed secret reference is intentionally absent from connector setup
fields and response schemas. Generic Authentik settings can only update the
Authentik URL/TLS/token inputs, never these remote resource bindings.
"""
internal_keys = (
'AUTHENTIK_FORGE_OIDC_PROVIDER_ID',
'AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING',
'AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING',
'AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING',
)
settings.apis.update({key: None for key in internal_keys})
settings.apis['AUTHENTIK_FORGE_OIDC_BINDING_VALID'] = False
try:
raw = get_secret(session, FORGE_OIDC_BINDING_SECRET_REF, touch=False)
if not isinstance(raw, str) or not raw:
return False
payload = json.loads(raw, object_pairs_hook=_unique_json_object)
if (
not isinstance(payload, dict)
or set(payload) != {'version', 'providerId', 'scopeMappingIds'}
or not isinstance(payload.get('scopeMappingIds'), dict)
or set(payload['scopeMappingIds']) != {'openid', 'email', 'profile'}
):
return False
binding = ForgeOidcBindingV1.model_validate(payload)
except Exception: # noqa: BLE001 - binding failures never expose encrypted-store contents.
return False
# Only publish values after the whole versioned record and distinct UUIDs
# have passed validation. Keep the stored JSON out of runtime settings.
values = {
'AUTHENTIK_FORGE_OIDC_PROVIDER_ID': binding.provider_id,
'AUTHENTIK_FORGE_OIDC_OPENID_CLAIM_MAPPING': str(binding.scope_mapping_ids.openid),
'AUTHENTIK_FORGE_OIDC_EMAIL_CLAIM_MAPPING': str(binding.scope_mapping_ids.email),
'AUTHENTIK_FORGE_OIDC_PROFILE_CLAIM_MAPPING': str(binding.scope_mapping_ids.profile),
}
settings.apis.update(values)
settings.apis['AUTHENTIK_FORGE_OIDC_BINDING_VALID'] = True
return True
def apply_stored_connector_settings(session: Session, connector_ids: set[str] | None = None) -> None:
@@ -28,6 +28,7 @@ REVISION = "a" * 40
TREE = "b" * 40
CALLER_ID = "c" * 64
MODERN_HELPER_ID = "f" * 64
RETIRED_HELPER_ID = bootstrap.RETIRED_FAILED_MODERN_HELPER_ID
HOST_HOSTNAME = "Tower"
@@ -99,7 +100,7 @@ bash "$work/upgrade.sh" \\
'''
def _legacy_helper_spec() -> dict[str, Any]:
def _legacy_helper_spec(revision: str = REVISION) -> dict[str, Any]:
tree = ast.parse(LEGACY_HELPER_SOURCE_FIXTURE)
function = next(
node
@@ -121,7 +122,7 @@ def _legacy_helper_spec() -> dict[str, Any]:
compile(ast.Module(body=[function], type_ignores=[]), "legacy-promoter", "exec"),
namespace,
)
return namespace["helper_container_spec"](revision=REVISION, image_id=CONTROLLER_IMAGE_ID)
return namespace["helper_container_spec"](revision=revision, image_id=CONTROLLER_IMAGE_ID)
def _image(labels: dict[str, str]) -> dict[str, Any]:
@@ -409,14 +410,43 @@ class FakeDocker:
self.existing_child = existing_child
self.created: list[dict[str, Any]] = []
self.started: list[str] = []
self.removed: list[str] = []
self.inspected: list[str] = []
self.host_name = HOST_HOSTNAME
self.log_bytes = b"controller_v2_ready revision=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
self.named_inspect_override: dict[str, Any] | None = None
self.named_inspect_override_at = 0
self.retired_id_inspect_override: dict[str, Any] | None = None
self.appops_inspect_count = 0
self.appops_inspect_override: dict[str, Any] | None = None
self.appops_inspect_override_at = 0
self._inspect_counts: dict[str, int] = {}
def inspect_container(self, reference: str) -> dict[str, Any] | None:
self.inspected.append(reference)
self._inspect_counts[reference] = self._inspect_counts.get(reference, 0) + 1
if reference == "itworx-controller-artifact-promoter-modern":
if (
self.named_inspect_override is not None
and self._inspect_counts[reference] == self.named_inspect_override_at
):
return self.named_inspect_override
return self.existing_child
if (
reference == RETIRED_HELPER_ID
and self.retired_id_inspect_override is not None
):
return self.retired_id_inspect_override
if (
reference == RETIRED_HELPER_ID
and self.existing_child is not None
and self.existing_child.get("Id") == RETIRED_HELPER_ID
):
return self.existing_child
if reference == APPOPS_CONTAINER_ID:
self.appops_inspect_count += 1
if self.appops_inspect_count == self.appops_inspect_override_at:
return self.appops_inspect_override
return self.containers.get(reference)
def daemon_name(self) -> str:
@@ -444,8 +474,15 @@ class FakeDocker:
source_revision = self.created[-1]["Labels"]["itworx.source.revision"]
self.containers["gitea-deploy-control"] = _modern_controller_inspect(source_revision)
def remove_retired_failed_container(self, identifier: str) -> None:
assert identifier == RETIRED_HELPER_ID
assert self.existing_child is not None
assert self.existing_child["Id"] == RETIRED_HELPER_ID
self.removed.append(identifier)
self.existing_child = None
def container_logs(self, identifier: str, *, tail: int, max_bytes: int) -> bytes:
assert identifier == MODERN_HELPER_ID
assert identifier in {MODERN_HELPER_ID, RETIRED_HELPER_ID}
assert tail <= 40
assert max_bytes <= 8192
return self.log_bytes[:max_bytes]
@@ -458,9 +495,13 @@ class FakeProtectedGit:
self.helper_source = helper_source
self.blobs_read: list[tuple[str, str]] = []
self.main_checks = 0
self.fail_current_main_at: int | None = None
self.retired_tree_valid = True
def verify_current_main(self, revision: str, tree: str) -> None:
self.main_checks += 1
if self.fail_current_main_at == self.main_checks:
raise bootstrap.BootstrapError("controller_promoter_source_not_current_main")
if (revision, tree) != (self.revision, self.tree):
raise bootstrap.BootstrapError("controller_promoter_source_not_current_main")
@@ -468,9 +509,18 @@ class FakeProtectedGit:
if (revision, tree) != (LEGACY_REVISION, LEGACY_TREE):
raise bootstrap.BootstrapError("controller_promoter_legacy_provenance_invalid")
def verify_retired_modern_revision(self, revision: str, tree: str) -> None:
if (revision, tree) != (
bootstrap.RETIRED_FAILED_MODERN_REVISION,
bootstrap.RETIRED_FAILED_MODERN_TREE,
) or not self.retired_tree_valid:
raise bootstrap.BootstrapError(
"controller_promoter_retired_helper_provenance_invalid"
)
def read_blob(self, revision: str, path: str) -> bytes:
self.blobs_read.append((revision, path))
if revision != self.revision or path != "backend/app/services/controller_artifact_helper_spec.py":
if revision not in {self.revision, bootstrap.RETIRED_FAILED_MODERN_REVISION} or path != "backend/app/services/controller_artifact_helper_spec.py":
raise bootstrap.BootstrapError("controller_promoter_source_blob_invalid")
return self.helper_source.encode("utf-8")
@@ -518,7 +568,10 @@ def _run_bootstrap(
*,
health: dict[str, Any] | None = None,
initial_health: dict[str, Any] | None = None,
health_sequence: list[dict[str, Any]] | None = None,
hostname: str = HOST_HOSTNAME,
source_revision: str = REVISION,
source_tree: str = TREE,
):
source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
encoding="utf-8"
@@ -530,15 +583,15 @@ def _run_bootstrap(
version="2.1.12",
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
)
health_reads = [initial, health or _health()]
health_reads = health_sequence or [initial, health or _health()]
def read_health() -> dict[str, Any]:
return health_reads.pop(0) if health_reads else health or _health()
return bootstrap.run_bootstrap(
"jens/unraid-appops-gateway",
REVISION,
TREE,
source_revision,
source_tree,
docker=docker,
protected_git=git,
hostname=hostname,
@@ -549,6 +602,54 @@ def _run_bootstrap(
)
def _retired_failed_child(helper_source: str) -> dict[str, Any]:
spec = bootstrap.render_modern_helper_spec(
helper_source,
revision=bootstrap.RETIRED_FAILED_MODERN_REVISION,
image_id=CONTROLLER_IMAGE_ID,
current_controller_id=CONTROLLER_CONTAINER_ID,
current_controller_revision=CONTROLLER_REVISION,
current_controller_tree=CONTROLLER_TREE,
current_controller_version="2.1.12",
)
child = _inspect_from_spec(
"itworx-controller-artifact-promoter-modern",
RETIRED_HELPER_ID,
CONTROLLER_IMAGE_ID,
spec,
state={"Running": False, "ExitCode": 1, "Status": "exited"},
)
child["Config"]["Volumes"] = None
child["Mounts"] = [
{"Type": "bind", "Source": "/mnt/cache", "Destination": "/mnt/cache", "RW": True, "Propagation": "rprivate"},
{"Type": "bind", "Source": "/mnt/user", "Destination": "/mnt/user", "RW": True, "Propagation": "rprivate"},
{"Type": "bind", "Source": "/var/lib/docker", "Destination": "/var/lib/docker", "RW": False, "Propagation": "rslave"},
{"Type": "bind", "Source": "/srv/example/appdata/gitea/git/repositories", "Destination": "/gitea-repositories", "RW": False, "Propagation": "rprivate"},
{"Type": "bind", "Source": "/var/run/docker.sock", "Destination": "/var/run/docker.sock", "RW": True, "Propagation": "rprivate"},
]
child["HostConfig"].update(
{
"Mounts": None,
"CapAdd": None,
"CapDrop": None,
"Devices": None,
"DeviceRequests": None,
"PortBindings": None,
"VolumesFrom": None,
"PidMode": "",
"UTSMode": "",
"UsernsMode": "",
"CgroupParent": "",
"IpcMode": "private",
"CgroupnsMode": "private",
"Runtime": "runc",
"AutoRemove": False,
"PublishAllPorts": False,
}
)
return child
def test_initial_controller_runtime_accepts_exact_current_compose_container():
docker = FakeDocker(_legacy_helper_spec())
base_image_labels = bootstrap._inspect_controller_base_image(docker)
@@ -876,6 +977,187 @@ def test_running_modern_child_returns_unknown_without_duplicate_or_stop():
assert docker.started == []
def test_exact_failed_historical_child_is_removed_before_current_helper_launch():
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
encoding="utf-8"
)
legacy_health = _health(
revision=CONTROLLER_REVISION,
tree=CONTROLLER_TREE,
version="2.1.12",
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
)
failed_child = _retired_failed_child(helper_source)
docker = FakeDocker(_legacy_helper_spec(), existing_child=failed_child)
git = FakeProtectedGit(helper_source)
result = _run_bootstrap(
docker,
git,
health_sequence=[legacy_health, legacy_health, legacy_health, _health()],
)
assert result["status"] == "promoted"
assert docker.removed == [RETIRED_HELPER_ID]
assert docker.created and docker.started == [MODERN_HELPER_ID]
assert RETIRED_HELPER_ID in docker.inspected
assert docker.appops_inspect_count == 2
assert git.blobs_read.count(
(bootstrap.RETIRED_FAILED_MODERN_REVISION, bootstrap.MODERN_HELPER_SOURCE_PATH)
) == 1
@pytest.mark.parametrize(
"mutation",
[
"running",
"successful-exit",
"boolean-exit",
"other-exit",
"wrong-id",
"config-volumes",
"extra-host-mount",
"cap-add",
"device-request",
"docker-root-writable",
"socket-bind-changed",
"legacy-controller-stopped",
"current-main-changed",
"second-name-inspect-changed",
"second-id-inspect-changed",
"appops-stopped-before-delete",
"historical-tree-proof-failed",
],
)
def test_retired_helper_cleanup_fails_closed_on_any_identity_or_state_drift(mutation: str):
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
encoding="utf-8"
)
failed_child = _retired_failed_child(helper_source)
docker = FakeDocker(_legacy_helper_spec(), existing_child=failed_child)
git = FakeProtectedGit(helper_source)
if mutation == "running":
failed_child["State"].update({"Running": True, "Status": "running"})
elif mutation == "successful-exit":
failed_child["State"]["ExitCode"] = 0
elif mutation == "boolean-exit":
failed_child["State"]["ExitCode"] = True
elif mutation == "other-exit":
failed_child["State"]["ExitCode"] = 2
elif mutation == "wrong-id":
failed_child["Id"] = "9" * 64
elif mutation == "config-volumes":
failed_child["Config"]["Volumes"] = {"/extra": {}}
elif mutation == "extra-host-mount":
failed_child["HostConfig"]["Mounts"] = [{"Type": "volume"}]
elif mutation == "cap-add":
failed_child["HostConfig"]["CapAdd"] = ["SYS_ADMIN"]
elif mutation == "device-request":
failed_child["HostConfig"]["DeviceRequests"] = [{"Capabilities": [["gpu"]]}]
elif mutation == "docker-root-writable":
failed_child["HostConfig"]["Binds"][2] = "/var/lib/docker:/var/lib/docker:rw"
failed_child["Mounts"][2]["RW"] = True
elif mutation == "socket-bind-changed":
failed_child["HostConfig"]["Binds"][4] = "/tmp/docker.sock:/var/run/docker.sock"
failed_child["Mounts"][4]["Source"] = "/tmp/docker.sock"
elif mutation == "legacy-controller-stopped":
docker.containers["gitea-deploy-control"]["State"]["Running"] = False
elif mutation == "current-main-changed":
git.fail_current_main_at = 2
elif mutation == "second-name-inspect-changed":
docker.named_inspect_override = json.loads(json.dumps(failed_child))
docker.named_inspect_override["Config"]["Cmd"].append("unexpected")
docker.named_inspect_override_at = 2
elif mutation == "second-id-inspect-changed":
docker.retired_id_inspect_override = json.loads(json.dumps(failed_child))
docker.retired_id_inspect_override["State"]["ExitCode"] = 0
elif mutation == "appops-stopped-before-delete":
docker.appops_inspect_override = json.loads(
json.dumps(docker.containers[APPOPS_CONTAINER_ID])
)
docker.appops_inspect_override["State"]["Running"] = False
docker.appops_inspect_override_at = 2
elif mutation == "historical-tree-proof-failed":
git.retired_tree_valid = False
legacy_health = _health(
revision=CONTROLLER_REVISION,
tree=CONTROLLER_TREE,
version="2.1.12",
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
)
with pytest.raises(bootstrap.BootstrapError):
_run_bootstrap(
docker,
git,
health_sequence=[legacy_health, legacy_health, legacy_health],
)
assert docker.removed == []
assert docker.created == []
assert docker.started == []
def test_retired_helper_remove_uses_non_force_non_volume_docker_delete():
client = bootstrap.DockerSocketClient()
calls: list[tuple[str, str]] = []
def request(method: str, path: str, **_kwargs: Any) -> tuple[int, bytes]:
calls.append((method, path))
return 204, b""
client._request = request # type: ignore[method-assign]
client.remove_retired_failed_container(RETIRED_HELPER_ID)
assert calls == [
("DELETE", f"/containers/{RETIRED_HELPER_ID}?force=false&v=false")
]
@pytest.mark.parametrize("status", [404, 409])
def test_retired_helper_delete_refuses_unexpected_docker_status(status: int):
client = bootstrap.DockerSocketClient()
def request(_method: str, _path: str, **_kwargs: Any) -> tuple[int, bytes]:
return status, b""
client._request = request # type: ignore[method-assign]
with pytest.raises(bootstrap.BootstrapError):
client.remove_retired_failed_container(RETIRED_HELPER_ID)
def test_same_failed_source_revision_is_never_retried_or_removed():
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
encoding="utf-8"
)
docker = FakeDocker(
_legacy_helper_spec(bootstrap.RETIRED_FAILED_MODERN_REVISION),
existing_child=_retired_failed_child(helper_source),
)
git = FakeProtectedGit(helper_source)
git.revision = bootstrap.RETIRED_FAILED_MODERN_REVISION
git.tree = bootstrap.RETIRED_FAILED_MODERN_TREE
legacy_health = _health(
revision=CONTROLLER_REVISION,
tree=CONTROLLER_TREE,
version="2.1.12",
artifact_sha256=bootstrap.LEGACY_CONTROLLER_ARTIFACT_SHA256,
)
result = _run_bootstrap(
docker,
git,
health_sequence=[legacy_health],
source_revision=git.revision,
source_tree=git.tree,
)
assert result["status"] == "failed"
assert result["exitCode"] == 1
assert docker.removed == []
assert docker.created == []
def test_completed_modern_child_with_wrong_artifact_is_not_relaunched():
helper_source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_text(
encoding="utf-8"
@@ -23,6 +23,18 @@ spec.loader.exec_module(module)
REVISION = "a" * 40
IMAGE_ID = "sha256:" + "b" * 64
ORIGINAL_COMPOSE = b"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: immutable-controller\n"
HISTORICAL_CONFIG_ROOT_RELEASE_FILES = (
"controller_paths.py", "mcp_hub_release_store.py", "hub_permission_preparation.py",
"deploy.py", "managed_appdata_paths.py", "prepare_managed_appdata_root.py", "runner_fleet.py",
"runner_capacity_controller.py", "runner_capacity_policy.py", "runner_fleet_upgrade_v2.py",
"runner_profiles.py", "runner_admission_policy.py", "runner_admission_controller.py",
"runner_resource_envelope.py", "runner_fleet_specialize.py", "runner_statistics_binding.py",
"status_server.py", "repopulse_adoption_store.py", "repopulse_registry_restore.py",
"repopulse_adoption_executor.py", "repopulse_adoption_effects.py",
"repopulse_adoption_storage_effects.py", "repopulse_target_attestation.py",
"stage_repopulse_target_attestation.py", "repopulse_adoption_cli.py",
"stage_repopulse_maintenance_environment.py", "run_repopulse_adoption.py",
)
def volume():
@@ -58,6 +70,214 @@ def test_only_the_exact_local_volume_is_accepted():
module.validate_volume(volume())
def backup_volume():
return {
"Name": "itworx-forge-master-key-backup",
"Driver": "local",
"Scope": "local",
"Options": None,
"Labels": {
"itworx.managed-by": "deployment-controller",
"itworx.purpose": "forge-master-key-backup",
},
"Mountpoint": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data",
}
def test_backup_volume_identity_is_exact_and_never_repairs_existing_metadata():
module.validate_backup_volume(backup_volume())
bad = backup_volume()
bad["Labels"]["itworx.purpose"] = "foreign"
with pytest.raises(module.VolumeTransitionError):
module.validate_backup_volume(bad)
def test_promotion_preparer_creates_only_the_fixed_backup_volume_if_absent(monkeypatch, tmp_path):
commands = []
present = False
def run(arguments, **_kwargs):
nonlocal present
commands.append(arguments)
if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]:
return b'"/var/lib/docker"'
if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]:
return b"" if not present else b"itworx-forge-master-key-backup\n"
if arguments[:3] == ["docker", "volume", "create"]:
present = True
return b"itworx-forge-master-key-backup\n"
if arguments == ["docker", "volume", "inspect", "itworx-forge-master-key-backup"]:
return json.dumps([backup_volume()]).encode()
raise AssertionError(arguments)
directory_calls = []
monkeypatch.setattr(module, "_run", run)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
def directory(path, anchor):
directory_calls.append((path, anchor))
return 23
monkeypatch.setattr(module, "_directory", directory)
module.ensure_backup_volume(create=True)
create = [arguments for arguments in commands if arguments[:3] == ["docker", "volume", "create"]]
assert create == [[
"docker", "volume", "create", "--driver", "local",
"--label", "itworx.managed-by=deployment-controller",
"--label", "itworx.purpose=forge-master-key-backup",
"itworx-forge-master-key-backup",
]]
assert directory_calls == [
(module.DOCKER_ROOT / "volumes", module.DOCKER_ROOT),
(module.BACKUP_MOUNTPOINT, module.DOCKER_ROOT),
]
def test_existing_backup_volume_is_validated_without_relabel_or_recreation(monkeypatch, tmp_path):
commands = []
def run(arguments, **_kwargs):
commands.append(arguments)
if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]:
return b'"/var/lib/docker"'
if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]:
return b"itworx-forge-master-key-backup\n"
if arguments == ["docker", "volume", "inspect", "itworx-forge-master-key-backup"]:
item = backup_volume()
item["CreatedAt"] = "2000-01-01T00:00:00Z"
return json.dumps([item]).encode()
raise AssertionError(arguments)
monkeypatch.setattr(module, "_run", run)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(
module, "_directory",
lambda _path, _anchor: 23,
)
module.ensure_backup_volume(create=False)
assert not any(arguments[:3] == ["docker", "volume", "create"] for arguments in commands)
def test_missing_backup_volume_is_not_created_outside_promotion(monkeypatch, tmp_path):
commands = []
def run(arguments, **_kwargs):
commands.append(arguments)
if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]:
return b'"/var/lib/docker"'
if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]:
return b""
raise AssertionError(arguments)
monkeypatch.setattr(module, "_run", run)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
monkeypatch.setattr(
module, "_directory",
lambda _path, _anchor: 23,
)
with pytest.raises(module.VolumeTransitionError, match="backup_volume_missing"):
module.ensure_backup_volume(create=False)
assert not any(arguments[:3] == ["docker", "volume", "create"] for arguments in commands)
def _prepare_promotion_doubles(monkeypatch, tmp_path, *, config_valid=True):
events = []
controller_id = "c" * 64
current = {
"Id": controller_id,
"Mounts": [{
"Destination": module.EXECUTABLE_TARGET,
"Source": module.CONTROLLER_DIR.as_posix(),
}],
}
previous_identity = {
"sourceRevision": "b" * 40,
"sourceTree": "c" * 40,
"controllerVersion": "2.1.0",
}
config_tree = ({}, {"": 0o755})
monkeypatch.setattr(module, "_validate_private_stage", lambda _stage: events.append("stage_validated"))
monkeypatch.setattr(module, "ensure_volume", lambda **_kwargs: None)
monkeypatch.setattr(
module, "_controller_image_inspect",
lambda _image_id: (events.append("controller_inspected") or current),
)
monkeypatch.setattr(
module, "_snapshot_attested_controller",
lambda *_args, **_kwargs: ({}, {"": 0o755}, previous_identity),
)
monkeypatch.setattr(module, "_validate_previous_private_tls", lambda _stage: None)
monkeypatch.setattr(module, "_release_tree", lambda *_args, **_kwargs: ({}, {"": 0o755}))
monkeypatch.setattr(module, "_config_tree", lambda *_args, **_kwargs: config_tree)
monkeypatch.setattr(module, "_read_config_leaf", lambda: config_tree)
monkeypatch.setattr(module, "_prepare_merged_tree", lambda *_args, **_kwargs: ({}, {"": 0o755}))
def validate_staged(*_args, **_kwargs):
events.append("config_validated")
if not config_valid:
raise module.VolumeTransitionError("staged_config_invalid")
monkeypatch.setattr(module, "_validate_staged_configuration", validate_staged)
monkeypatch.setattr(module, "_install_release_leaf", lambda *_args, **_kwargs: None)
monkeypatch.setattr(module, "_read_root_compose", lambda: (ORIGINAL_COMPOSE, 0o644))
monkeypatch.setattr(module, "_mirror_snapshot_tar", lambda: b"mirror")
monkeypatch.setattr(
module, "rendered_model",
lambda *_args, **_kwargs: (events.append("compose_rendered") or b"rendered-compose"),
)
monkeypatch.setattr(
module, "_verify_compose_image",
lambda *_args, **_kwargs: events.append("compose_verified"),
)
monkeypatch.setattr(
module, "ensure_backup_volume",
lambda **_kwargs: events.append("backup_volume_prepared"),
raising=False,
)
monkeypatch.setattr(module, "_release_tar_bytes", lambda *_args, **_kwargs: b"tar")
backup = tmp_path / "promotion-backup"
backup.mkdir(mode=0o700)
backup_fd = 23
monkeypatch.setattr(module, "_create_backup", lambda: (backup, backup_fd))
monkeypatch.setattr(module, "_write_backup", lambda *_args, **_kwargs: None)
monkeypatch.setattr(module.os, "close", lambda _descriptor: None)
return events
def test_promotion_prepares_backup_volume_after_source_stage_controller_and_config_checks(
monkeypatch, tmp_path
):
events = _prepare_promotion_doubles(monkeypatch, tmp_path)
module.prepare_promotion(
tmp_path / "stage",
source_revision="d" * 40,
source_tree="e" * 40,
controller_version="2.2.0",
image_id=IMAGE_ID,
)
assert events.index("stage_validated") < events.index("config_validated")
assert events.index("config_validated") < events.index("backup_volume_prepared")
assert events.index("compose_rendered") < events.index("backup_volume_prepared")
assert events.count("compose_verified") == 2
assert events.index("compose_verified") < events.index("backup_volume_prepared")
def test_promotion_does_not_prepare_backup_volume_when_staged_configuration_fails(
monkeypatch, tmp_path
):
events = _prepare_promotion_doubles(monkeypatch, tmp_path, config_valid=False)
with pytest.raises(module.VolumeTransitionError, match="staged_config_invalid"):
module.prepare_promotion(
tmp_path / "stage",
source_revision="d" * 40,
source_tree="e" * 40,
controller_version="2.2.0",
image_id=IMAGE_ID,
)
assert "backup_volume_prepared" not in events
def writer_inventory(inherited_labels):
identifier = "c" * 64
spec = module._writer_spec(IMAGE_ID, REVISION, kind="config")
@@ -230,18 +450,28 @@ def test_compose_uses_protected_code_and_two_config_views(compose):
"target": module.TARGET,
"volume": {"nocopy": True},
} in volumes
assert {
"type": "volume",
"source": "itworx-forge-master-key-backup",
"target": "/forge-master-key-backup",
"volume": {"nocopy": True},
} in volumes
assert {
"type": "bind",
"source": module._expected_config_path().as_posix(),
"target": module.CONFIG_TARGET,
"read_only": True,
} in volumes
assert len(volumes) == 4
assert len(volumes) == 5
volumes.remove(next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET))
volumes.remove(next(item for item in volumes if item["target"] == module.TARGET))
volumes.remove(next(item for item in volumes if item["target"] == "/forge-master-key-backup"))
volumes.remove(next(item for item in volumes if item["target"] == module.CONFIG_TARGET))
assert rendered["volumes"] == {
module.VOLUME: {"name": module.VOLUME, "external": True}
module.VOLUME: {"name": module.VOLUME, "external": True},
"itworx-forge-master-key-backup": {
"name": "itworx-forge-master-key-backup", "external": True
},
}
assert rendered["services"]["runner"] == baseline["services"]["runner"]
assert service["environment"] == baseline["services"]["deploy-control"]["environment"]
@@ -257,6 +487,46 @@ def test_compose_uses_protected_code_and_two_config_views(compose):
assert calls[0][1] == ORIGINAL_COMPOSE
def test_compose_allows_only_the_fixed_managed_appdata_volume_mount(compose):
baseline, _calls = compose
mount = {
"type": "volume",
"source": module.MANAGED_APPDATA_VOLUME,
"target": module.MANAGED_APPDATA_MOUNTPOINT.as_posix(),
}
baseline["services"][module.SERVICE]["volumes"].append(mount)
baseline["volumes"] = {
module.MANAGED_APPDATA_VOLUME: {
"name": module.MANAGED_APPDATA_VOLUME,
"external": True,
}
}
rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID))
assert mount in rendered["services"][module.SERVICE]["volumes"]
assert rendered["volumes"][module.MANAGED_APPDATA_VOLUME] == {
"name": module.MANAGED_APPDATA_VOLUME,
"external": True,
}
def test_compose_rejects_a_managed_appdata_target_alias(compose):
baseline, _calls = compose
baseline["services"][module.SERVICE]["volumes"].append({
"type": "volume",
"source": module.MANAGED_APPDATA_VOLUME,
"target": "/var/lib/docker/volumes/foreign/_data",
})
baseline["volumes"] = {
module.MANAGED_APPDATA_VOLUME: {
"name": module.MANAGED_APPDATA_VOLUME,
"external": True,
}
}
with pytest.raises(module.VolumeTransitionError, match="managed_appdata"):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
def test_source_relative_controller_bind_uses_original_compose_directory(monkeypatch):
def run(arguments, *, data=None, **kwargs):
if arguments[:3] == ["docker", "image", "inspect"]:
@@ -274,7 +544,14 @@ def test_source_relative_controller_bind_uses_original_compose_directory(monkeyp
assert next(m["source"] for m in mounts if m["target"] == module.EXECUTABLE_TARGET) == module._mount_source_for_revision(REVISION)
@pytest.mark.parametrize("damage", ["target_collision", "foreign_volume", "graph_rw", "relative_bind", "env_file"])
@pytest.mark.parametrize(
"damage",
[
"target_collision", "foreign_volume", "graph_rw", "relative_bind", "env_file",
"backup_target_collision", "backup_volume_alias", "backup_target_child_alias",
"backup_definition_collision",
],
)
def test_compose_rejects_unsafe_or_unpinned_activation_inputs(compose, damage):
baseline, _ = compose
if damage == "target_collision":
@@ -289,12 +566,65 @@ def test_compose_rejects_unsafe_or_unpinned_activation_inputs(compose, damage):
baseline["services"]["deploy-control"]["volumes"][0]["read_only"] = False
elif damage == "relative_bind":
baseline["services"]["runner"]["volumes"][0]["source"] = "./unsafe"
else:
elif damage == "env_file":
baseline["services"]["deploy-control"]["env_file"] = [".env"]
elif damage == "backup_target_collision":
baseline["services"]["deploy-control"]["volumes"].append(
{"type": "bind", "source": "/foreign", "target": "/forge-master-key-backup"}
)
elif damage == "backup_volume_alias":
baseline["services"]["runner"]["volumes"].append(
{
"type": "volume", "source": "itworx-forge-master-key-backup",
"target": "/foreign-backup",
}
)
elif damage == "backup_target_child_alias":
baseline["services"]["runner"]["volumes"].append(
{"type": "bind", "source": "/foreign", "target": "/forge-master-key-backup/state"}
)
else:
baseline["volumes"] = {
"itworx-forge-master-key-backup": {"name": "foreign", "external": True}
}
with pytest.raises(module.VolumeTransitionError):
module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)
def test_pre_promotion_accepts_the_healthy_controller_without_the_new_mount(compose):
baseline, _calls = compose
reference = "registry.example/controller:stable"
baseline["services"][module.SERVICE]["image"] = reference
current = {
"Image": IMAGE_ID,
"Config": {
"Image": reference,
"Labels": {
"com.docker.compose.project": "gitea-deploy-runners",
"com.docker.compose.service": module.SERVICE,
},
},
"State": {"Health": {"Status": "healthy"}},
"Mounts": [
{
"Type": "volume", "Name": module.VOLUME,
"Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET,
"RW": True,
}
],
}
original = f"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: {reference}\n".encode()
rendered = json.loads(
module.rendered_model(original, REVISION, IMAGE_ID, current_container=current)
)
assert not any(mount.get("Destination") == "/forge-master-key-backup" for mount in current["Mounts"])
assert any(
mount.get("target") == "/forge-master-key-backup"
and mount.get("source") == "itworx-forge-master-key-backup"
for mount in rendered["services"][module.SERVICE]["volumes"]
)
def test_compose_tag_replacement_cannot_change_controller_startup_image(compose, monkeypatch):
_baseline, calls = compose
original_run = module._run
@@ -385,6 +715,184 @@ def test_config_snapshot_has_exact_allowlist_and_broker_token():
assert config_dirs == directories
def _forge_secret_entries():
return {
name: (f'fixture:{index}'.encode(), module.FORGE_SECRET_MODE)
for index, name in enumerate(sorted(module.FORGE_SECRET_OWNERS))
}
def test_complete_forge_secret_bundle_has_exact_export_modes_and_owners():
entries = {
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
} | _forge_secret_entries()
directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700}
config, config_dirs = module._config_tree(entries, directories)
archive_bytes = module._release_tar_bytes(REVISION, (config, config_dirs))
restored, restored_dirs = module._read_tar_tree(archive_bytes)
assert restored == config
assert restored_dirs == config_dirs
with tarfile.open(fileobj=io.BytesIO(archive_bytes), mode='r:') as archive:
for name, owner in module.FORGE_SECRET_OWNERS.items():
member = archive.getmember(name)
assert (member.uid, member.gid) == owner
assert member.mode == module.FORGE_SECRET_MODE
assert archive.getmember('broker-token').uid == 0
assert archive.getmember('broker-token').gid == 0
@pytest.mark.parametrize('damage', ['partial', 'unknown', 'wrong_mode', 'nested'])
def test_forge_secret_snapshot_requires_the_complete_exact_private_tree(damage):
entries = {
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
} | _forge_secret_entries()
directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700}
if damage == 'partial':
entries.pop(min(module.FORGE_SECRET_OWNERS))
elif damage == 'unknown':
entries[f'{module.FORGE_SECRET_DIRECTORY}/unexpected.secret'] = (b'x', 0o400)
elif damage == 'wrong_mode':
name = min(module.FORGE_SECRET_OWNERS)
entries[name] = (entries[name][0], 0o600)
else:
directories[f'{module.FORGE_SECRET_DIRECTORY}/nested'] = 0o700
with pytest.raises(module.VolumeTransitionError):
module._config_tree(entries, directories)
def test_forge_secret_tar_owner_drift_is_rejected():
output = io.BytesIO()
with tarfile.open(fileobj=output, mode='w', format=tarfile.USTAR_FORMAT) as archive:
member = tarfile.TarInfo(
f'{module.FORGE_SECRET_DIRECTORY}/runtime_database_url.secret'
)
member.size = 1
member.mode = module.FORGE_SECRET_MODE
member.uid = member.gid = 0
archive.addfile(member, io.BytesIO(b'x'))
with pytest.raises(module.VolumeTransitionError, match='entry_unsafe'):
module._read_tar_tree(output.getvalue())
def test_config_writer_uses_archive_mode_to_preserve_forge_leaf_owners(monkeypatch):
tree = (
{
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
} | _forge_secret_entries(),
{'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700},
)
writer_id = 'f' * 64
commands = []
copy_payloads = []
def run(arguments, *, data=None, **_kwargs):
commands.append(arguments)
if arguments == ['docker', 'create']:
return writer_id.encode()
if arguments[:2] == ['docker', 'wait']:
return b'0'
if arguments[:2] == ['docker', 'cp']:
copy_payloads.append(data)
return b''
monkeypatch.setattr(module, '_read_partial_config_leaf', lambda: None)
monkeypatch.setattr(module, 'ensure_volume', lambda **_kwargs: None)
monkeypatch.setattr(module, '_release_tar_bytes', lambda *_args, **_kwargs: b'archive')
monkeypatch.setattr(module, '_remove_stale_writer', lambda *_args, **_kwargs: None)
monkeypatch.setattr(module, '_writer_spec', lambda *_args, **_kwargs: ['docker', 'create'])
monkeypatch.setattr(module, '_inspect_writer', lambda *_args, **_kwargs: {})
monkeypatch.setattr(module, '_read_config_leaf', lambda: tree)
monkeypatch.setattr(module, '_run', run)
module._install_config_tree(tree, IMAGE_ID)
assert [command for command in commands if command[:2] == ['docker', 'cp']] == [[
'docker', 'cp', '-a', '-', f'{writer_id}:{module.TARGET}/{module.CONFIG_DIR}',
]]
assert copy_payloads == [b'archive']
def test_partial_forge_bundle_fails_during_promotion_preparation_before_stop(
monkeypatch, tmp_path
):
valid_config_tree = module._config_tree
events = _prepare_promotion_doubles(monkeypatch, tmp_path)
monkeypatch.setattr(module, '_config_tree', valid_config_tree)
partial = {
'projects.json': (b'{}', 0o600),
'broker-token': (b'fixture-token', 0o600),
'gitea-validation.json': (b'{}', 0o600),
'gitea-validation-token': (b'fixture-validation-token', 0o600),
min(module.FORGE_SECRET_OWNERS): (b'fixture', module.FORGE_SECRET_MODE),
}
directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700}
identity = {
'sourceRevision': 'b' * 40,
'sourceTree': 'c' * 40,
'controllerVersion': '2.1.0',
}
monkeypatch.setattr(
module, '_snapshot_attested_controller',
lambda *_args, **_kwargs: (partial, directories, identity),
)
with pytest.raises(module.VolumeTransitionError, match='forge_secret_bundle_incomplete'):
module.prepare_promotion(
tmp_path / 'stage',
source_revision='d' * 40,
source_tree='e' * 40,
controller_version='2.2.0',
image_id=IMAGE_ID,
)
assert 'backup_volume_prepared' not in events
@pytest.mark.skipif(os.name == 'nt' or not hasattr(os, 'geteuid') or os.geteuid() != 0,
reason='fixed Forge ownership roundtrip requires Linux root')
def test_forge_secret_filesystem_capture_and_private_stage_keep_exact_owner(tmp_path):
config_root = tmp_path / 'config'
config_root.mkdir(mode=0o700)
secret_root = config_root / module.FORGE_SECRET_DIRECTORY
secret_root.mkdir(mode=0o700)
for index, (relative, owner) in enumerate(sorted(module.FORGE_SECRET_OWNERS.items())):
path = config_root / relative
path.write_bytes(f'fixture:{index}'.encode())
os.chown(path, *owner)
path.chmod(module.FORGE_SECRET_MODE)
descriptor = os.open(config_root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
try:
entries, directories = module._read_tree_fd(descriptor, allow_forge_secrets=True)
finally:
os.close(descriptor)
config, config_dirs = module._config_tree(entries, directories, allow_partial=True)
payload = module._release_tar_bytes(REVISION, (config, config_dirs))
roundtrip, roundtrip_dirs = module._read_tar_tree(payload)
assert roundtrip == config
assert roundtrip_dirs == config_dirs
staged = tmp_path / 'private-stage'
module._write_private_tree(staged, config, config_dirs)
for relative, owner in module.FORGE_SECRET_OWNERS.items():
info = (staged / relative).stat(follow_symlinks=False)
assert (info.st_uid, info.st_gid) == owner
assert stat.S_IMODE(info.st_mode) == module.FORGE_SECRET_MODE
def test_unknown_config_file_is_rejected():
entries = {
"projects.json": (b"{}", 0o600),
@@ -655,6 +1163,154 @@ def test_installer_release_order_matches_renderer_contract():
assert module.RELEASE_FILES[:3] == (
"controller_paths.py", "mcp_hub_release_store.py", "hub_permission_preparation.py"
)
assert module.RELEASE_FILES[module.RELEASE_FILES.index("forge_first_install.py") + 1] == (
"forge_database_credentials.py"
)
helper_index = module.RELEASE_FILES.index("forge_database_credentials.py") + 1
assert module.RELEASE_FILES[helper_index : helper_index + 2] == (
"forge_backup_paths.py", "forge_secret_bundle.py"
)
assert module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER == tuple(
name for name in module.RELEASE_FILES
if name not in {"forge_backup_paths.py", "forge_secret_bundle.py"}
)
assert module.LEGACY_RELEASE_FILES_WITHOUT_ROLE_HELPER == tuple(
name for name in module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER
if name != "forge_database_credentials.py"
)
installer = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8")
module_list = installer.split("module_files=(", 1)[1].split(")", 1)[0].split()
module_helper_index = module_list.index("forge_database_credentials.py") + 1
assert module_list[module_helper_index : module_helper_index + 2] == [
"forge_backup_paths.py", "forge_secret_bundle.py"
]
source_list = installer.split("source_paths=(", 1)[1].split(")", 1)[0].split()
source_helper_index = source_list.index("ops/gitea-deploy-broker/forge_database_credentials.py") + 1
assert source_list[source_helper_index : source_helper_index + 2] == [
"ops/gitea-deploy-broker/forge_backup_paths.py",
"ops/gitea-deploy-broker/forge_secret_bundle.py",
]
def test_current_release_digest_binds_the_role_password_helper():
entries = {name: (f"source:{name}".encode(), 0o644) for name in module.RELEASE_FILES}
expected = module._artifact_digest(entries)
assert module._matching_release_files(entries, expected) == module.RELEASE_FILES
entries["forge_database_credentials.py"] = (b"tampered helper", 0o644)
with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"):
module._matching_release_files(entries, expected)
def test_previous_role_helper_release_digest_remains_recognized_and_bound():
files = module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER
entries = {name: (f"source:{name}".encode(), 0o644) for name in files}
expected = module._artifact_digest(entries, files)
assert module._matching_release_files(entries, expected) == files
entries["forge_database_credentials.py"] = (b"tampered role helper", 0o644)
with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"):
module._matching_release_files(entries, expected)
def test_historical_config_root_manifest_order_and_digest_remain_exact():
files = HISTORICAL_CONFIG_ROOT_RELEASE_FILES
assert module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT == files
entries = {name: (f"source:{name}".encode(), 0o644) for name in files}
expected = module._artifact_digest(entries, files)
assert module._matching_release_files(entries, expected) == files
entries["status_server.py"] = (b"tampered historical file", 0o644)
with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"):
module._matching_release_files(entries, expected)
def test_activated_controller_must_have_the_exact_backup_mount(monkeypatch):
mounts = [
{
"Type": "bind", "Source": module._expected_revision_path(REVISION).as_posix(),
"Destination": module.EXECUTABLE_TARGET, "RW": False,
},
{
"Type": "volume", "Name": module.VOLUME,
"Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True,
},
{
"Type": "bind", "Source": module._expected_config_path().as_posix(),
"Destination": module.CONFIG_TARGET, "RW": False,
},
{
"Type": "bind", "Source": module.DOCKER_ROOT.as_posix(),
"Destination": module.DOCKER_ROOT.as_posix(), "RW": False,
},
{
"Type": "volume", "Name": "itworx-forge-master-key-backup",
"Source": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data",
"Destination": "/forge-master-key-backup", "RW": True,
},
]
item = {"Mounts": mounts}
monkeypatch.setattr(module, "_controller_image_inspect", lambda _image_id: item)
monkeypatch.setattr(module, "_read_release_leaf", lambda _revision: ({}, {"": 0o755}))
monkeypatch.setattr(module, "_release_identity", lambda _entries: {"sourceRevision": REVISION})
monkeypatch.setattr(module, "_read_config_leaf", lambda: ({}, {"": 0o755}))
module._verify_active_mount(REVISION, IMAGE_ID)
mounts.pop()
with pytest.raises(module.VolumeTransitionError, match="backup_mount"):
module._verify_active_mount(REVISION, IMAGE_ID)
@pytest.mark.parametrize(
"damage",
["backup_readonly", "backup_name", "backup_source", "backup_alias", "docker_root_rw"],
)
def test_activated_controller_rejects_backup_or_docker_root_mount_drift(
monkeypatch, damage
):
mounts = [
{
"Type": "bind", "Source": module._expected_revision_path(REVISION).as_posix(),
"Destination": module.EXECUTABLE_TARGET, "RW": False,
},
{
"Type": "volume", "Name": module.VOLUME,
"Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True,
},
{
"Type": "bind", "Source": module._expected_config_path().as_posix(),
"Destination": module.CONFIG_TARGET, "RW": False,
},
{
"Type": "bind", "Source": module.DOCKER_ROOT.as_posix(),
"Destination": module.DOCKER_ROOT.as_posix(), "RW": False,
},
{
"Type": "volume", "Name": module.BACKUP_VOLUME,
"Source": module.BACKUP_MOUNTPOINT.as_posix(),
"Destination": module.BACKUP_TARGET, "RW": True,
},
]
if damage == "backup_readonly":
mounts[-1]["RW"] = False
elif damage == "backup_name":
mounts[-1]["Name"] = "foreign-volume"
elif damage == "backup_source":
mounts[-1]["Source"] = "/var/lib/docker/volumes/foreign/_data"
elif damage == "backup_alias":
mounts.append({
"Type": "bind", "Source": "/foreign", "Destination": f"{module.BACKUP_TARGET}/child",
"RW": True,
})
else:
mounts[-2]["RW"] = True
monkeypatch.setattr(module, "_controller_image_inspect", lambda _image_id: {"Mounts": mounts})
monkeypatch.setattr(module, "_read_release_leaf", lambda _revision: ({}, {"": 0o755}))
monkeypatch.setattr(module, "_release_identity", lambda _entries: {"sourceRevision": REVISION})
monkeypatch.setattr(module, "_read_config_leaf", lambda: ({}, {"": 0o755}))
with pytest.raises(module.VolumeTransitionError):
module._verify_active_mount(REVISION, IMAGE_ID)
@pytest.mark.skipif(os.name == "nt", reason="dirfd swap test requires POSIX openat")
@@ -943,6 +1599,10 @@ def test_capture_program_is_fixed_descriptor_based_and_compiles():
@pytest.mark.parametrize("legacy_config", [True, False])
def test_capture_archive_roundtrips_through_a_nonseekable_pipe(legacy_config):
source = module._controller_capture_source(config_root_in_code=legacy_config)
assert '__FORGE_' not in source
assert 'runtime_database_url.secret' in source
assert '/forge-master-key-backup' not in source
compile(source, 'controller-capture', 'exec')
statements = ast.parse(source).body
start = next(i for i, node in enumerate(statements)
if isinstance(node, ast.Assign)
@@ -958,6 +1618,7 @@ def test_capture_archive_roundtrips_through_a_nonseekable_pipe(legacy_config):
"import io,sys,tarfile\n"
"records={'deploy.py':(b'fixture source',0o644),'secrets/key':(b'fixture secret',0o600)}\n"
"dirs={'secrets':0o700}\n"
"forge_secret_owners={}\n"
)
result = subprocess.run([sys.executable, "-c", fixture + archive_program],
capture_output=True, timeout=10, check=False)
@@ -971,8 +1632,13 @@ def test_capture_archive_roundtrips_through_a_nonseekable_pipe(legacy_config):
assert all(member.uid == member.gid == 0 for member in archive.getmembers())
def test_prior_release_capture_must_match_protected_git_tree(monkeypatch):
files = module.LEGACY_RELEASE_FILES_2_1_5
@pytest.mark.parametrize("files", [
module.LEGACY_RELEASE_FILES_WITHOUT_ROLE_HELPER,
module.LEGACY_RELEASE_FILES_2_1_5,
module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT,
module.LEGACY_RELEASE_FILES_BEFORE_FORGE,
])
def test_prior_release_capture_must_match_protected_git_tree(monkeypatch, files):
entries = {name: (f"source:{name}".encode(), 0o644) for name in files}
source_revision = "32a6eece0e3e1276946abdc04196c819daf02e2a"
source_tree = "e0d7e80b79504b964d968e51e0d21c288cac8f0d"
@@ -31,6 +31,10 @@ def test_windows_wrapper_archives_every_file_required_by_root_installer():
)
assert vendor_sources == required_vendor_sources
assert "scripts/prepare_controller_hub_release_volume.py" in archived.group(1)
assert "ops/gitea-deploy-broker/forge_first_install.py" in required_paths
assert "ops/gitea-deploy-broker/forge_database_credentials.py" in required_paths
assert "ops/gitea-deploy-broker/forge_backup_paths.py" in required_paths
assert "ops/gitea-deploy-broker/forge_secret_bundle.py" in required_paths
assert "scripts/upgrade_deployment_controller.sh" in archived.group(1)
assert "scripts/vendor" in archived.group(1)
assert "scripts/vendor/pyyaml-LICENSE" in required.group(1)
@@ -39,6 +43,28 @@ def test_windows_wrapper_archives_every_file_required_by_root_installer():
assert '"${work}/vendor/yaml/${vendor_file}"' in installer
assert ('[[ -d "${script_dir}/vendor" && ! -L "${script_dir}/vendor" && '
'-d "${script_dir}/vendor/yaml" && ! -L "${script_dir}/vendor/yaml" ]]') in installer
assert "forge_first_install.py" in installer.split("module_files=(", 1)[1].split(")", 1)[0]
assert "forge_database_credentials.py" in installer.split("module_files=(", 1)[1].split(")", 1)[0]
assert "forge_backup_paths.py" in installer.split("module_files=(", 1)[1].split(")", 1)[0]
assert "forge_secret_bundle.py" in installer.split("module_files=(", 1)[1].split(")", 1)[0]
def test_forge_first_install_module_is_bound_into_controller_release_and_config():
renderer = (ROOT / "scripts/prepare_controller_artifact.py").read_text(encoding="utf-8")
preparer = (ROOT / "scripts/prepare_controller_hub_release_volume.py").read_text(encoding="utf-8")
assert '"forge_first_install.py"' in renderer.split("_ARTIFACT_FILES = (", 1)[1].split(")", 1)[0]
release_files = preparer.split("RELEASE_FILES = (", 1)[1].split(")", 1)[0]
legacy_role_release_files = preparer.split(
"LEGACY_RELEASE_FILES_WITH_ROLE_HELPER = (", 1
)[1].split(")", 1)[0]
assert "'forge_first_install.py'" in legacy_role_release_files
assert "'forge_database_credentials.py'" in legacy_role_release_files
assert "*LEGACY_RELEASE_FILES_WITH_ROLE_HELPER" in release_files
assert "'forge_backup_paths.py'" in release_files
assert "'forge_secret_bundle.py'" in release_files
assert "'forge-first-install.json'" in preparer.split("CONFIG_FILES = {", 1)[1].split("}", 1)[0]
assert "'forge-bootstrap-secrets'" in preparer.split("CONFIG_SECRET_DIRS = {", 1)[1].split("}", 1)[0]
def test_windows_wrapper_supplies_private_archive_and_exact_image_identity():
@@ -192,6 +192,8 @@ def test_upgrade_prepares_before_stop_and_reconciles_between_stop_and_activation
activate = 'python3 "${work}/${PREPARER_NAME}" activate --backup "$backup"'
assert '/srv/example/appdata/gitea-deploy-runners/deploy.lock' not in script
assert 'lock_path="${ROOT}/promotion.lock"' in script
assert 'lock_path="${VOLUME_ROOT}/promotion.lock"' not in script
assert script.index(prepare) < script.index(stop)
assert script.index(stop) < script.index(reconcile)
assert script.index(reconcile) < script.index(activate)
+6
View File
@@ -7001,6 +7001,12 @@ def test_controller_release_identity_is_content_bound(status_module, tmp_path, m
renderer = importlib.util.module_from_spec(renderer_spec)
renderer_spec.loader.exec_module(renderer)
assert renderer._ARTIFACT_FILES == status_module._CONTROLLER_ARTIFACT_FILES
forge_first_install_index = status_module._CONTROLLER_ARTIFACT_FILES.index(
"forge_first_install.py"
)
assert status_module._CONTROLLER_ARTIFACT_FILES[forge_first_install_index + 1] == (
"forge_database_credentials.py"
)
artifact_root = tmp_path / "controller"
artifact_root.mkdir()
for filename in status_module._CONTROLLER_ARTIFACT_FILES:
+339
View File
@@ -0,0 +1,339 @@
from __future__ import annotations
import importlib.util
import os
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
MODULE_PATH = ROOT / "ops/gitea-deploy-broker/forge_backup_paths.py"
if MODULE_PATH.is_file():
spec = importlib.util.spec_from_file_location("forge_backup_paths_test", MODULE_PATH)
assert spec is not None and spec.loader is not None
paths = importlib.util.module_from_spec(spec)
spec.loader.exec_module(paths)
else:
paths = None
def _paths():
if paths is None:
pytest.fail("fixed Forge backup path verifier has not been implemented", pytrace=False)
return paths
def volume_metadata():
return {
"Name": "itworx-forge-master-key-backup",
"Driver": "local",
"Scope": "local",
"Options": None,
"Labels": {
"itworx.managed-by": "deployment-controller",
"itworx.purpose": "forge-master-key-backup",
},
"Mountpoint": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data",
}
def controller_mounts():
return [
{
"Type": "volume",
"Name": "itworx-forge-master-key-backup",
"Driver": "local",
"Source": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data",
"Destination": "/forge-master-key-backup",
"RW": True,
},
{
"Type": "bind",
"Name": "",
"Source": "/var/lib/docker",
"Destination": "/var/lib/docker",
"RW": False,
},
{
"Type": "volume",
"Name": "itworx-managed-appdata",
"Driver": "local",
"Source": "/var/lib/docker/volumes/itworx-managed-appdata/_data",
"Destination": "/var/lib/docker/volumes/itworx-managed-appdata/_data",
"RW": True,
},
{
"Type": "volume",
"Name": "itworx-controller-hub-releases",
"Driver": "local",
"Source": "/var/lib/docker/volumes/itworx-controller-hub-releases/_data",
"Destination": "/controller-hub-releases",
"RW": True,
},
{
"Type": "bind",
"Name": "",
"Source": "/var/lib/docker/volumes/itworx-controller-hub-releases/_data/controller-config",
"Destination": "/controller-config",
"RW": False,
},
]
def mountinfo(*, backup_options="rw,nosuid,nodev", docker_options="ro,relatime"):
base = [
"35 25 8:1 / / rw,relatime - ext4 /dev/sda1 rw,relatime",
f"41 35 8:2 / /var/lib/docker {docker_options} - ext4 /dev/sda2 ro,relatime",
f"42 35 8:3 / /forge-master-key-backup {backup_options} - ext4 /dev/sda3 rw,relatime",
]
base.extend(
f"{50 + index} 41 8:{index + 4} / /var/lib/docker/containers/c{index}/mount{index} "
f"{'ro' if index % 2 else 'rw'},relatime - ext4 /dev/sdb{index} rw,relatime"
for index in range(35)
)
return "\n".join(base)
def test_exact_local_backup_volume_metadata_is_accepted():
module = _paths()
assert module.validate_volume_metadata(volume_metadata(), "/var/lib/docker") is None
@pytest.mark.parametrize(
("field", "value"),
[
("Name", "foreign"),
("Driver", "nfs"),
("Scope", "global"),
("Options", {"device": "/mnt/share"}),
("Options", []),
("Labels", {"itworx.managed-by": "deployment-controller"}),
("Labels", {
"itworx.managed-by": "deployment-controller",
"itworx.purpose": "managed-appdata",
}),
("Mountpoint", "/mnt/foreign"),
],
)
def test_backup_volume_metadata_drift_is_rejected(field, value):
module = _paths()
item = volume_metadata()
item[field] = value
with pytest.raises(module.UnsafeForgeBackupVolume):
module.validate_volume_metadata(item, "/var/lib/docker")
def test_backup_volume_metadata_requires_the_fixed_docker_root():
module = _paths()
with pytest.raises(module.UnsafeForgeBackupVolume):
module.validate_volume_metadata(volume_metadata(), "/mnt/docker")
def test_mount_contract_allows_only_the_fixed_rw_backup_and_ro_docker_root():
module = _paths()
assert module.validate_controller_mounts(
controller_mounts(),
controller_id_before="a" * 64,
controller_id_after="a" * 64,
) is None
def test_mount_contract_allows_managed_appdata_to_be_absent():
module = _paths()
mounts = controller_mounts()
mounts.pop(2)
assert module.validate_controller_mounts(
mounts,
controller_id_before="a" * 64,
controller_id_after="a" * 64,
) is None
def test_mount_contract_allows_only_the_exact_managed_appdata_nested_volume():
module = _paths()
mounts = controller_mounts()
module.validate_controller_mounts(
mounts,
controller_id_before="a" * 64,
controller_id_after="a" * 64,
)
mounts[-3]["Destination"] = "/var/lib/docker/volumes/foreign/_data"
with pytest.raises(module.UnsafeForgeBackupVolume):
module.validate_controller_mounts(
mounts,
controller_id_before="a" * 64,
controller_id_after="a" * 64,
)
@pytest.mark.parametrize(
"damage",
[
"controller_changed",
"backup_rw_disabled",
"backup_name_changed",
"backup_source_changed",
"backup_destination_changed",
"backup_driver_changed",
"duplicate_backup_mount",
"backup_source_alias",
"backup_target_child_alias",
"backup_target_parent_alias",
"docker_root_rw",
"docker_root_child_mount",
"managed_appdata_duplicate",
"managed_appdata_source_changed",
"managed_appdata_driver_changed",
"managed_appdata_target_alias",
"managed_appdata_name_alias",
"root_exposure",
],
)
def test_mount_contract_rejects_aliases_and_mount_drift(damage):
module = _paths()
mounts = controller_mounts()
before = after = "a" * 64
if damage == "controller_changed":
after = "b" * 64
elif damage == "backup_rw_disabled":
mounts[0]["RW"] = False
elif damage == "backup_name_changed":
mounts[0]["Name"] = "foreign"
elif damage == "backup_source_changed":
mounts[0]["Source"] = "/mnt/foreign"
elif damage == "backup_destination_changed":
mounts[0]["Destination"] = "/backup-copy"
elif damage == "backup_driver_changed":
mounts[0]["Driver"] = "nfs"
elif damage == "duplicate_backup_mount":
mounts.append(dict(mounts[0]))
elif damage == "backup_source_alias":
mounts.append({
"Type": "bind", "Source": volume_metadata()["Mountpoint"],
"Destination": "/backup-copy", "RW": False,
})
elif damage == "backup_target_child_alias":
mounts.append({
"Type": "bind", "Source": "/foreign", "Destination": "/forge-master-key-backup/state",
"RW": True,
})
elif damage == "backup_target_parent_alias":
mounts.append({
"Type": "bind", "Source": "/foreign", "Destination": "/",
"RW": True,
})
elif damage == "docker_root_rw":
mounts[1]["RW"] = True
elif damage == "docker_root_child_mount":
mounts.append({
"Type": "bind", "Source": "/var/lib/docker/volumes/other",
"Destination": "/var/lib/docker/volumes/other", "RW": True,
})
elif damage == "managed_appdata_duplicate":
mounts.append(dict(mounts[2]))
elif damage == "managed_appdata_source_changed":
mounts[2]["Source"] = "/var/lib/docker/volumes/foreign/_data"
elif damage == "managed_appdata_driver_changed":
mounts[2]["Driver"] = "nfs"
elif damage == "managed_appdata_target_alias":
mounts[2]["Destination"] = "/appdata-copy"
elif damage == "managed_appdata_name_alias":
mounts[2].update({
"Source": "/var/lib/docker/volumes/foreign/_data",
"Destination": "/appdata-copy",
})
else:
mounts.append({
"Type": "bind", "Source": "/", "Destination": "/", "RW": True,
})
with pytest.raises(module.UnsafeForgeBackupVolume):
module.validate_controller_mounts(
mounts, controller_id_before=before, controller_id_after=after
)
def test_proc_mountinfo_requires_the_exact_rw_backup_and_ro_docker_mounts():
module = _paths()
assert module._validate_mountinfo_contract(mountinfo()) is None
for unsafe in (
mountinfo(backup_options="ro,nosuid,nodev"),
mountinfo(docker_options="rw,relatime"),
mountinfo().replace("/forge-master-key-backup", "/backup-copy"),
mountinfo() + "\n43 35 8:4 / /forge-master-key-backup rw - ext4 /dev/sda4 rw",
):
with pytest.raises(module.UnsafeForgeBackupVolume):
module._validate_mountinfo_contract(unsafe)
@pytest.mark.skipif(os.name == "nt", reason="directory inode checks require POSIX descriptors")
def test_directory_identity_binds_backup_mount_to_the_fixed_docker_volume_source(tmp_path):
module = _paths()
source = tmp_path / "source"
mounted = tmp_path / "mounted"
source.mkdir()
mounted.mkdir()
source_fd = os.open(source, os.O_RDONLY)
mounted_fd = os.open(mounted, os.O_RDONLY)
same_fd = os.open(source, os.O_RDONLY)
try:
module._require_same_directory(source_fd, same_fd)
with pytest.raises(module.UnsafeForgeBackupVolume, match="mount_source_mismatch"):
module._require_same_directory(source_fd, mounted_fd)
finally:
os.close(source_fd)
os.close(mounted_fd)
os.close(same_fd)
@pytest.mark.skipif(
os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0,
reason="directory ownership checks require root on POSIX",
)
def test_fixed_directory_walk_rejects_symlinks_and_unsafe_modes(tmp_path):
module = _paths()
volume_root = tmp_path / "volume"
volume_root.mkdir(mode=0o755)
volume_root.chmod(0o755)
fd = module._open_directory_no_symlinks(volume_root, anchor=tmp_path)
os.close(fd)
linked = tmp_path / "linked"
linked.symlink_to(volume_root, target_is_directory=True)
with pytest.raises(module.UnsafeForgeBackupVolume):
module._open_directory_no_symlinks(linked, anchor=tmp_path)
volume_root.chmod(0o777)
with pytest.raises(module.UnsafeForgeBackupVolume):
module._open_directory_no_symlinks(volume_root, anchor=tmp_path)
@pytest.mark.skipif(
os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0,
reason="directory ACL checks require root on POSIX",
)
def test_fixed_directory_walk_rejects_access_acl(tmp_path, monkeypatch):
module = _paths()
volume_root = tmp_path / "volume"
volume_root.mkdir(mode=0o755)
volume_root.chmod(0o755)
monkeypatch.setattr(module.os, "listxattr", lambda _fd: ["system.posix_acl_access"])
with pytest.raises(module.UnsafeForgeBackupVolume):
module._open_directory_no_symlinks(volume_root, anchor=tmp_path)
def test_read_only_identity_check_fails_closed_without_creating_or_repairing_paths(tmp_path, monkeypatch):
module = _paths()
sentinel = tmp_path / "unchanged"
sentinel.write_bytes(b"keep")
bad = volume_metadata()
bad["Labels"]["itworx.purpose"] = "foreign-purpose"
monkeypatch.setattr(module, "_read_volume_snapshot", lambda: (bad, "/var/lib/docker"))
monkeypatch.setattr(
module,
"_open_fixed_backup_root",
lambda: pytest.fail("unsafe volume metadata must fail before opening the data root"),
)
with pytest.raises(module.UnsafeForgeBackupVolume):
module.validate_forge_backup_volume()
assert sentinel.read_bytes() == b"keep"
assert sorted(path.name for path in tmp_path.iterdir()) == ["unchanged"]
File diff suppressed because it is too large. Load diff
@@ -177,6 +177,11 @@ def test_forge_host_preflight_compares_exact_main_and_reports_fixed_runtime_chec
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()
)
@@ -217,13 +222,14 @@ def test_forge_host_preflight_compares_exact_main_and_reports_fixed_runtime_chec
"frontdoor_image_digest": "sha256:" + "c" * 64,
}
result = status_module._forge_bootstrap_runtime_checks(profile)
assert result["status"] == "preflight_unknown"
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
@@ -2,6 +2,7 @@ from __future__ import annotations
from sqlmodel import Session, SQLModel, create_engine
from backend.app.core.config import settings
from backend.app.db.models import DeploymentJob
from backend.app.schemas.enrollment import EnrollmentTrustConfigV1
from backend.app.schemas.forge_bootstrap import ForgeBootstrapPreflightV1
@@ -10,6 +11,9 @@ from backend.app.services.forge_bootstrap_preflight import (
ForgeBootstrapPreflightService,
)
_PRIVATE_OWNER = "itworx-forge-private"
_SOURCE_URL = "https://gitea.example.com/Jens/itworx-forge"
def _profile(**overrides):
value = {
@@ -58,6 +62,67 @@ def test_forge_profile_is_strict_and_cannot_select_runtime_inputs():
raise AssertionError(f"invalid {field} must be rejected")
def test_preflight_registry_attestation_binds_protected_owner_and_both_image_labels(monkeypatch):
from backend.app.services.forge_bootstrap_preflight import (
_checked_controller_result,
)
profile = _profile()
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["image_registry"] = {"status": "passed"}
registry = {
"owner": _PRIVATE_OWNER,
"ownerVisibility": "private",
"workspaceDigest": profile.workspace_image_digest,
"frontdoorDigest": profile.frontdoor_image_digest,
"workspacePullable": True,
"frontdoorPullable": True,
"workspaceSource": _SOURCE_URL,
"workspaceRevision": profile.revision,
"frontdoorSource": _SOURCE_URL,
"frontdoorRevision": profile.revision,
}
result = {
"apiVersion": "itworx.forge-bootstrap-preflight/v1",
"status": "preflight_unknown",
"revision": profile.revision,
"workspaceImageDigest": profile.workspace_image_digest,
"frontdoorImageDigest": profile.frontdoor_image_digest,
"checks": checks,
"evidence": {"attestations": {"image_registry": registry}},
}
monkeypatch.setitem(settings.apis, "FORGE_PRIVATE_PACKAGE_OWNER", _PRIVATE_OWNER)
accepted = _checked_controller_result(result, profile)
assert accepted is not None
assert accepted[1]["attestations"]["image_registry"] == registry
for protected_owner in (None, "Jens", "owner.with.dot", "a" * 40):
monkeypatch.setitem(settings.apis, "FORGE_PRIVATE_PACKAGE_OWNER", protected_owner)
assert _checked_controller_result(result, profile) is None
monkeypatch.setitem(settings.apis, "FORGE_PRIVATE_PACKAGE_OWNER", _PRIVATE_OWNER)
for field, invalid in (
("owner", "another-owner"),
("workspaceSource", "https://attacker.invalid/repo"),
("frontdoorRevision", "d" * 40),
("workspaceDigest", profile.frontdoor_image_digest),
("workspacePullable", 1),
("frontdoorPullable", 1),
("workspacePullable", "true"),
("frontdoorPullable", None),
):
bad = {**registry, field: invalid}
result["evidence"]["attestations"]["image_registry"] = bad
assert _checked_controller_result(result, profile) is None
class _Broker:
def __init__(self, result):
self.result = result
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,487 @@
from __future__ import annotations
import importlib.util
import json
import os
import sys
import threading
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
BROKER = ROOT / "ops" / "gitea-deploy-broker"
if str(BROKER) not in sys.path:
sys.path.insert(0, str(BROKER))
@pytest.fixture
def credentials_module():
spec = importlib.util.spec_from_file_location(
"forge_database_credentials_test",
BROKER / "forge_database_credentials.py",
)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def _secrets(module):
return {
module.RUNTIME_ROLE: "runtime-secret-SENTINEL-0123456789abcdef",
module.MIGRATOR_ROLE: "migrator-secret-SENTINEL-fedcba9876543210",
}
def _uri(role: str, password: str) -> bytes:
return f"postgresql://{role}:{password}@postgres:5432/forge".encode("ascii")
def _install_fake_docker(
module,
tmp_path: Path,
secrets: dict[str, str],
*,
behavior: str = "success",
):
marker = tmp_path / "fake-docker-state.txt"
script = tmp_path / "fake-docker"
secret_literals = json.dumps(secrets)
marker_literal = json.dumps(str(marker))
script.write_text(
f"#!{sys.executable}\n"
"import json, os, sys, termios, time\n"
f"expected = {secret_literals}\n"
f"marker = {marker_literal}\n"
f"behavior = {behavior!r}\n"
"args = sys.argv[1:]\n"
"try:\n"
" role_arg = args[args.index('-c') + 1]\n"
" role = role_arg.split(' ', 1)[1]\n"
"except Exception:\n"
" open(marker, 'a').write('bad-command\\n')\n"
" raise SystemExit(90)\n"
f"expected_args = ['exec', '-i', '-t', '--user', '70:70', '--env', 'LANG=C', '--env', 'LC_ALL=C', '--env', 'PGPASSFILE=/dev/null', '--env', 'PGSERVICEFILE=/dev/null', '--env', 'PGPASSWORD=', '--env', 'PGOPTIONS=', 'itworx-forge-postgres-1', '/bin/busybox', 'timeout', '-s', 'TERM', '-k', '{module.REMOTE_KILL_AFTER_SECONDS}', '{module.REMOTE_TIMEOUT_SECONDS}', 'psql', '-X', '-w', '-v', 'ON_ERROR_STOP=1', '-h', '/var/run/postgresql', '-p', '5432', '-U', 'forge_bootstrap', '-d', 'forge', '-c', role_arg]\n"
"if args != expected_args or role not in expected:\n"
" open(marker, 'a').write('bad-command\\n')\n"
" raise SystemExit(91)\n"
"secret = expected[role]\n"
"if any(secret in value for value in sys.argv + list(os.environ.values())):\n"
" open(marker, 'a').write('secret-in-command-or-env\\n')\n"
" raise SystemExit(92)\n"
"if termios.tcgetattr(0)[3] & (termios.ECHO | termios.ECHONL):\n"
" open(marker, 'a').write('echo-enabled\\n')\n"
" raise SystemExit(93)\n"
"if behavior == 'exit-before-prompt':\n"
" raise SystemExit(7)\n"
"if behavior == 'timeout-before-prompt':\n"
" open(marker + '.pid', 'w').write(str(os.getpid()))\n"
" time.sleep(30)\n"
" raise SystemExit(0)\n"
"if behavior == 'output-flood':\n"
" os.write(2, b'x' * 65536)\n"
" time.sleep(30)\n"
" raise SystemExit(0)\n"
"if behavior == 'prompts-then-exit':\n"
" os.write(2, f'Enter new password for user \\\"{role}\\\": Enter it again: '.encode())\n"
" raise SystemExit(0)\n"
"os.write(2, f'Enter new password for user \\\"{role}\\\": '.encode())\n"
"first = sys.stdin.readline().rstrip('\\r\\n')\n"
"if first != secret:\n"
" open(marker, 'a').write('first-mismatch\\n')\n"
" raise SystemExit(94)\n"
"if behavior == 'echo-received':\n"
" os.write(2, first.encode() + b'\\n')\n"
"if behavior == 'exit-after-first':\n"
" raise SystemExit(8)\n"
"os.write(2, b'Enter it again: ')\n"
"second = sys.stdin.readline().rstrip('\\r\\n')\n"
"if second != secret:\n"
" open(marker, 'a').write('second-mismatch\\n')\n"
" raise SystemExit(95)\n"
"open(marker, 'a').write(role + '=matched-noecho\\n')\n"
"raise SystemExit(0)\n",
encoding="utf-8",
)
script.chmod(0o700)
module._DOCKER_CLI = str(script)
module._CHILD_PATH = str(tmp_path)
return marker
def _install_uri_reader(monkeypatch, module, secrets):
uri_by_name = {
"runtime_database_url": _uri(module.RUNTIME_ROLE, secrets[module.RUNTIME_ROLE]),
"migrator_database_url": _uri(module.MIGRATOR_ROLE, secrets[module.MIGRATOR_ROLE]),
}
reads = []
def read(_root, name, role):
reads.append((name, role))
return uri_by_name.get(name)
monkeypatch.setattr(
module,
"read_database_uri",
read,
)
return reads
def _journal(initial):
states = dict(initial)
calls: list[tuple[str, str]] = []
def role_state(role):
return states[role]
def transition(name, allowed, destination):
def apply(role):
if states[role] not in allowed:
raise RuntimeError("compare-and-swap rejected")
states[role] = destination
calls.append((name, role))
return True
return apply
return states, calls, {
"role_state": role_state,
"persist_role_intent": transition("intent", {"pending", "retryable"}, "intent"),
"persist_role_retryable": transition("retryable", {"intent"}, "retryable"),
"persist_role_unknown": transition("unknown", {"intent"}, "unknown"),
"persist_role_complete": transition("complete", {"intent"}, "completed"),
}
def _run(module, secret_root, journal):
return module.set_forge_database_role_passwords(secret_root, **journal)
def test_fixed_roles_are_set_through_hidden_prompts_without_secret_output(
credentials_module, monkeypatch, tmp_path
):
module = credentials_module
secrets = _secrets(module)
reads = _install_uri_reader(monkeypatch, module, secrets)
marker = _install_fake_docker(module, tmp_path, secrets, behavior="echo-received")
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
)
result = _run(module, tmp_path, journal)
assert result == {
"status": "completed",
"completedRoles": [module.RUNTIME_ROLE, module.MIGRATOR_ROLE],
"activeRole": None,
}
assert states == {
module.RUNTIME_ROLE: "completed",
module.MIGRATOR_ROLE: "completed",
}
assert calls == [
("intent", module.RUNTIME_ROLE),
("complete", module.RUNTIME_ROLE),
("intent", module.MIGRATOR_ROLE),
("complete", module.MIGRATOR_ROLE),
]
assert reads == [
("runtime_database_url", module.RUNTIME_ROLE),
("migrator_database_url", module.MIGRATOR_ROLE),
]
assert marker.read_text(encoding="utf-8").splitlines() == [
f"{module.RUNTIME_ROLE}=matched-noecho",
f"{module.MIGRATOR_ROLE}=matched-noecho",
]
serialized = json.dumps(result)
assert all(secret not in serialized for secret in secrets.values())
def test_completed_role_is_skipped_and_uncertain_role_is_never_replayed(
credentials_module, monkeypatch, tmp_path
):
module = credentials_module
secrets = _secrets(module)
_install_uri_reader(monkeypatch, module, secrets)
marker = _install_fake_docker(module, tmp_path, secrets)
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "completed", module.MIGRATOR_ROLE: "intent"}
)
result = _run(module, tmp_path, journal)
assert result["status"] == "outcome_unknown"
assert result["completedRoles"] == [module.RUNTIME_ROLE]
assert result["activeRole"] == module.MIGRATOR_ROLE
assert calls == []
assert not marker.exists()
def test_migrator_cannot_be_journaled_complete_before_runtime_role(
credentials_module, tmp_path
):
module = credentials_module
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "completed"}
)
result = _run(module, tmp_path, journal)
assert result["status"] == "blocked"
assert result["reason"] == "role_journal_order_invalid"
assert result["completedRoles"] == []
assert calls == []
@pytest.mark.parametrize("behavior", ["exit-before-prompt", "timeout-before-prompt", "output-flood"])
def test_no_input_failures_are_bounded_reaped_and_retryable(
credentials_module, monkeypatch, tmp_path, behavior
):
module = credentials_module
secrets = _secrets(module)
_install_uri_reader(monkeypatch, module, secrets)
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
)
monkeypatch.setattr(module, "PROMPT_TIMEOUT_SECONDS", 0.15)
monkeypatch.setattr(module, "ROLE_TIMEOUT_SECONDS", 0.6)
monkeypatch.setattr(module, "REMOTE_TIMEOUT_SECONDS", 1)
monkeypatch.setattr(module, "REMOTE_KILL_AFTER_SECONDS", 1)
monkeypatch.setattr(module, "REMOTE_TERMINATION_MARGIN_SECONDS", 0.1)
monkeypatch.setattr(module, "MAX_TRANSCRIPT_BYTES", 128)
marker = _install_fake_docker(module, tmp_path, secrets, behavior=behavior)
started = __import__("time").monotonic()
result = _run(module, tmp_path, journal)
elapsed = __import__("time").monotonic() - started
assert result["status"] == "failed"
assert result["reason"] == "psql_failed_before_password_input"
assert result["activeRole"] == module.RUNTIME_ROLE
assert states[module.RUNTIME_ROLE] == "retryable"
assert states[module.MIGRATOR_ROLE] == "pending"
assert calls == [
("intent", module.RUNTIME_ROLE),
("retryable", module.RUNTIME_ROLE),
]
assert elapsed < 3
if behavior == "timeout-before-prompt":
assert elapsed >= 1.8
child_pid = int(Path(str(marker) + ".pid").read_text(encoding="utf-8"))
with pytest.raises(ProcessLookupError):
os.kill(child_pid, 0)
assert not marker.exists() or not any(
value in marker.read_text(encoding="utf-8")
for value in secrets.values()
)
def test_any_password_input_without_success_becomes_unknown(
credentials_module, monkeypatch, tmp_path
):
module = credentials_module
secrets = _secrets(module)
_install_uri_reader(monkeypatch, module, secrets)
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
)
monkeypatch.setattr(module, "PROMPT_TIMEOUT_SECONDS", 0.15)
monkeypatch.setattr(module, "REMOTE_TIMEOUT_SECONDS", 1)
monkeypatch.setattr(module, "REMOTE_KILL_AFTER_SECONDS", 1)
monkeypatch.setattr(module, "REMOTE_TERMINATION_MARGIN_SECONDS", 0.1)
marker = _install_fake_docker(module, tmp_path, secrets, behavior="exit-after-first")
result = _run(module, tmp_path, journal)
assert result["status"] == "outcome_unknown"
assert result["reason"] == "psql_result_uncertain"
assert states[module.RUNTIME_ROLE] == "unknown"
assert states[module.MIGRATOR_ROLE] == "pending"
assert calls == [
("intent", module.RUNTIME_ROLE),
("unknown", module.RUNTIME_ROLE),
]
assert not marker.exists() or not any(
value in marker.read_text(encoding="utf-8")
for value in secrets.values()
)
def test_invalid_fixed_uri_blocks_before_any_intent_or_process(
credentials_module, monkeypatch, tmp_path
):
module = credentials_module
monkeypatch.setattr(module, "read_database_uri", lambda *_args: None)
secrets = _secrets(module)
marker = _install_fake_docker(module, tmp_path, secrets)
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
)
result = _run(module, tmp_path, journal)
assert result["status"] == "blocked"
assert result["reason"] == "database_uri_unavailable"
assert states == {module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
assert calls == []
assert not marker.exists()
def test_concurrent_call_stops_on_durable_intent_without_starting_a_second_child(
credentials_module, monkeypatch, tmp_path
):
module = credentials_module
secrets = _secrets(module)
_install_uri_reader(monkeypatch, module, secrets)
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
)
entered = threading.Event()
release = threading.Event()
child_calls = []
def hold_child(role, _password):
child_calls.append(role)
entered.set()
assert release.wait(timeout=2)
return "failed", False
monkeypatch.setattr(module, "_run_password_command", hold_child)
first_result = []
thread = threading.Thread(
target=lambda: first_result.append(_run(module, tmp_path, journal)),
daemon=True,
)
thread.start()
assert entered.wait(timeout=1)
second_result = _run(module, tmp_path, journal)
release.set()
thread.join(timeout=2)
assert not thread.is_alive()
assert second_result["status"] == "outcome_unknown"
assert second_result["reason"] == "role_requires_login_reconciliation"
assert first_result[0]["status"] == "failed"
assert child_calls == [module.RUNTIME_ROLE]
assert states[module.RUNTIME_ROLE] == "retryable"
assert calls == [
("intent", module.RUNTIME_ROLE),
("retryable", module.RUNTIME_ROLE),
]
@pytest.mark.parametrize("claim_result", [False, None])
def test_failed_durable_intent_claim_never_starts_child(
credentials_module, monkeypatch, tmp_path, claim_result
):
module = credentials_module
secrets = _secrets(module)
_install_uri_reader(monkeypatch, module, secrets)
marker = _install_fake_docker(module, tmp_path, secrets)
states, calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
)
journal["persist_role_intent"] = lambda _role: claim_result
result = _run(module, tmp_path, journal)
assert result["status"] == "outcome_unknown"
assert result["reason"] == "role_intent_persistence_failed"
assert states[module.RUNTIME_ROLE] == "pending"
assert calls == []
assert not marker.exists()
def test_exited_child_prompt_output_is_drained_without_sending_password(
credentials_module, monkeypatch, tmp_path
):
module = credentials_module
secrets = _secrets(module)
_install_uri_reader(monkeypatch, module, secrets)
_install_fake_docker(module, tmp_path, secrets, behavior="prompts-then-exit")
states, _calls, journal = _journal(
{module.RUNTIME_ROLE: "pending", module.MIGRATOR_ROLE: "pending"}
)
child = []
writes = []
original_popen = module.subprocess.Popen
original_select = module.select.select
original_write_password = module._write_password_line
def capture_popen(*args, **kwargs):
process = original_popen(*args, **kwargs)
child.append(process)
return process
def select_after_exit(*args, **kwargs):
child[0].wait(timeout=2)
return original_select(*args, **kwargs)
def record_write(*args, **kwargs):
writes.append(True)
return original_write_password(*args, **kwargs)
monkeypatch.setattr(module.subprocess, "Popen", capture_popen)
monkeypatch.setattr(module.select, "select", select_after_exit)
monkeypatch.setattr(module, "_write_password_line", record_write)
result = _run(module, tmp_path, journal)
assert result["status"] != "completed"
assert writes == []
assert states[module.RUNTIME_ROLE] != "completed"
assert states[module.RUNTIME_ROLE] == "retryable"
def test_zero_length_pty_write_fails_closed(credentials_module, monkeypatch):
module = credentials_module
monkeypatch.setattr(module.os, "write", lambda *_args: 0)
monkeypatch.setattr(
module.select,
"select",
lambda _readers, writers, _errors, _timeout: ([], writers, []),
)
with pytest.raises(OSError):
module._write_password_line(123, bytearray(b"bounded-secret"))
def test_pty_write_backpressure_expires_within_fixed_budget(
credentials_module, monkeypatch
):
module = credentials_module
ticks = iter(range(0, 10))
monkeypatch.setattr(module.time, "monotonic", lambda: float(next(ticks)))
monkeypatch.setattr(
module.select,
"select",
lambda _readers, _writers, _errors, _timeout: ([], [], []),
)
monkeypatch.setattr(module, "PTY_WRITE_TIMEOUT_SECONDS", 2.0)
with pytest.raises(TimeoutError):
module._write_password_line(123, bytearray(b"bounded-secret"))
def test_uri_password_decoder_rejects_malformed_or_unbounded_values(credentials_module):
module = credentials_module
role = module.RUNTIME_ROLE
assert module._password_from_uri(_uri(role, "x" * 31), role) is None
assert module._password_from_uri(_uri(role, "x" * 257), role) is None
assert module._password_from_uri(
f"postgresql://{role}:bad%Q0value-with-more-than-32-characters@postgres:5432/forge".encode(),
role,
) is None
decoded = module._password_from_uri(
f"postgresql://{role}:encoded%2Dpassword%2D0123456789abcdef@postgres:5432/forge".encode(),
role,
)
assert decoded == bytearray(b"encoded-password-0123456789abcdef")
decoded[:] = b"\0" * len(decoded)
File diff suppressed because it is too large. Load diff
+300
View File
@@ -0,0 +1,300 @@
from __future__ import annotations
import os
from contextlib import contextmanager
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, SQLModel, create_engine, select
from backend.app.services import user_access
_REAL_CURRENT_USER = user_access.current_user
_PROVISION_PATH = "/api/apps/authentik/forge/oidc/provision"
@pytest.fixture
def forge_identity_client(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.core.config import settings
from backend.app.db import session as db_session
from backend.app.db.init_db import init_db
from backend.app.main import app
from backend.app.services import forge_oidc_provisioning
if os.name == "nt":
# The shared Windows runner temp ACL is deliberately not the production
# data-directory ACL; isolate route tests from that host configuration.
monkeypatch.setattr(
forge_oidc_provisioning,
"_windows_private_path",
lambda *_args, **_kwargs: None,
)
else:
monkeypatch.setattr(
forge_oidc_provisioning,
"_FORGE_OIDC_LOCK_ROOT",
tmp_path / "forge-oidc-runtime-lock",
)
engine = create_engine(
f"sqlite:///{tmp_path / 'forge-oidc-auth.db'}",
connect_args={"check_same_thread": False},
)
monkeypatch.setattr(db_session, "engine", engine)
monkeypatch.setenv("APPOPS_REQUIRE_OPERATOR_AUTH", "false")
monkeypatch.setattr(settings, "operator_proxy_auth_enabled", False)
monkeypatch.setattr(settings, "auth_admin_subjects", ["authentik:owner"], raising=False)
monkeypatch.setattr(settings, "auth_admin_emails", [], raising=False)
monkeypatch.setattr(settings, "data_dir", str(tmp_path))
monkeypatch.setattr(user_access, "current_user", _REAL_CURRENT_USER)
SQLModel.metadata.create_all(engine)
init_db()
client = TestClient(app, client=("198.18.10.20", 42000))
yield client
engine.dispose()
def _sign_in(client: TestClient, subject: str) -> None:
from backend.app.db import session as db_session
from backend.app.services.user_access import issue_session, provision_user
with Session(db_session.engine) as session:
user = provision_user(
session,
"authentik",
"https://auth.example/application/o/appops/",
{"sub": subject, "email": f"{subject}@example.com"},
)
cookie = issue_session(session, user)
client.cookies.set("appops_user_session", cookie)
def test_forge_oidc_route_requires_authenticated_operator_and_csrf(
forge_identity_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.services import setup_service
original_loader = setup_service.apply_stored_connector_settings
loader_calls = []
def tracked_loader(session, connector_ids=None):
loader_calls.append(connector_ids)
return original_loader(session, connector_ids)
monkeypatch.setattr(setup_service, "apply_stored_connector_settings", tracked_loader)
unauthenticated = forge_identity_client.post(_PROVISION_PATH, json={})
assert unauthenticated.status_code == 401
_sign_in(forge_identity_client, "owner")
missing_csrf = forge_identity_client.post(_PROVISION_PATH, json={})
assert missing_csrf.status_code == 403
assert loader_calls == []
def test_forge_oidc_route_rejects_read_only_user_and_nonempty_body(
forge_identity_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.services import setup_service
original_loader = setup_service.apply_stored_connector_settings
loader_calls = []
def tracked_loader(session, connector_ids=None):
loader_calls.append(connector_ids)
return original_loader(session, connector_ids)
monkeypatch.setattr(setup_service, "apply_stored_connector_settings", tracked_loader)
_sign_in(forge_identity_client, "reader")
denied = forge_identity_client.post(
_PROVISION_PATH,
json={},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert denied.status_code == 403
_sign_in(forge_identity_client, "owner")
invalid = forge_identity_client.post(
_PROVISION_PATH,
json={"client_id": "caller-selected"},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert invalid.status_code == 422
missing_body = forge_identity_client.post(
_PROVISION_PATH,
headers={"X-AppOps-Session-CSRF": "1"},
)
assert missing_body.status_code == 422
assert loader_calls == []
def test_forge_oidc_route_loads_stored_connector_settings_and_passes_actor(
forge_identity_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.core.config import settings
from backend.app.db import session as db_session
from backend.app.db.models import AppSetting
from backend.app.services import forge_oidc_provisioning, setup_service
from backend.app.services.secret_store import set_secret
_sign_in(forge_identity_client, "owner")
loader_calls = []
constructed = []
outcome_identities = []
events = []
protected_admin_token = "protected-authentik-admin-token-fixture"
monkeypatch.setattr(settings, "environment", "production")
monkeypatch.setattr(settings, "secret_key", "forge-oidc-api-secret-key-material-0123456789")
monkeypatch.setattr(settings, "secret_key_source", "env")
with Session(db_session.engine) as session:
session.add(
AppSetting(
key="connector:authentik:config",
value_json={"url": "https://auth.example", "verifyTls": True},
is_secret=False,
)
)
set_secret(session, "AUTHENTIK_TOKEN", protected_admin_token)
session.commit()
original_loader = setup_service.apply_stored_connector_settings
original_lock = forge_oidc_provisioning._forge_oidc_process_lock
original_store_status = forge_oidc_provisioning.secret_storage_status
@contextmanager
def tracked_lock():
with original_lock():
events.append("lock-enter")
yield
events.append("lock-exit")
def tracked_store_status():
events.append("store-check")
return original_store_status()
def tracked_loader(session, connector_ids=None):
events.append("connector-loader")
loader_calls.append(connector_ids)
assert connector_ids == {"authentik"}
return original_loader(session, connector_ids)
class FakeAuthentikConnector:
def __init__(self):
constructed.append(
(
settings.apis.get("AUTHENTIK_URL"),
settings.apis.get("AUTHENTIK_VERIFY_TLS"),
settings.apis.get("AUTHENTIK_TOKEN"),
)
)
def ensure_forge_oidc_prerequisites(self, *, expected_client_secret):
assert expected_client_secret is None
return {"success": False, "status": "failed"}
original_outcome = forge_oidc_provisioning._connector_outcome
def tracked_outcome(session, *, identity, outcome, secret_created):
outcome_identities.append(identity)
return original_outcome(
session,
identity=identity,
outcome=outcome,
secret_created=secret_created,
)
monkeypatch.setattr(setup_service, "apply_stored_connector_settings", tracked_loader)
monkeypatch.setattr(forge_oidc_provisioning, "_forge_oidc_process_lock", tracked_lock)
monkeypatch.setattr(forge_oidc_provisioning, "secret_storage_status", tracked_store_status)
monkeypatch.setattr(forge_oidc_provisioning, "AuthentikConnector", FakeAuthentikConnector)
monkeypatch.setattr(forge_oidc_provisioning, "_connector_outcome", tracked_outcome)
response = forge_identity_client.post(
_PROVISION_PATH,
json={},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert response.status_code == 200
assert response.json()["status"] == "failed"
assert response.json()["message"] == "forge_oidc_provisioning_failed"
assert constructed == [("https://auth.example", True, protected_admin_token)]
assert loader_calls == [{"authentik"}]
assert events == ["lock-enter", "store-check", "connector-loader", "lock-exit"]
assert outcome_identities == ["authentik:owner"]
assert protected_admin_token not in response.text
assert "client_secret" not in response.json()
def test_forge_oidc_production_store_not_ready_does_not_load_connector(
forge_identity_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.core.config import settings
from backend.app.services import setup_service
_sign_in(forge_identity_client, "owner")
monkeypatch.setattr(settings, "environment", "production")
monkeypatch.setattr(settings, "secret_key", "")
monkeypatch.setattr(settings, "secret_key_source", "missing")
loader_calls = []
original_loader = setup_service.apply_stored_connector_settings
def tracked_loader(session, connector_ids=None):
loader_calls.append(connector_ids)
return original_loader(session, connector_ids)
monkeypatch.setattr(setup_service, "apply_stored_connector_settings", tracked_loader)
response = forge_identity_client.post(
_PROVISION_PATH,
json={},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert response.status_code == 200
assert response.json()["status"] == "blocked"
assert response.json()["message"] == "forge_oidc_secret_store_not_production_ready"
assert loader_calls == []
def test_forge_oidc_route_returns_blocked_when_exact_policy_is_disabled(
forge_identity_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.db import session as db_session
from backend.app.db.models import Policy
from backend.app.services import setup_service
loader_calls = []
original_loader = setup_service.apply_stored_connector_settings
def tracked_loader(session, connector_ids=None):
loader_calls.append(connector_ids)
return original_loader(session, connector_ids)
monkeypatch.setattr(setup_service, "apply_stored_connector_settings", tracked_loader)
_sign_in(forge_identity_client, "owner")
with Session(db_session.engine) as session:
policy = session.exec(
select(Policy).where(
(Policy.action == "forge.oidc.provision")
& (Policy.target == "itworx-forge")
)
).one()
policy.manual = "blocked"
session.add(policy)
session.commit()
response = forge_identity_client.post(
_PROVISION_PATH,
json={},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert response.status_code == 200
assert response.json()["status"] == "blocked"
assert response.json()["message"] == "forge_oidc_exact_target_policy_required"
assert loader_calls == []
File diff suppressed because it is too large. Load diff
+188
View File
@@ -0,0 +1,188 @@
from __future__ import annotations
import os
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, SQLModel, create_engine
from backend.app.services import user_access
_REAL_CURRENT_USER = user_access.current_user
_PROVISION_PATH = "/api/apps/authentik/forge/controller-reader/provision"
@pytest.fixture
def forge_reader_operator_client(tmp_path, monkeypatch: pytest.MonkeyPatch):
from backend.app.core.config import settings
from backend.app.db import session as db_session
from backend.app.db.init_db import init_db
from backend.app.main import app
if os.name == "nt":
from backend.app.services import forge_oidc_provisioning
monkeypatch.setattr(
forge_oidc_provisioning,
"_windows_private_path",
lambda *_args, **_kwargs: None,
)
else:
from backend.app.services import forge_oidc_provisioning
monkeypatch.setattr(
forge_oidc_provisioning,
"_FORGE_OIDC_LOCK_ROOT",
tmp_path / "forge-reader-api-lock",
)
engine = create_engine(
f"sqlite:///{tmp_path / 'forge-reader-operator.db'}",
connect_args={"check_same_thread": False},
)
monkeypatch.setattr(db_session, "engine", engine)
monkeypatch.setenv("APPOPS_REQUIRE_OPERATOR_AUTH", "false")
monkeypatch.setattr(settings, "operator_proxy_auth_enabled", False)
monkeypatch.setattr(settings, "auth_admin_subjects", ["authentik:owner"], raising=False)
monkeypatch.setattr(settings, "auth_admin_emails", [], raising=False)
monkeypatch.setattr(settings, "data_dir", str(tmp_path))
monkeypatch.setattr(user_access, "current_user", _REAL_CURRENT_USER)
SQLModel.metadata.create_all(engine)
init_db()
client = TestClient(app, client=("198.18.10.20", 42000))
yield client
engine.dispose()
def _spy_actual_connector_loader(monkeypatch: pytest.MonkeyPatch):
from backend.app.services import setup_service
original = setup_service.apply_stored_connector_settings
calls = []
def tracked(session, connector_ids=None):
calls.append(connector_ids)
return original(session, connector_ids)
monkeypatch.setattr(setup_service, "apply_stored_connector_settings", tracked)
return calls
def _sign_in(client: TestClient, subject: str) -> None:
from backend.app.db import session as db_session
from backend.app.services.user_access import issue_session, provision_user
with Session(db_session.engine) as session:
user = provision_user(
session,
"authentik",
"https://auth.example/application/o/appops/",
{"sub": subject, "email": f"{subject}@example.com"},
)
cookie = issue_session(session, user)
client.cookies.set("appops_user_session", cookie)
def test_forge_reader_route_requires_authenticated_operator_and_csrf(
forge_reader_operator_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
loader_calls = _spy_actual_connector_loader(monkeypatch)
unauthenticated = forge_reader_operator_client.post(_PROVISION_PATH, json={})
assert unauthenticated.status_code == 401
_sign_in(forge_reader_operator_client, "owner")
missing_csrf = forge_reader_operator_client.post(_PROVISION_PATH, json={})
assert missing_csrf.status_code == 403
assert loader_calls == []
def test_forge_reader_route_rejects_read_only_user_and_caller_selectors(
forge_reader_operator_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
loader_calls = _spy_actual_connector_loader(monkeypatch)
_sign_in(forge_reader_operator_client, "reader")
denied = forge_reader_operator_client.post(
_PROVISION_PATH,
json={},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert denied.status_code == 403
_sign_in(forge_reader_operator_client, "owner")
invalid = forge_reader_operator_client.post(
_PROVISION_PATH,
json={"token": "caller-selected"},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert invalid.status_code == 422
assert loader_calls == []
def test_forge_reader_default_denied_policy_does_not_load_connector_or_mutate(
forge_reader_operator_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.db import session as db_session
from backend.app.db.models import Policy
loader_calls = _spy_actual_connector_loader(monkeypatch)
_sign_in(forge_reader_operator_client, "owner")
with Session(db_session.engine) as session:
policy = session.get(Policy, "policy-forge-oidc-provision")
policy.manual = "blocked"
session.add(policy)
session.commit()
response = forge_reader_operator_client.post(
_PROVISION_PATH,
json={},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert response.status_code == 200
assert response.json()["status"] == "blocked"
assert response.json()["message"] == "forge_reader_exact_target_policy_required"
assert loader_calls == []
def test_forge_reader_route_passes_operator_and_projects_only_safe_result(
forge_reader_operator_client: TestClient,
monkeypatch: pytest.MonkeyPatch,
):
from backend.app.api import routes_authentik
_sign_in(forge_reader_operator_client, "owner")
loader_calls = _spy_actual_connector_loader(monkeypatch)
calls = []
held_token = "reader-token-must-never-be-returned"
def provision(session, *, actor_identity):
calls.append((session, actor_identity))
return {
"success": True,
"status": "ready",
"message": "forge_reader_ready",
"ready": True,
"secretRef": "ITWORX_FORGE_AUTHENTIK_READ_TOKEN",
}
monkeypatch.setattr(routes_authentik, "provision_forge_controller_reader", provision)
response = forge_reader_operator_client.post(
_PROVISION_PATH,
json={},
headers={"X-AppOps-Session-CSRF": "1"},
)
assert response.status_code == 200
assert response.json() == {
"success": True,
"status": "ready",
"message": "forge_reader_ready",
"ready": True,
"secretRef": "ITWORX_FORGE_AUTHENTIK_READ_TOKEN",
}
assert calls and calls[0][1] == "authentik:owner"
assert loader_calls == []
assert held_token not in response.text
+747
View File
@@ -0,0 +1,747 @@
from __future__ import annotations
import importlib.util
import json
import os
import stat
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
BROKER = ROOT / "ops" / "gitea-deploy-broker"
if str(BROKER) not in sys.path:
sys.path.insert(0, str(BROKER))
@pytest.fixture
def bundle_module():
spec = importlib.util.spec_from_file_location(
"forge_secret_bundle_test",
BROKER / "forge_secret_bundle.py",
)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def _config(**overrides: str) -> bytes:
value = {
"schema": "itworx.forge-first-install-config/v4",
"issuer": "https://auth.example.test/application/o/forge/",
}
value.update(overrides)
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
def _initialize_test_roots(roots) -> None:
for path in dict.fromkeys(
(roots.config_root, roots.state_parent, roots.backup_root)
):
path.mkdir(mode=0o700)
path.chmod(0o700)
def test_validator_must_return_literal_true_before_any_filesystem_effect(
bundle_module, tmp_path
):
oidc = b"oidc-secret-sentinel-123456789"
authentik = b"authentik-token-sentinel-123456789"
config = _config()
roots = bundle_module._Roots.for_test(tmp_path)
for result in (False, None, 1):
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda _raw, result=result: result,
roots=roots,
)
assert list(tmp_path.iterdir()) == []
def raises_with_secret(_raw: bytes) -> bool:
raise RuntimeError("validation failed: " + oidc.decode("ascii"))
with pytest.raises(bundle_module.ForgeSecretBundleError) as raised:
bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=raises_with_secret,
roots=roots,
)
assert oidc.decode("ascii") not in str(raised.value)
assert list(tmp_path.iterdir()) == []
@pytest.mark.parametrize(
"config",
[
b'{"schema":"v4","schema":"v4"}',
b'{ "schema":"v4"}',
b'{"schema":NaN}',
b"[]",
b"\xff",
],
)
def test_rejects_duplicate_invalid_noncanonical_or_nonobject_config_without_effects(
bundle_module, tmp_path, config
):
roots = bundle_module._Roots.for_test(tmp_path)
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
config,
validate_config=lambda _raw: True,
roots=roots,
)
assert list(tmp_path.iterdir()) == []
def test_rejects_supplied_secret_embedded_in_config_before_validator_or_writes(
bundle_module, tmp_path
):
oidc = b"oidc-secret-sentinel-123456789"
roots = bundle_module._Roots.for_test(tmp_path)
called = False
def validate(_raw: bytes) -> bool:
nonlocal called
called = True
return True
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
oidc,
b"authentik-token-sentinel-123456789",
_config(note=oidc.decode("ascii")),
validate_config=validate,
roots=roots,
)
assert called is False
assert list(tmp_path.iterdir()) == []
def test_rejects_escaped_secret_value_without_calling_validator(
bundle_module, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
secret = b'oidc-"quoted"-secret-123456789'
encoded = _config(note=secret.decode("ascii"))
called = False
def validate(_raw: bytes) -> bool:
nonlocal called
called = True
return True
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
secret,
b"authentik-token-sentinel-123456789",
encoded,
validate_config=validate,
roots=roots,
)
assert called is False
assert list(tmp_path.iterdir()) == []
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_creates_fixed_files_and_backup_and_retry_only_verifies(
bundle_module, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
oidc = b"oidc-secret-sentinel-123456789"
authentik = b"authentik-token-sentinel-123456789"
config = _config()
def validate(raw):
return raw == config
result = bundle_module._prepare_with_roots(
oidc, authentik, config, validate_config=validate, roots=roots
)
assert result == {
"status": "bundle_ready",
"secretFilesReady": 7,
"masterKeyBackupVerified": True,
"applyPreflight": "not_evaluated",
}
assert oidc.decode("ascii") not in repr(result)
assert authentik.decode("ascii") not in repr(result)
assert sorted(path.name for path in roots.secrets_root.iterdir()) == sorted(
f"{name}.secret"
for name in (
"postgres_bootstrap_password",
"runtime_database_url",
"migrator_database_url",
"oidc_client_secret",
"session_secret",
"master_key",
"authentik_read_token",
)
)
assert (roots.secrets_root / "oidc_client_secret.secret").read_bytes() == oidc
assert (
roots.secrets_root / "authentik_read_token.secret"
).read_bytes() == authentik
master_key = (roots.secrets_root / "master_key.secret").read_bytes()
assert len(master_key) == 32
assert (roots.backup_root / "master_key.backup").read_bytes() == master_key
import urllib.parse
generated = {
(roots.secrets_root / "postgres_bootstrap_password.secret").read_bytes(),
urllib.parse.urlsplit(
(roots.secrets_root / "runtime_database_url.secret").read_text(
encoding="ascii"
)
).password.encode("ascii"),
urllib.parse.urlsplit(
(roots.secrets_root / "migrator_database_url.secret").read_text(
encoding="ascii"
)
).password.encode("ascii"),
(roots.secrets_root / "session_secret.secret").read_bytes(),
}
assert len(generated) == 4
assert all(len(value) >= 32 for value in generated)
from forge_first_install import FORGE_SECRETS
for name, (_logical, uid, gid, minimum, maximum) in FORGE_SECRETS.items():
path = roots.secrets_root / f"{name}.secret"
info = path.stat()
assert (info.st_uid, info.st_gid) == (uid, gid)
assert info.st_mode & 0o777 == 0o400
assert minimum <= info.st_size <= maximum
assert (roots.secrets_root.stat().st_mode & 0o777) == 0o700
assert (roots.config_path.stat().st_mode & 0o777) == 0o600
assert (roots.backup_root / "master_key.backup").stat().st_mode & 0o777 == 0o400
journal = roots.journal_path.read_text(encoding="utf-8")
assert "oidc-secret-sentinel-123456789" not in journal
assert "authentik-token-sentinel-123456789" not in journal
assert "sha256" not in journal.lower()
before = {
path.name: (path.stat().st_ino, path.read_bytes())
for path in roots.secrets_root.iterdir()
}
backup_identity = (roots.backup_root / "master_key.backup").stat().st_ino
config_identity = roots.config_path.stat().st_ino
original_write = bundle_module._write_all
def reject_write(_fd: int, _payload: bytes) -> None:
raise AssertionError("a completed retry must not write")
bundle_module._write_all = reject_write
retry = bundle_module._prepare_with_roots(
oidc, authentik, config, validate_config=validate, roots=roots
)
bundle_module._write_all = original_write
assert retry == result
assert before == {
path.name: (path.stat().st_ino, path.read_bytes())
for path in roots.secrets_root.iterdir()
}
assert (roots.backup_root / "master_key.backup").stat().st_ino == backup_identity
assert roots.config_path.stat().st_ino == config_identity
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_partial_retry_reuses_already_committed_password_and_recovers_backup(
bundle_module, monkeypatch, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
oidc = b"oidc-secret-sentinel-123456789"
authentik = b"authentik-token-sentinel-123456789"
config = _config()
original_write = bundle_module._write_all
failed = False
def fail_runtime_write(fd: int, payload: bytes) -> None:
nonlocal failed
if payload.startswith(b"postgresql://") and not failed:
failed = True
raise OSError("injected write failure")
original_write(fd, payload)
monkeypatch.setattr(bundle_module, "_write_all", fail_runtime_write)
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
bootstrap = roots.secrets_root / "postgres_bootstrap_password.secret"
original_bootstrap = bootstrap.read_bytes()
original_inode = bootstrap.stat().st_ino
monkeypatch.setattr(bundle_module, "_write_all", original_write)
result = bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
assert result["status"] == "bundle_ready"
assert bootstrap.read_bytes() == original_bootstrap
assert bootstrap.stat().st_ino == original_inode
master = (roots.secrets_root / "master_key.secret").read_bytes()
assert (roots.backup_root / "master_key.backup").read_bytes() == master
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_link_failure_after_durable_stage_keeps_and_reuses_original_secret(
bundle_module, monkeypatch, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
oidc = b"oidc-secret-sentinel-123456789"
authentik = b"authentik-token-sentinel-123456789"
config = _config()
original_link = os.link
failed = False
def fail_runtime_link(source, destination, **kwargs):
nonlocal failed
if destination == "runtime_database_url.secret" and not failed:
failed = True
raise OSError("injected link failure")
return original_link(source, destination, **kwargs)
monkeypatch.setattr(os, "link", fail_runtime_link)
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
journal = json.loads(roots.journal_path.read_bytes())
stage = journal["staging"]
assert stage["kind"] == "secret"
assert stage["name"] == "runtime_database_url"
staged_file = roots.secrets_root / stage["temporary"]
assert staged_file.is_file()
original_payload = staged_file.read_bytes()
assert original_payload.startswith(b"postgresql://forge_runtime:")
monkeypatch.setattr(os, "link", original_link)
result = bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
assert result["status"] == "bundle_ready"
runtime_password = (roots.secrets_root / "runtime_database_url.secret").read_bytes()
assert runtime_password == original_payload
assert not staged_file.exists()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_journal_directory_fsync_uncertainty_preserves_referenced_stage(
bundle_module, monkeypatch, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
oidc = b"oidc-secret-sentinel-123456789"
authentik = b"authentik-token-sentinel-123456789"
config = _config()
original_fsync = os.fsync
failed = False
def fail_stage_directory_fsync(fd: int) -> None:
nonlocal failed
if not failed and stat.S_ISDIR(os.fstat(fd).st_mode):
try:
journal = json.loads(roots.journal_path.read_bytes())
except (OSError, ValueError):
journal = {}
stage = journal.get("staging")
if stage is not None and stage.get("name") == "runtime_database_url":
failed = True
raise OSError("injected directory fsync uncertainty")
original_fsync(fd)
monkeypatch.setattr(os, "fsync", fail_stage_directory_fsync)
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
journal = json.loads(roots.journal_path.read_bytes())
stage = journal["staging"]
assert stage["name"] == "runtime_database_url"
staged_file = roots.secrets_root / stage["temporary"]
assert staged_file.is_file()
original_payload = staged_file.read_bytes()
monkeypatch.setattr(os, "fsync", original_fsync)
result = bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
assert result["status"] == "bundle_ready"
assert (
roots.secrets_root / "runtime_database_url.secret"
).read_bytes() == original_payload
assert not staged_file.exists()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_backup_link_failure_reuses_same_master_key_and_completes_backup(
bundle_module, monkeypatch, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
oidc = b"oidc-secret-sentinel-123456789"
authentik = b"authentik-token-sentinel-123456789"
config = _config()
original_link = os.link
failed = False
def fail_backup_link(source, destination, **kwargs):
nonlocal failed
if destination == "master_key.backup" and not failed:
failed = True
raise OSError("injected backup link failure")
return original_link(source, destination, **kwargs)
monkeypatch.setattr(os, "link", fail_backup_link)
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
journal = json.loads(roots.journal_path.read_bytes())
stage = journal["staging"]
assert stage["kind"] == "backup"
assert stage["name"] == "master_key.backup"
staged_file = roots.backup_root / stage["temporary"]
original_payload = staged_file.read_bytes()
original_master = (roots.secrets_root / "master_key.secret").read_bytes()
original_master_inode = (roots.secrets_root / "master_key.secret").stat().st_ino
assert original_payload == original_master
assert len(original_payload) == 32
monkeypatch.setattr(os, "link", original_link)
result = bundle_module._prepare_with_roots(
oidc,
authentik,
config,
validate_config=lambda raw: raw == config,
roots=roots,
)
assert result["status"] == "bundle_ready"
assert (roots.secrets_root / "master_key.secret").read_bytes() == original_master
assert (
roots.secrets_root / "master_key.secret"
).stat().st_ino == original_master_inode
assert (roots.backup_root / "master_key.backup").read_bytes() == original_payload
assert not staged_file.exists()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_existing_secret_residue_is_never_overwritten_or_adopted(
bundle_module, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
roots.secrets_root.mkdir(mode=0o700)
residue = roots.secrets_root / "session_secret.secret"
residue.write_bytes(b"foreign-residue-must-survive")
residue.chmod(0o400)
before = residue.read_bytes()
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
_config(),
validate_config=lambda _raw: True,
roots=roots,
)
assert residue.read_bytes() == before
assert not roots.journal_path.exists()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_unsafe_ancestor_and_symlinked_secret_root_fail_without_chmod(
bundle_module, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
roots.config_root.chmod(0o777)
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
_config(),
validate_config=lambda _raw: True,
roots=roots,
)
assert roots.config_root.stat().st_mode & 0o777 == 0o777
roots.config_root.chmod(0o700)
target = tmp_path / "foreign-secret-dir"
target.mkdir(mode=0o700)
roots.secrets_root.symlink_to(target, target_is_directory=True)
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
_config(),
validate_config=lambda _raw: True,
roots=roots,
)
assert list(target.iterdir()) == []
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_stale_journal_generation_is_rejected_by_compare_and_swap(
bundle_module, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
roots.state_root.mkdir(mode=0o700)
descriptor = os.open(roots.state_root, os.O_RDONLY | os.O_DIRECTORY)
try:
record = bundle_module._empty_journal()
bundle_module._write_journal(descriptor, record, expected_generation=None)
stale = dict(record)
record["status"] = "progress"
bundle_module._persist(descriptor, record)
before = roots.journal_path.read_bytes()
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._write_journal(descriptor, stale, expected_generation=0)
assert roots.journal_path.read_bytes() == before
finally:
os.close(descriptor)
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_backup_root_rejects_untracked_files_before_creating_journal(
bundle_module, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
foreign = roots.backup_root / "unrelated-backup-residue"
foreign.write_bytes(b"leave this untouched")
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
_config(),
validate_config=lambda _raw: True,
roots=roots,
)
assert foreign.read_bytes() == b"leave this untouched"
assert not roots.state_root.exists()
assert not roots.secrets_root.exists()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
@pytest.mark.parametrize("target", ["config", "backup"])
def test_existing_config_or_backup_files_are_never_overwritten(
bundle_module, tmp_path, target
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
path = (
roots.config_path
if target == "config"
else roots.backup_root / "master_key.backup"
)
path.write_bytes(b"foreign-config-or-backup")
path.chmod(0o600)
original = path.read_bytes()
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
_config(),
validate_config=lambda _raw: True,
roots=roots,
)
assert path.read_bytes() == original
assert not roots.state_root.exists()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_hardlinked_existing_secret_fails_closed_without_rekeying(
bundle_module, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
args = (
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
_config(),
)
bundle_module._prepare_with_roots(
*args,
validate_config=lambda _raw: True,
roots=roots,
)
master = roots.secrets_root / "master_key.secret"
original = master.read_bytes()
foreign_link = tmp_path / "foreign-master-link"
os.link(master, foreign_link)
journal_before = roots.journal_path.read_bytes()
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
*args,
validate_config=lambda _raw: True,
roots=roots,
)
assert master.read_bytes() == original
assert roots.journal_path.read_bytes() == journal_before
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_pinned_file_reader_rejects_symlink_swap_between_stat_and_open(
bundle_module, monkeypatch, tmp_path
):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
roots.secrets_root.mkdir(mode=0o700)
target = roots.secrets_root / "trusted-content"
target.write_bytes(b"trusted")
target.chmod(0o400)
race = roots.secrets_root / "race-target"
race.write_bytes(b"before-race")
race.chmod(0o400)
directory_fd = os.open(roots.secrets_root, os.O_RDONLY | os.O_DIRECTORY)
original_stat_at = bundle_module._stat_at
swapped = False
def swap_after_stat(fd: int, name: str):
nonlocal swapped
info = original_stat_at(fd, name)
if name == "race-target" and info is not None and not swapped:
swapped = True
os.unlink(name, dir_fd=fd)
os.symlink("trusted-content", name, dir_fd=fd)
return info
monkeypatch.setattr(bundle_module, "_stat_at", swap_after_stat)
try:
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._read_file_at(
directory_fd,
"race-target",
uid=0,
gid=0,
min_size=1,
max_size=32,
allowed_modes=frozenset({0o400}),
)
finally:
os.close(directory_fd)
assert swapped is True
assert race.is_symlink()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the production helper requires POSIX root ownership",
)
def test_changed_master_key_backup_is_not_silently_rewritten(bundle_module, tmp_path):
roots = bundle_module._Roots.for_test(tmp_path)
_initialize_test_roots(roots)
args = (
b"oidc-secret-sentinel-123456789",
b"authentik-token-sentinel-123456789",
_config(),
)
bundle_module._prepare_with_roots(
*args,
validate_config=lambda _raw: True,
roots=roots,
)
backup = roots.backup_root / "master_key.backup"
original = backup.read_bytes()
backup.write_bytes(bytes(byte ^ 0xFF for byte in original))
journal_before = roots.journal_path.read_bytes()
with pytest.raises(bundle_module.ForgeSecretBundleError):
bundle_module._prepare_with_roots(
*args,
validate_config=lambda _raw: True,
roots=roots,
)
assert backup.read_bytes() == bytes(byte ^ 0xFF for byte in original)
assert (roots.secrets_root / "master_key.secret").read_bytes() == original
assert roots.journal_path.read_bytes() == journal_before
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,863 @@
from __future__ import annotations
import base64
import hashlib
import http.client
import importlib.util
import json
import os
import subprocess
import sys
import threading
import time
import types
import uuid
from http.server import ThreadingHTTPServer
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
BROKER = ROOT / "ops" / "gitea-deploy-broker"
KEY = b"forge-delivery-test-hmac-key-32bytes"
OIDC_SECRET = b"oidc-client-secret-sentinel-1234567890"
READ_TOKEN = b"authentik-read-token-sentinel-1234567890"
ARTIFACT = {
"controllerVersion": "1.2.3",
"sourceRepository": "jens/unraid-appops-gateway",
"sourceRevision": "1" * 40,
"sourceTree": "2" * 40,
"artifactSha256": "3" * 64,
"builtAt": "2026-10-03T10:00:00Z",
}
PROFILE = {
"api_version": "itworx.forge-bootstrap-preflight/v1",
"revision": "a" * 40,
"workspace_image_digest": "sha256:" + "b" * 64,
"frontdoor_image_digest": "sha256:" + "c" * 64,
}
def _load_status_module():
try:
import fcntl # noqa: F401 - use the real Linux flock implementation in tests.
except ImportError: # pragma: no cover - Windows-only import shim.
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_secret_delivery_status_test", BROKER / "status_server.py"
)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
@pytest.fixture
def status_module():
return _load_status_module()
@pytest.fixture
def endpoint(status_module):
server = ThreadingHTTPServer(("127.0.0.1", 0), status_module.Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
yield server.server_address
finally:
server.shutdown()
server.server_close()
thread.join(timeout=2)
def _config(**overrides: str) -> bytes:
value = {
"schema": "itworx.forge-first-install-config/v4",
"issuer": "https://auth.example-host.com/application/o/itworx-forge/",
"audience": "itworx-forge",
"client_id": "itworx-forge",
"authentik_api_url": "https://auth.example-host.com",
"launch_url": "https://forge.example.com",
"authorization_flow": "default-provider-authorization-implicit-consent",
"invalidation_flow": "default-provider-invalidation-flow",
"authentik_openid_mapping_id": "01234567-89ab-4cde-8fab-0123456789ab",
"authentik_email_mapping_id": "21234567-89ab-4cde-8fab-0123456789ab",
"authentik_profile_mapping_id": "31234567-89ab-4cde-8fab-0123456789ab",
"authentik_provider_id": "1234567890",
"authentik_signing_key_id": "11234567-89ab-4cde-8fab-0123456789ab",
"private_package_owner": "itworx-forge-private",
}
value.update(overrides)
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
def _payload(status_module, *, config_bytes: bytes | None = None, **overrides):
config_bytes = config_bytes if config_bytes is not None else _config()
value = {
"apiVersion": status_module.FORGE_SECRET_DELIVERY_API_VERSION,
"target": status_module.FORGE_SECRET_DELIVERY_TARGET,
"profile": dict(PROFILE),
"configBase64": base64.b64encode(config_bytes).decode("ascii"),
"oidcClientSecretBase64": base64.b64encode(OIDC_SECRET).decode("ascii"),
"authentikReadTokenBase64": base64.b64encode(READ_TOKEN).decode("ascii"),
}
value.update(overrides)
return value
def _signed_headers(status_module, body: bytes, *, nonce: str | None = None):
timestamp = str(int(time.time()))
nonce = nonce or uuid.uuid4().hex
signature = status_module._request_signature(
KEY,
method="POST",
path=status_module.FORGE_SECRET_DELIVERY_PATH,
timestamp=timestamp,
nonce=nonce,
raw_body=body,
)
return {
"Content-Type": "application/json",
"X-ITWorx-Deployment-Timestamp": timestamp,
"X-ITWorx-Deployment-Nonce": nonce,
"X-ITWorx-Deployment-Signature": signature,
}
def _post(address, path: str, body: bytes, headers: dict[str, str]):
connection = http.client.HTTPConnection(*address, timeout=3)
try:
connection.request("POST", path, body=body, headers=headers)
response = connection.getresponse()
return response.status, response.read()
finally:
connection.close()
def _install_hmac_test(monkeypatch, status_module):
consumed: set[str] = set()
monkeypatch.setattr(status_module, "_forge_first_install_secret", lambda: KEY)
def consume(_secret, nonce):
if nonce in consumed:
return False
consumed.add(nonce)
return True
monkeypatch.setattr(status_module, "_consume_appops_nonce", consume)
return consumed
def _install_successful_prerequisites(monkeypatch, status_module, helper=None):
counters = {"artifact": 0, "store": 0, "backup": 0, "runtime": 0}
def artifact():
counters["artifact"] += 1
return dict(ARTIFACT)
def store():
counters["store"] += 1
return ("/var/lib/docker", "itworx-controller-hub-releases", (1, 2), (1, 2))
def backup():
counters["backup"] += 1
return True
def runtime(profile, *, _held_candidate):
counters["runtime"] += 1
assert profile == PROFILE
assert isinstance(_held_candidate, dict)
return {"status": "bundle_prerequisites_passed"}
monkeypatch.setattr(status_module, "_forge_first_install_requests_are_absent", lambda: True)
monkeypatch.setattr(status_module, "_forge_controller_artifact_identity", artifact)
monkeypatch.setattr(status_module, "_forge_release_store_identity", store)
monkeypatch.setattr(status_module, "_forge_fixed_backup_volume_is_ready", backup)
monkeypatch.setattr(status_module, "_forge_bootstrap_runtime_checks", runtime)
if helper is not None:
monkeypatch.setattr(status_module, "_prepare_forge_secret_bundle", helper)
return counters
def test_candidate_parser_requires_canonical_config_and_secret_free_strings(status_module):
parsed = status_module._forge_secret_delivery_candidate(_payload(status_module))
assert parsed is not None
profile, candidate = parsed
assert profile == PROFILE
assert candidate["config_bytes"] == _config()
assert candidate["oidc_client_secret"] == OIDC_SECRET
assert candidate["authentik_read_token"] == READ_TOKEN
pretty = json.dumps(json.loads(_config()), indent=2).encode()
assert status_module._forge_secret_delivery_candidate(
_payload(status_module, config_bytes=pretty)
) is None
secret_bearing_config = _config(authorization_flow=OIDC_SECRET.decode())
assert status_module._forge_secret_delivery_candidate(
_payload(status_module, config_bytes=secret_bearing_config)
) is None
noncanonical = _payload(status_module)
noncanonical["oidcClientSecretBase64"] += "\n"
assert status_module._forge_secret_delivery_candidate(noncanonical) is None
def test_invalid_authentication_has_no_remote_or_bundle_side_effects(
status_module, endpoint, monkeypatch
):
counters = {"runtime": 0, "bundle": 0}
consumed = _install_hmac_test(monkeypatch, status_module)
monkeypatch.setattr(
status_module,
"_forge_controller_artifact_identity",
lambda: pytest.fail("unauthenticated input reached local gate"),
)
monkeypatch.setattr(
status_module,
"_forge_release_store_identity",
lambda: pytest.fail("unauthenticated input reached volume probe"),
)
monkeypatch.setattr(
status_module,
"_forge_fixed_backup_volume_is_ready",
lambda: pytest.fail("unauthenticated input reached backup probe"),
)
monkeypatch.setattr(
status_module,
"_forge_bootstrap_runtime_checks",
lambda *_args, **_kwargs: counters.__setitem__("runtime", counters["runtime"] + 1),
)
monkeypatch.setattr(
status_module,
"_prepare_forge_secret_bundle",
lambda *_args, **_kwargs: counters.__setitem__("bundle", counters["bundle"] + 1),
)
body = json.dumps(_payload(status_module), separators=(",", ":")).encode()
headers = _signed_headers(status_module, body)
headers["X-ITWorx-Deployment-Signature"] = "0" * 64
status, response = _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
headers,
)
assert status == 403
assert response == b'{"status":"authentication_rejected"}\n'
assert counters == {"runtime": 0, "bundle": 0}
assert consumed == set()
def test_duplicate_json_key_is_rejected_before_auth_or_remote_checks(
status_module, endpoint, monkeypatch
):
consumed = _install_hmac_test(monkeypatch, status_module)
called = []
monkeypatch.setattr(
status_module,
"_forge_prepare_secret_delivery_bundle",
lambda *_args, **_kwargs: called.append(True),
)
body = b'{"apiVersion":"x","apiVersion":"y"}'
status, response = _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
{
"Content-Type": "application/json",
"X-ITWorx-Deployment-Timestamp": str(int(time.time())),
"X-ITWorx-Deployment-Nonce": uuid.uuid4().hex,
"X-ITWorx-Deployment-Signature": "0" * 64,
},
)
assert status == 400
assert response == b'{"status":"invalid_json"}\n'
assert called == []
assert consumed == set()
@pytest.mark.parametrize(
("body", "content_type", "expected_status"),
[
(b"{}", "application/json; charset=utf-8", 400),
(b"x" * 16_385, "application/json", 413),
],
)
def test_body_limits_and_content_type_fail_before_auth(
status_module, endpoint, monkeypatch, body, content_type, expected_status
):
consumed = _install_hmac_test(monkeypatch, status_module)
monkeypatch.setattr(
status_module,
"_forge_controller_artifact_identity",
lambda: pytest.fail("invalid HTTP framing reached local gate"),
)
headers = {
"Content-Type": content_type,
"X-ITWorx-Deployment-Timestamp": str(int(time.time())),
"X-ITWorx-Deployment-Nonce": uuid.uuid4().hex,
"X-ITWorx-Deployment-Signature": "0" * 64,
}
status, _response = _post(
endpoint, status_module.FORGE_SECRET_DELIVERY_PATH, body, headers
)
assert status == expected_status
assert consumed == set()
@pytest.mark.parametrize("failed_gate", ["artifact", "store", "backup", "runtime"])
def test_every_fixed_prerequisite_gate_precedes_bundle_writer(
status_module, endpoint, monkeypatch, failed_gate
):
_install_hmac_test(monkeypatch, status_module)
calls = {name: 0 for name in ("artifact", "store", "backup", "runtime", "helper")}
def artifact():
calls["artifact"] += 1
return None if failed_gate == "artifact" else dict(ARTIFACT)
def store():
calls["store"] += 1
return None if failed_gate == "store" else ("fixed", "volume", (1, 2), (1, 2))
def backup():
calls["backup"] += 1
return failed_gate != "backup"
def runtime(profile, *, _held_candidate):
calls["runtime"] += 1
return {
"status": "bundle_prerequisites_blocked"
if failed_gate == "runtime"
else "bundle_prerequisites_passed"
}
def helper(*_args, **_kwargs):
calls["helper"] += 1
return {
"status": "bundle_ready",
"secretFilesReady": 7,
"masterKeyBackupVerified": True,
"applyPreflight": "not_evaluated",
}
monkeypatch.setattr(status_module, "_forge_first_install_requests_are_absent", lambda: True)
monkeypatch.setattr(status_module, "_forge_controller_artifact_identity", artifact)
monkeypatch.setattr(status_module, "_forge_release_store_identity", store)
monkeypatch.setattr(status_module, "_forge_fixed_backup_volume_is_ready", backup)
monkeypatch.setattr(status_module, "_forge_bootstrap_runtime_checks", runtime)
monkeypatch.setattr(status_module, "_prepare_forge_secret_bundle", helper)
body = json.dumps(_payload(status_module), separators=(",", ":")).encode()
status, _response = _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
_signed_headers(status_module, body),
)
assert status == 409
assert calls["helper"] == 0
if failed_gate in {"artifact", "store", "backup"}:
assert calls["runtime"] == 0
else:
assert calls["runtime"] == 1
def test_signed_route_returns_only_sanitized_bundle_readiness_and_replay_is_rejected(
status_module, endpoint, monkeypatch
):
consumed = _install_hmac_test(monkeypatch, status_module)
helper_calls = []
def helper(oidc_secret, read_token, config_bytes, *, validate_config):
helper_calls.append((oidc_secret, read_token, config_bytes))
assert oidc_secret == OIDC_SECRET
assert read_token == READ_TOKEN
assert validate_config(config_bytes) is True
return {
"status": "bundle_ready",
"secretFilesReady": 7,
"masterKeyBackupVerified": True,
"applyPreflight": "not_evaluated",
}
counters = _install_successful_prerequisites(monkeypatch, status_module, helper)
monkeypatch.setattr(
status_module._forge_first_install_controller,
"remember_preflight",
lambda *_args: pytest.fail("bundle readiness must not remember a preflight"),
)
monkeypatch.setattr(
status_module._forge_first_install_controller,
"apply",
lambda *_args: pytest.fail("bundle readiness must not start first install"),
)
body = json.dumps(_payload(status_module), separators=(",", ":")).encode()
headers = _signed_headers(status_module, body)
nonce = headers["X-ITWorx-Deployment-Nonce"]
status, response = _post(
endpoint, status_module.FORGE_SECRET_DELIVERY_PATH, body, headers
)
assert status == 200
decoded = json.loads(response)
assert decoded == {
"apiVersion": status_module.FORGE_SECRET_DELIVERY_API_VERSION,
"target": "itworx-forge",
"status": "bundle_ready",
"profile": PROFILE,
"controllerArtifact": {
key: ARTIFACT[key]
for key in (
"controllerVersion",
"sourceRepository",
"sourceRevision",
"sourceTree",
"artifactSha256",
)
},
"secretFilesReady": 7,
"masterKeyBackupVerified": True,
"applyPreflight": "not_evaluated",
}
assert "secretFilesReady" in decoded
assert OIDC_SECRET.decode() not in response.decode()
assert READ_TOKEN.decode() not in response.decode()
assert base64.b64encode(OIDC_SECRET).decode() not in response.decode()
assert base64.b64encode(READ_TOKEN).decode() not in response.decode()
assert len(consumed) == 1 and nonce in consumed
assert helper_calls == [(OIDC_SECRET, READ_TOKEN, _config())]
assert counters == {"artifact": 2, "store": 2, "backup": 2, "runtime": 1}
status, replay_response = _post(
endpoint, status_module.FORGE_SECRET_DELIVERY_PATH, body, headers
)
assert status == 403
assert replay_response == b'{"status":"authentication_rejected"}\n'
assert len(helper_calls) == 1
assert counters == {"artifact": 2, "store": 2, "backup": 2, "runtime": 1}
def test_new_nonce_retries_same_bundle_and_changed_input_is_rejected(
status_module, endpoint, monkeypatch
):
_install_hmac_test(monkeypatch, status_module)
from forge_secret_bundle import ForgeSecretBundleError
accepted_inputs = None
helper_calls = []
def helper(oidc_secret, read_token, config_bytes, *, validate_config):
nonlocal accepted_inputs
inputs = (oidc_secret, read_token, config_bytes)
helper_calls.append(inputs)
assert validate_config(config_bytes) is True
if accepted_inputs is None:
accepted_inputs = inputs
elif inputs != accepted_inputs:
raise ForgeSecretBundleError("input_changed")
return {
"status": "bundle_ready",
"secretFilesReady": 7,
"masterKeyBackupVerified": True,
"applyPreflight": "not_evaluated",
}
_install_successful_prerequisites(monkeypatch, status_module, helper)
def send(payload):
body = json.dumps(payload, separators=(",", ":")).encode()
return _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
_signed_headers(status_module, body),
)
assert send(_payload(status_module))[0] == 200
assert send(_payload(status_module))[0] == 200
changed = _payload(status_module)
changed["oidcClientSecretBase64"] = base64.b64encode(b"x" * 40).decode("ascii")
status, response = send(changed)
assert status == 409
assert json.loads(response) == {
"status": "secret_bundle_failed",
"code": "input_changed",
}
assert accepted_inputs == (OIDC_SECRET, READ_TOKEN, _config())
assert helper_calls == [
accepted_inputs,
accepted_inputs,
(b"x" * 40, READ_TOKEN, _config()),
]
assert OIDC_SECRET.decode() not in response.decode()
assert READ_TOKEN.decode() not in response.decode()
@pytest.mark.skipif(
os.name != "posix" or getattr(os, "geteuid", lambda: 1)() != 0,
reason="the real bundle helper requires POSIX root ownership",
)
def test_signed_http_delivery_uses_real_bundle_helper_and_never_rotates(
status_module, endpoint, monkeypatch, tmp_path
):
_install_hmac_test(monkeypatch, status_module)
import importlib
bundle_module = importlib.import_module("forge_secret_bundle")
assert Path(bundle_module.__file__).resolve() == (
BROKER / "forge_secret_bundle.py"
).resolve()
root = tmp_path / "real-bundle-root"
root.mkdir(mode=0o700)
root.chmod(0o700)
roots = bundle_module._Roots.for_test(root)
for path in (roots.config_root, roots.state_parent, roots.backup_root):
path.mkdir(mode=0o700, exist_ok=True)
path.chmod(0o700)
monkeypatch.setattr(bundle_module, "_PRODUCTION_ROOTS", roots)
counters = _install_successful_prerequisites(
monkeypatch, status_module
)
monkeypatch.setattr(
status_module._forge_first_install_controller,
"remember_preflight",
lambda *_args: pytest.fail("bundle readiness must not persist a preflight"),
)
monkeypatch.setattr(
status_module._forge_first_install_controller,
"apply",
lambda *_args: pytest.fail("bundle readiness must not start first install"),
)
def send(payload):
body = json.dumps(payload, separators=(",", ":")).encode()
return _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
_signed_headers(status_module, body),
)
status, response = send(_payload(status_module))
assert status == 200
decoded = json.loads(response)
assert decoded["status"] == "bundle_ready"
assert decoded["secretFilesReady"] == 7
assert decoded["masterKeyBackupVerified"] is True
assert decoded["applyPreflight"] == "not_evaluated"
assert OIDC_SECRET.decode() not in response.decode()
assert READ_TOKEN.decode() not in response.decode()
secret_paths = sorted(roots.secrets_root.iterdir())
assert {path.name for path in secret_paths} == {
"postgres_bootstrap_password.secret",
"runtime_database_url.secret",
"migrator_database_url.secret",
"oidc_client_secret.secret",
"session_secret.secret",
"master_key.secret",
"authentik_read_token.secret",
}
assert (roots.secrets_root / "oidc_client_secret.secret").read_bytes() == OIDC_SECRET
assert (roots.secrets_root / "authentik_read_token.secret").read_bytes() == READ_TOKEN
assert roots.config_path.read_bytes() == _config()
master_key = (roots.secrets_root / "master_key.secret").read_bytes()
assert len(master_key) == 32
backup_path = roots.backup_root / "master_key.backup"
assert backup_path.read_bytes() == master_key
protected_paths = [*secret_paths, roots.config_path, backup_path]
def identities():
return {
path: (path.stat().st_ino, path.read_bytes())
for path in protected_paths
}
original = identities()
status, retry_response = send(_payload(status_module))
assert status == 200
assert json.loads(retry_response)["status"] == "bundle_ready"
assert identities() == original
changed = _payload(status_module)
changed["authentikReadTokenBase64"] = base64.b64encode(
b"different-authentik-read-token-value-12345"
).decode("ascii")
status, changed_response = send(changed)
assert status == 409
assert json.loads(changed_response) == {
"status": "secret_bundle_failed",
"code": "input_changed",
}
assert identities() == original
assert OIDC_SECRET.decode() not in changed_response.decode()
assert READ_TOKEN.decode() not in changed_response.decode()
assert counters == {"artifact": 6, "store": 6, "backup": 6, "runtime": 3}
@pytest.mark.parametrize(
"bad_result",
[
{
"status": "bundle_ready",
"secretFilesReady": True,
"masterKeyBackupVerified": True,
"applyPreflight": "not_evaluated",
},
{
"status": "bundle_ready",
"secretFilesReady": 7,
"masterKeyBackupVerified": 1,
"applyPreflight": "not_evaluated",
},
{
"status": "bundle_ready",
"secretFilesReady": 7,
"masterKeyBackupVerified": True,
"applyPreflight": "not_evaluated",
"unexpected": "must be rejected",
},
],
)
def test_malformed_core_helper_results_are_not_accepted(
status_module, endpoint, monkeypatch, bad_result
):
_install_hmac_test(monkeypatch, status_module)
_install_successful_prerequisites(
monkeypatch, status_module, lambda *_args, **_kwargs: bad_result
)
body = json.dumps(_payload(status_module), separators=(",", ":")).encode()
status, response = _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
_signed_headers(status_module, body),
)
assert status == 409
assert json.loads(response) == {
"status": "secret_bundle_failed",
"code": "bundle_result_unverified",
}
def test_unrecognized_core_error_code_is_replaced_with_fixed_code(
status_module, endpoint, monkeypatch
):
_install_hmac_test(monkeypatch, status_module)
from forge_secret_bundle import ForgeSecretBundleError
private_error = "unapproved-" + OIDC_SECRET.decode()
def helper(*_args, **_kwargs):
raise ForgeSecretBundleError(private_error)
_install_successful_prerequisites(monkeypatch, status_module, helper)
body = json.dumps(_payload(status_module), separators=(",", ":")).encode()
status, response = _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
_signed_headers(status_module, body),
)
assert status == 409
assert json.loads(response) == {
"status": "secret_bundle_failed",
"code": "bundle_preparation_failed",
}
assert private_error.encode() not in response
assert OIDC_SECRET.decode().encode() not in response
def test_helper_error_text_is_never_returned(status_module, endpoint, monkeypatch):
_install_hmac_test(monkeypatch, status_module)
def helper(*_args, **_kwargs):
raise RuntimeError("disk failed: " + OIDC_SECRET.decode())
_install_successful_prerequisites(monkeypatch, status_module, helper)
body = json.dumps(_payload(status_module), separators=(",", ":")).encode()
status, response = _post(
endpoint,
status_module.FORGE_SECRET_DELIVERY_PATH,
body,
_signed_headers(status_module, body),
)
assert status == 409
assert OIDC_SECRET.decode() not in response.decode()
assert READ_TOKEN.decode() not in response.decode()
assert b"disk failed" not in response
def test_held_candidate_runtime_skips_destination_secrets_and_preflight_receipt(
status_module, monkeypatch, tmp_path
):
parsed = status_module._forge_secret_delivery_candidate(_payload(status_module))
assert parsed is not None
profile, candidate = parsed
app_root = tmp_path / "managed-appdata"
postgres = app_root / "itworx-forge" / "postgres"
postgres.mkdir(parents=True)
postgres.chmod(0o700)
os.chown(postgres, 70, 70)
app_root.chmod(0o700)
monkeypatch.setattr(status_module, "MANAGED_APPDATA_ROOT", app_root)
monkeypatch.setattr(status_module, "_refresh_appdata_root_selection", lambda: app_root)
monkeypatch.setattr(status_module, "appdata_root_is_safe", lambda _root: True)
monkeypatch.setattr(status_module, "_forge_app_dir_metadata_is_safe", lambda _info: True)
monkeypatch.setattr(status_module, "_forge_gitea_main_revision", lambda: profile["revision"])
manifest = b"fixed compose bytes used by this unit test"
monkeypatch.setattr(
status_module,
"FORGE_COMPOSE_SHA256",
hashlib.sha256(manifest).hexdigest(),
)
calls = {"registry": 0, "oidc": 0, "secret_metadata": 0}
def registry(_profile, *, candidate_config):
calls["registry"] += 1
assert candidate_config is candidate["config"]
return "passed", {"ownerVisibility": "private"}
def oidc(*, held_candidate):
calls["oidc"] += 1
assert held_candidate is candidate
return "passed", {"clientSecretMatched": True}
def secret_metadata(*_args, **_kwargs):
calls["secret_metadata"] += 1
pytest.fail("held candidate must not read destination secret files")
monkeypatch.setattr(status_module, "_forge_image_registry_attestation", registry)
monkeypatch.setattr(status_module, "_forge_oidc_attestation", oidc)
monkeypatch.setattr(status_module, "_forge_secret_metadata", secret_metadata)
monkeypatch.setattr(status_module, "_load_forge_bootstrap_config", lambda *_args: pytest.fail("held config fell back to disk"))
monkeypatch.setattr(status_module, "_forge_push_validation_attestation", lambda _revision: ("passed", {"context": "fixed"}))
monkeypatch.setattr(status_module, "_forge_local_image_present", lambda _image: None)
monkeypatch.setattr(
status_module._forge_first_install_controller,
"remember_preflight",
lambda *_args: pytest.fail("secret bundle readiness must not persist a preflight receipt"),
)
def fake_run(arguments, **_kwargs):
if arguments[:2] == ["git", "--git-dir"]:
return subprocess.CompletedProcess(arguments, 0, stdout=manifest, stderr=b"")
if arguments == ["docker", "network", "ls", "--format", "{{.Name}}"]:
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 == ["docker", "ps", "-a", "--format", "{{.Names}}"]:
return subprocess.CompletedProcess(arguments, 0, stdout="", stderr="")
pytest.fail(f"unexpected runtime command: {arguments}")
monkeypatch.setattr(status_module.subprocess, "run", fake_run)
result = status_module._forge_bootstrap_runtime_checks(
profile, _held_candidate=candidate
)
assert result["status"] == "bundle_prerequisites_passed"
assert "secret_delivery" not in result["checks"]
assert calls == {"registry": 1, "oidc": 1, "secret_metadata": 0}
def test_release_store_proof_checks_fixed_volume_alias_and_pinned_identity(
status_module, monkeypatch
):
release_store = status_module._mcp_hub_release_store
document = {
"Name": release_store.VOLUME_NAME,
"Driver": "local",
"Scope": "local",
"Mountpoint": release_store.VOLUME_HOST_ROOT.as_posix(),
"Options": None,
"Labels": dict(release_store.EXPECTED_LABELS),
}
commands = []
def run_fixed(arguments, *, maximum_stdout, **_kwargs):
commands.append((arguments, maximum_stdout))
if arguments[:2] == ["docker", "info"]:
return b"/var/lib/docker\n", b""
if arguments[:3] == ["docker", "volume", "inspect"]:
return json.dumps(document).encode(), b""
pytest.fail(f"unexpected command {arguments}")
class _PinnedDirectory:
def __init__(self, identity):
self.value = identity
def identity(self):
return self.value
class _Store:
host = _PinnedDirectory((4, 5))
logical = _PinnedDirectory((4, 5))
def __enter__(self):
return self
def __exit__(self, *_args):
return None
def assert_current(self):
return None
opened = []
def open_fixed(metadata, docker_root_dir):
opened.append((metadata, docker_root_dir))
return _Store()
monkeypatch.setattr(status_module, "_forge_bounded_readonly_command", run_fixed)
monkeypatch.setattr(status_module, "_repopulse_adoption_alias_mounts_ready", lambda: True)
monkeypatch.setattr(
status_module._repopulse_adoption_store,
"capability_report",
lambda: {"controllerAliasesReady": True},
)
monkeypatch.setattr(release_store, "open_fixed_release_store", open_fixed)
identity = status_module._forge_release_store_identity()
assert identity == (
"/var/lib/docker",
release_store.VOLUME_NAME,
(4, 5),
(4, 5),
)
assert opened == [(document, "/var/lib/docker")]
assert [item[0] for item in commands] == [
["docker", "info", "--format", "{{.DockerRootDir}}"],
[
"docker",
"volume",
"inspect",
"--format",
"{{json .}}",
release_store.VOLUME_NAME,
],
]
opened.clear()
document["Labels"] = {"itworx.purpose": "different-volume"}
assert status_module._forge_release_store_identity() is None
assert opened == []
def test_backup_volume_verifier_requires_its_read_only_none_contract(
status_module, monkeypatch
):
backup = types.ModuleType("forge_backup_paths")
backup.validate_forge_backup_volume = lambda: None
monkeypatch.setitem(sys.modules, "forge_backup_paths", backup)
assert status_module._forge_fixed_backup_volume_is_ready() is True
backup.validate_forge_backup_volume = lambda: True
assert status_module._forge_fixed_backup_volume_is_ready() is False
@@ -267,6 +267,36 @@ def test_forge_bootstrap_preflight_is_read_only_and_rejects_generic_apply(integr
json={"operation": "apply", "plan": profile},
)
assert generic_apply.status_code == 422
generic_rollback = client.post(
"/api/deployments",
headers={**human_headers(client), "Idempotency-Key": "forge-generic-rollback-0001"},
json={"operation": "rollback", "plan": profile},
)
assert generic_rollback.status_code == 422
apply_profile = {
"api_version": "itworx.forge-first-install/v1",
"revision": profile["revision"],
"workspace_image_digest": profile["workspace_image_digest"],
"frontdoor_image_digest": profile["frontdoor_image_digest"],
"preflight_job_id": result["id"],
"preflight_sha256": result["preflightSha256"],
}
dedicated_apply = client.post(
"/api/deployments/forge-bootstrap/first-install",
headers={**human_headers(client), "Idempotency-Key": "forge-api-install-0001"},
json=apply_profile,
)
assert dedicated_apply.status_code == 409
assert "passed Forge preflight" in dedicated_apply.json()["detail"]
assert client.get(
"/api/deployments/forge-bootstrap/first-install/forge-install-" + "0" * 64,
headers=human_headers(client),
).status_code == 404
assert client.get(
"/api/deployments/forge-install-" + "0" * 64,
headers=human_headers(client),
).status_code == 404
async def exercise_mcp():
async with Client(mcp_server.appops_mcp) as mcp_client:
@@ -280,6 +310,23 @@ def test_forge_bootstrap_preflight_is_read_only_and_rejects_generic_apply(integr
{"plan": profile, "idempotencyKey": "forge-mcp-apply-0001"},
)
assert apply.structured_content["status"] == "invalid"
forge_apply = await mcp_client.call_tool(
"forge.bootstrap.apply",
{"profile": apply_profile, "idempotencyKey": "forge-mcp-install-0001"},
)
assert forge_apply.structured_content["status"] == "invalid"
forge_status = await mcp_client.call_tool(
"forge.bootstrap.status", {"jobId": "forge-install-" + "0" * 64}
)
assert forge_status.structured_content["status"] == "not_found"
generic_forge_status = await mcp_client.call_tool(
"deployment.status", {"jobId": "forge-install-" + "0" * 64}
)
assert generic_forge_status.structured_content["status"] == "invalid"
forge_resume = await mcp_client.call_tool(
"forge.bootstrap.resume", {"jobId": "forge-install-" + "0" * 64}
)
assert forge_resume.structured_content["status"] == "not_found"
asyncio.run(exercise_mcp())
@@ -2,13 +2,13 @@ from __future__ import annotations
import importlib.util
import io
import json
import sys
import types
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
CONTROLLER_ROOT = ROOT / "ops" / "gitea-deploy-broker"
CONTROLLER_PATH = CONTROLLER_ROOT / "runner_capacity_controller.py"
@@ -123,8 +123,18 @@ def test_host_snapshot_handles_absent_load_tuple(capacity_module, monkeypatch):
assert result["host"] is None
@pytest.mark.parametrize("raw", ["", "pswpin 1\n", "pswpin -1\npswpout 2\n",
"pswpin 1\npswpin 2\npswpout 3\n", "pswpin invalid\npswpout 2\n", "x" * 65537])
@pytest.mark.parametrize(
"raw",
[
"",
"pswpin 1\n",
"pswpin -1\npswpout 2\n",
"pswpin 1\npswpin 2\npswpout 3\n",
"pswpin invalid\npswpout 2\n",
"x" * 65537,
],
ids=["empty", "missing-counter", "negative", "duplicate", "invalid", "oversized"],
)
def test_strict_swap_reader_rejects_missing_or_invalid_counters(capacity_module, monkeypatch, raw):
monkeypatch.setattr(capacity_module, "Path", lambda _path: types.SimpleNamespace(
open=lambda **_kwargs: io.StringIO(raw)
@@ -161,6 +171,96 @@ def test_host_snapshot_rejects_invalid_global_metrics(capacity_module, monkeypat
assert result["host"] is None
def test_snapshot_and_cycle_treat_empty_optional_burst_inspect_as_not_configured(
capacity_module, monkeypatch
):
service = capacity_module.RunnerCapacityController()
commands = []
state_reads = []
def inspect_only(arguments, **kwargs):
commands.append((arguments, kwargs))
assert arguments == ["docker", "inspect", capacity_module.BURST_RUNNER_NAME]
assert kwargs == {"check": False}
return "[]"
def unexpected(*_args, **_kwargs):
pytest.fail("an absent optional burst runner must not reach registration, state, or mutation")
monkeypatch.setattr(service, "_run", inspect_only)
monkeypatch.setattr(service, "_read_state", lambda: state_reads.append("read") or {})
for name in ("_runner_registration", "_gitea_request", "_write_state", "_record", "_set_disabled"):
monkeypatch.setattr(service, name, unexpected)
snapshot = service.snapshot()
result = service.cycle()
assert snapshot["status"] == "not_configured"
assert snapshot["runner"] == capacity_module.BURST_RUNNER_NAME
assert result["status"] == "not_configured"
assert result["runner"] == capacity_module.BURST_RUNNER_NAME
assert state_reads == ["read"]
assert commands == [
(["docker", "inspect", capacity_module.BURST_RUNNER_NAME], {"check": False}),
(["docker", "inspect", capacity_module.BURST_RUNNER_NAME], {"check": False}),
]
@pytest.mark.parametrize(
("raw", "reason"),
[
("not-json", "runner_capacity_container_invalid"),
("{}", "runner_capacity_container_invalid"),
(
json.dumps([
{"Config": {"Labels": {
"com.docker.compose.project": "gitea-deploy-runners",
"tech.itworx.role": "gitea-actions-burst-runner",
}}, "State": {}},
{"Config": {"Labels": {
"com.docker.compose.project": "gitea-deploy-runners",
"tech.itworx.role": "gitea-actions-burst-runner",
}}, "State": {}},
]),
"runner_capacity_container_invalid",
),
(
json.dumps([{
"Config": {"Labels": {
"com.docker.compose.project": "foreign-project",
"tech.itworx.role": "gitea-actions-burst-runner",
}},
"State": {},
}]),
"runner_capacity_container_identity_invalid",
),
],
)
def test_cycle_still_refuses_malformed_foreign_or_multiple_burst_inspect_results(
capacity_module, monkeypatch, raw, reason
):
service = capacity_module.RunnerCapacityController()
commands = []
def inspect_only(arguments, **kwargs):
commands.append((arguments, kwargs))
return raw
def unexpected(*_args, **_kwargs):
pytest.fail("invalid burst inventory must stop before registration or mutation")
monkeypatch.setattr(service, "_run", inspect_only)
for name in ("_runner_registration", "_gitea_request", "_write_state", "_record", "_set_disabled"):
monkeypatch.setattr(service, name, unexpected)
with pytest.raises(capacity_module.RunnerCapacityError, match=reason):
service.cycle()
assert commands == [
(["docker", "inspect", capacity_module.BURST_RUNNER_NAME], {"check": False})
]
def _wire_common(service, monkeypatch, *, running=False, busy=False, disabled=True):
monkeypatch.setattr(
service,
@@ -183,7 +283,7 @@ def _wire_common(service, monkeypatch, *, running=False, busy=False, disabled=Tr
"labels": ["linux-validation", "ubuntu-latest"],
},
)
monkeypatch.setattr(service, "_read_state", lambda: {})
monkeypatch.setattr(service, "_read_state", dict)
monkeypatch.setattr(service, "_write_state", lambda _state: None)
monkeypatch.setattr(service, "_record", lambda _event: None)
monkeypatch.setattr(service, "_host_guard_state", lambda: ("clear", ""))
@@ -421,6 +421,119 @@ def test_successful_main_run_accepts_actual_gitea_run_without_workflow_id(
assert calls[1].endswith("/actions/runs?branch=main&limit=100&page=1")
def test_source_image_fetch_accepts_actual_gitea_commit_tree_shape(
specialize, monkeypatch, tmp_path
):
revision = "a" * 40
tree = "b" * 40
calls = []
workflow_calls = []
def api(path):
calls.append(path)
if path.endswith("/branches/main"):
return {"commit": {"id": revision}}
if path.endswith(f"/git/commits/{revision}"):
return {"sha": revision, "commit": {"tree": {"sha": tree}}}
if "/actions/runs/" in path and "/artifacts?" in path:
return {
"artifacts": [
{
"name": specialize.IMAGE_ARTIFACT_PREFIX + revision,
"expired": False,
"id": 44,
"size_in_bytes": 1,
}
]
}
raise AssertionError(f"unexpected fixed Gitea API path: {path}")
def successful_run(workflow_path, requested_revision):
assert requested_revision == revision
workflow_calls.append(workflow_path)
return {"id": 41 if workflow_path == specialize.IMAGE_WORKFLOW else 42}
class ReachedArtifactDownload(Exception):
pass
def download(_artifact_id, _destination):
raise ReachedArtifactDownload
monkeypatch.setattr(specialize, "_api_json", api)
monkeypatch.setattr(specialize, "_successful_main_run", successful_run)
monkeypatch.setattr(specialize, "_download_artifact_zip", download)
with pytest.raises(ReachedArtifactDownload):
specialize._fetch_source_image_artifact(revision, tree, tmp_path)
assert workflow_calls == [specialize.IMAGE_WORKFLOW, specialize.FULL_WORKFLOW]
assert calls == [
f"/repos/{specialize.SOURCE_REPOSITORY_API}/branches/main",
f"/repos/{specialize.SOURCE_REPOSITORY_API}/git/commits/{revision}",
f"/repos/{specialize.SOURCE_REPOSITORY_API}/actions/runs/41/artifacts?name={specialize.IMAGE_ARTIFACT_PREFIX}{revision}",
]
@pytest.mark.parametrize(
("commit_value", "error"),
[
({"sha": "a" * 40}, "source_tree_mismatch"),
({"sha": "a" * 40, "commit": {}}, "source_tree_mismatch"),
(
{"sha": "a" * 40, "commit": {"tree": {"sha": "c" * 40}}},
"source_tree_mismatch",
),
(
{"sha": "a" * 40, "tree": {"sha": "b" * 40}},
"source_tree_mismatch",
),
(
{"sha": "c" * 40, "commit": {"tree": {"sha": "b" * 40}}},
"source_revision_mismatch",
),
(
{"commit": {"tree": {"sha": "b" * 40}}},
"source_revision_mismatch",
),
],
)
def test_source_image_fetch_rejects_noncanonical_gitea_commit_payload_before_artifact_lookup(
specialize, monkeypatch, tmp_path, commit_value, error
):
revision = "a" * 40
calls = []
workflow_calls = []
def api(path):
calls.append(path)
if path.endswith("/branches/main"):
return {"commit": {"id": revision}}
if path.endswith(f"/git/commits/{revision}"):
return commit_value
raise AssertionError(f"unexpected fixed Gitea API path: {path}")
def unexpected_workflow(*_args):
workflow_calls.append(True)
raise AssertionError("workflow lookup must not follow an untrusted commit response")
monkeypatch.setattr(specialize, "_api_json", api)
monkeypatch.setattr(specialize, "_successful_main_run", unexpected_workflow)
monkeypatch.setattr(
specialize,
"_download_artifact_zip",
lambda *_args: pytest.fail("artifact download must not follow an untrusted commit response"),
)
with pytest.raises(specialize.RunnerFleetSpecializeError, match=error):
specialize._fetch_source_image_artifact(revision, "b" * 40, tmp_path)
assert calls == [
f"/repos/{specialize.SOURCE_REPOSITORY_API}/branches/main",
f"/repos/{specialize.SOURCE_REPOSITORY_API}/git/commits/{revision}",
]
assert workflow_calls == []
@pytest.mark.parametrize(
"changes",
[
+48 -1
View File
@@ -93,12 +93,59 @@ def test_portainer_config_save_never_returns_secret(api_client):
"verifyTls": False,
}
assert secret not in str(payload)
all_settings = api_client.get("/api/settings/connectors").json()
assert secret not in str(all_settings)
assert next(item for item in all_settings if item["connectorId"] == "portainer")["hasSecret"] is True
def test_generic_authentik_setup_cannot_set_or_overwrite_forge_oidc_binding(api_client):
import json
from sqlmodel import Session
from backend.app.db import session as db_session
from backend.app.schemas.forge_oidc import FORGE_OIDC_BINDING_SECRET_REF
from backend.app.services.secret_store import get_secret, set_secret
from backend.app.services.setup_service import _apply_forge_oidc_binding_to_runtime
from backend.app.core.config import settings
binding = json.dumps(
{
"version": 1,
"providerId": "31",
"scopeMappingIds": {
"openid": "75a34fd6-cc81-40b8-a8c3-2745af9567ab",
"email": "312810bb-11da-47d8-97d4-f2155348eaa2",
"profile": "7aa8c5f8-5e8e-4a1b-9c11-3a4f0a731cff",
},
}
)
with Session(db_session.engine) as session:
set_secret(session, FORGE_OIDC_BINDING_SECRET_REF, binding)
rejected = api_client.patch(
"/api/settings/connectors/authentik",
json={"forgeOidcBinding": binding, "providerId": "999"},
)
assert rejected.status_code == 422
saved = api_client.patch(
"/api/settings/connectors/authentik",
json={"url": "https://auth.example-host.com", "secret": "GENERIC_AUTHENTIK_TOKEN"},
)
assert saved.status_code == 200
assert binding not in saved.text
assert "GENERIC_AUTHENTIK_TOKEN" not in saved.text
with Session(db_session.engine) as session:
assert get_secret(session, FORGE_OIDC_BINDING_SECRET_REF, touch=False) == binding
assert _apply_forge_oidc_binding_to_runtime(session) is True
assert settings.apis["AUTHENTIK_FORGE_OIDC_PROVIDER_ID"] == "31"
all_settings = api_client.get("/api/settings/connectors").json()
assert binding not in str(all_settings)
assert "GENERIC_AUTHENTIK_TOKEN" not in str(all_settings)
def test_portainer_config_save_trims_safe_fields(api_client):
payload = api_client.patch(
"/api/settings/connectors/portainer",