Publish curated Unraid-AppOps-Gateway source from ad0a390e431166c3198a2ac3bec010c2b44f2dea
This commit is contained in:
1 parent
29c1e3d5a1
commit
98d31477f9
35 files changed
+7264
-185
No files matched your search
@@ -22,7 +22,12 @@ from ..connectors.nginx_proxy_manager import NginxProxyManagerConnector
|
||||
from ..connectors.portainer import PortainerConnector
|
||||
from ..core.time import utc_now
|
||||
from ..db.models import AppSetting, Enrollment, EnrollmentJob, Policy
|
||||
from ..schemas.enrollment import EnrollmentPlanV1, EnrollmentTrustConfigV1
|
||||
from ..schemas.enrollment import (
|
||||
LEGACY_APPDATA_ROOT,
|
||||
MANAGED_DOCKER_APPDATA_ROOT,
|
||||
EnrollmentPlanV1,
|
||||
EnrollmentTrustConfigV1,
|
||||
)
|
||||
from .audit_logger import log_event
|
||||
from .deployment_broker import DeploymentBrokerClient
|
||||
from .enrollment_profiles import PROFILE_VERSION, profile_contract, profile_summary
|
||||
@@ -40,6 +45,47 @@ _TERMINAL = {"ready", "failed", "compensation_required", "preflight_failed"}
|
||||
_CONTROLLER_EVIDENCE = {"status", "targetId", "repository", "applicationId", "appdataPath"}
|
||||
_PROXY_EVIDENCE = {"success", "status", "hostId", "hostname"}
|
||||
_OIDC_EVIDENCE = {"success", "status", "applicationId", "providerId", "clientSecretCreated"}
|
||||
_APPDATA_ROOT_STATUSES = {"ready", "needs_initialization", "unavailable"}
|
||||
|
||||
|
||||
def _trusted_appdata_roots_readiness(
|
||||
controller_capability: dict[str, Any],
|
||||
) -> list[dict[str, str]]:
|
||||
roots = (LEGACY_APPDATA_ROOT, MANAGED_DOCKER_APPDATA_ROOT)
|
||||
unavailable = {root: "unavailable" for root in roots}
|
||||
reported = controller_capability.get("availableTrustedAppdataRoots")
|
||||
if isinstance(reported, list):
|
||||
if len(reported) != len(roots):
|
||||
return [{"root": root, "status": unavailable[root]} for root in roots]
|
||||
statuses: dict[str, str] = {}
|
||||
for item in reported:
|
||||
if not isinstance(item, dict):
|
||||
return [{"root": root, "status": unavailable[root]} for root in roots]
|
||||
root = item.get("root")
|
||||
status = item.get("status")
|
||||
if (
|
||||
not isinstance(root, str)
|
||||
or root not in unavailable
|
||||
or root in statuses
|
||||
or not isinstance(status, str)
|
||||
or status not in _APPDATA_ROOT_STATUSES
|
||||
):
|
||||
return [{"root": expected, "status": unavailable[expected]} for expected in roots]
|
||||
statuses[root] = status
|
||||
if set(statuses) != set(roots):
|
||||
return [{"root": root, "status": unavailable[root]} for root in roots]
|
||||
return [{"root": root, "status": statuses[root]} for root in roots]
|
||||
|
||||
# A legacy V1 controller reports only its configured root. It cannot attest
|
||||
# independent managed-volume readiness, so only its exact legacy root can
|
||||
# be marked ready through this compatibility path.
|
||||
if "availableTrustedAppdataRoots" not in controller_capability:
|
||||
if (
|
||||
controller_capability.get("status") == "ready"
|
||||
and controller_capability.get("trustedAppdataRoot") == LEGACY_APPDATA_ROOT
|
||||
):
|
||||
unavailable[LEGACY_APPDATA_ROOT] = "ready"
|
||||
return [{"root": root, "status": unavailable[root]} for root in roots]
|
||||
|
||||
|
||||
class EnrollmentError(ValueError):
|
||||
@@ -135,6 +181,8 @@ class EnrollmentPlane:
|
||||
"status": "needs_setup",
|
||||
"blockers": ["enrollment_trust_not_configured"],
|
||||
"configured": False,
|
||||
"trustedAppdataRoot": LEGACY_APPDATA_ROOT,
|
||||
"managedAppdataBackupAcknowledged": False,
|
||||
"secretStore": storage,
|
||||
}
|
||||
return {
|
||||
@@ -143,6 +191,7 @@ class EnrollmentPlane:
|
||||
"configured": True,
|
||||
"controllerIdentity": trust.controller_identity,
|
||||
"trustedAppdataRoot": trust.trusted_appdata_root,
|
||||
"managedAppdataBackupAcknowledged": trust.managed_appdata_backup_acknowledged,
|
||||
"approvedNetworks": list(trust.approved_networks),
|
||||
"allowedDomainSuffixes": list(trust.allowed_domain_suffixes),
|
||||
"autonomousProfiles": list(trust.autonomous_profiles),
|
||||
@@ -162,11 +211,43 @@ class EnrollmentPlane:
|
||||
bootstrap_secret = get_secret(session, trust.bootstrap_secret_ref, touch=False)
|
||||
if bootstrap_secret:
|
||||
controller_capability = self.broker.enrollment_capabilities(secret=bootstrap_secret)
|
||||
available_roots = _trusted_appdata_roots_readiness(controller_capability)
|
||||
if trust is not None:
|
||||
selected_root = next(
|
||||
(
|
||||
item
|
||||
for item in available_roots
|
||||
if item["root"] == trust.trusted_appdata_root
|
||||
),
|
||||
None,
|
||||
)
|
||||
controller_root_matches = bool(selected_root and selected_root.get("status") == "ready")
|
||||
else:
|
||||
controller_root_matches = False
|
||||
broker_ready = bool(
|
||||
bootstrap
|
||||
and self.broker.is_configured()
|
||||
and controller_capability.get("status") == "ready"
|
||||
and controller_root_matches
|
||||
)
|
||||
controller_managed_storage = controller_capability.get("managedStorage")
|
||||
if not isinstance(controller_managed_storage, dict):
|
||||
controller_managed_storage = {}
|
||||
if trust is None or trust.trusted_appdata_root != MANAGED_DOCKER_APPDATA_ROOT:
|
||||
managed_storage_status = "not_selected"
|
||||
managed_storage_blocker = ""
|
||||
elif not trust.managed_appdata_backup_acknowledged:
|
||||
managed_storage_status = "blocked"
|
||||
managed_storage_blocker = "managed_appdata_backup_acknowledgement_required"
|
||||
elif controller_root_matches and controller_managed_storage.get("status") == "ready":
|
||||
managed_storage_status = "ready"
|
||||
managed_storage_blocker = ""
|
||||
elif controller_managed_storage.get("initializationAvailable") is True:
|
||||
managed_storage_status = "initialization_required"
|
||||
managed_storage_blocker = "managed_appdata_initialization_required"
|
||||
else:
|
||||
managed_storage_status = "unavailable"
|
||||
managed_storage_blocker = "managed_appdata_volume_unavailable"
|
||||
npm_ready = self.npm.is_configured()
|
||||
oidc_configured = bool(
|
||||
trust
|
||||
@@ -193,9 +274,27 @@ class EnrollmentPlane:
|
||||
"policyProvisioning": item(configured, "policy_template_unavailable"),
|
||||
"rollback": item(broker_ready, "deployment_executor_unavailable"),
|
||||
"verification": item(broker_ready, "deployment_executor_unavailable"),
|
||||
"availableTrustedAppdataRoots": available_roots,
|
||||
"managedStorage": {
|
||||
"status": managed_storage_status,
|
||||
"blockerCode": managed_storage_blocker,
|
||||
"root": MANAGED_DOCKER_APPDATA_ROOT,
|
||||
"backupScope": "dockerRootDir",
|
||||
"backupAcknowledged": bool(
|
||||
trust and trust.managed_appdata_backup_acknowledged
|
||||
),
|
||||
},
|
||||
"controller": {
|
||||
"status": str(controller_capability.get("status") or "unavailable"),
|
||||
"reason": str(controller_capability.get("reason") or "deployment_executor_unavailable"),
|
||||
"status": "ready" if broker_ready else "unavailable",
|
||||
"reason": (
|
||||
""
|
||||
if broker_ready
|
||||
else str(controller_capability.get("reason") or (
|
||||
"trusted_appdata_root_mismatch"
|
||||
if controller_capability.get("status") == "ready" and not controller_root_matches
|
||||
else "deployment_executor_unavailable"
|
||||
))
|
||||
),
|
||||
},
|
||||
"profiles": [profile_summary(name) for name in ("internal-webapp", "internal-webapp-oidc", "internal-service", "repopulse-authenticated")],
|
||||
}
|
||||
@@ -233,6 +332,7 @@ class EnrollmentPlane:
|
||||
"container_name": plan.target.container_name,
|
||||
"compose_file": plan.target.compose_file,
|
||||
"appdata_path": plan.appdata_path,
|
||||
"trusted_appdata_root": plan.trusted_appdata_root,
|
||||
"data_path": plan.data_path,
|
||||
"runtime_uid": plan.storage.runtime_uid,
|
||||
"runtime_gid": plan.storage.runtime_gid,
|
||||
@@ -264,6 +364,17 @@ class EnrollmentPlane:
|
||||
checks.append(_check("network", "passed", "Requested Docker network is within the trusted profile boundary."))
|
||||
else:
|
||||
checks.append(_check("network_unavailable", "blocked", "Requested Docker network is not approved for enrollment."))
|
||||
# RepoPulse is an established, fixed legacy-root exception; the
|
||||
# operator's selected root applies to other new targets only.
|
||||
expected_appdata_root = (
|
||||
LEGACY_APPDATA_ROOT
|
||||
if plan.profile == "repopulse-authenticated"
|
||||
else trust.trusted_appdata_root
|
||||
)
|
||||
if plan.trusted_appdata_root == expected_appdata_root:
|
||||
checks.append(_check("trusted_appdata_root", "passed", "Requested appdata root matches its fixed profile trust boundary."))
|
||||
else:
|
||||
checks.append(_check("trusted_appdata_root_mismatch", "blocked", "Requested appdata root does not match the explicit operator trust boundary."))
|
||||
if plan.profile in trust.autonomous_profiles:
|
||||
checks.append(_check("autonomous_profile", "passed", "Deployment profile is approved for autonomous enrollment."))
|
||||
else:
|
||||
@@ -397,7 +508,15 @@ class EnrollmentPlane:
|
||||
checks.append(_check("secret_store", "passed" if storage_ready else "blocked", "Production-grade write-only secret storage is ready." if storage_ready else "Enrollment requires APPOPS_SECRET_KEY from a protected environment variable or file; the development fallback is not permitted."))
|
||||
if trust:
|
||||
checks.append(_check("controller_bootstrap_secret", "passed" if has_secret(session, trust.bootstrap_secret_ref) else "blocked", "Controller bootstrap secret is available by reference." if has_secret(session, trust.bootstrap_secret_ref) else "Controller bootstrap secret reference is not configured."))
|
||||
if has_secret(session, trust.bootstrap_secret_ref):
|
||||
expected_appdata_root = (
|
||||
LEGACY_APPDATA_ROOT
|
||||
if plan.profile == "repopulse-authenticated"
|
||||
else trust.trusted_appdata_root
|
||||
)
|
||||
root_matches_trust = plan.trusted_appdata_root == expected_appdata_root
|
||||
if not root_matches_trust:
|
||||
checks.append(_check("controller_target", "blocked", "Controller preflight was skipped because the requested appdata root is outside the profile trust boundary."))
|
||||
elif has_secret(session, trust.bootstrap_secret_ref):
|
||||
controller = self.broker.enrollment_preflight(
|
||||
self._controller_payload(plan, enrollment_id="enr-00000000000000000000"),
|
||||
secret=get_secret(session, trust.bootstrap_secret_ref, touch=False) or "",
|
||||
|
||||
Reference in new issue
Block a user