from __future__ import annotations import pytest from sqlmodel import Session, SQLModel, create_engine, select from backend.app.db.models import Enrollment, EnrollmentJob, Policy from backend.app.schemas.enrollment import EnrollmentPlanV1, EnrollmentTrustConfigV1 from backend.app.services import enrollment_plane as enrollment_plane_module from backend.app.services.enrollment_plane import EnrollmentPlane from backend.app.services.secret_store import get_secret, set_secret def _plan(**overrides): value = { "application_id": "wsbc-portal", "display_name": "Windows Support Bundle Collector", "repository": "Jens/Windows-Support-Bundle-Collector", "revision": "a" * 40, "profile": "internal-webapp-oidc", "target": {"target_identity": "unraid-controller", "container_name": "wsbc-portal"}, "storage": {"runtime_uid": 1654, "runtime_gid": 1654}, "network": {"docker_network": "itworx-npm", "internal_port": 8080}, "artifact": {"release_version": "0.23.0", "image_repository": "wsbc-portal", "image_tag": "0.23.0"}, "exposure": {"hostname": "wsbc.example.com", "upstream_service": "wsbc-portal"}, "authentication": { "mode": "native-oidc", "redirect_path": "/signin-oidc", "logout_path": "/signout-callback-oidc", "roles": {"administrator": "Administrator", "support_agent": "SupportAgent", "read_only": "ReadOnly"}, }, "secrets": {"oidc_client_secret_required": True}, } value.update(overrides) return EnrollmentPlanV1.model_validate(value) def _repopulse_plan(**overrides): value = { "application_id": "repopulse", "display_name": "RepoPulse", "environment": "production", "repository": "Jens/RepoPulse", "revision": "c" * 40, "profile": "repopulse-authenticated", "target": {"target_identity": "unraid-controller", "container_name": "repopulse"}, "storage": {"runtime_uid": 10001, "runtime_gid": 10001}, "network": {"docker_network": "itworx-mcp-hub-npm", "internal_port": 8000}, "artifact": { "release_version": "1.5.0", "image_repository": "repopulse-managed", "image_tag": "production", "health_path": "/health", }, "exposure": {"hostname": "repopulse.example.com", "upstream_service": "repopulse"}, "secrets": {"additional_secret_refs": ["REPOPULSE_GITEA_TOKEN"]}, } value.update(overrides) return EnrollmentPlanV1.model_validate(value) def _trust(): return EnrollmentTrustConfigV1.model_validate({ "controller_identity": "unraid-controller", "bootstrap_secret_ref": "ENROLLMENT_BOOTSTRAP", "approved_networks": ["itworx-npm", "itworx-mcp-hub-npm"], "allowed_domain_suffixes": ["example.com"], "autonomous_profiles": ["internal-webapp-oidc", "internal-webapp", "internal-service", "repopulse-authenticated"], "repopulse_proxy_access_list_id": 42, "oidc_issuer": "https://auth.example.com/application/o/wsbc-portal", "administrator_group": "Administrator", "support_agent_group": "SupportAgent", "read_only_group": "ReadOnly", }) class _Broker: appdata_root = "/srv/example/appdata" def is_configured(self): return True def enrollment_capabilities(self, *, secret): assert secret == "b" * 64 return { "status": "ready", "trustedAppdataRoot": self.appdata_root, "features": { "deploymentApply": True, "deploymentVerify": True, "rollback": True, "exactTargetPolicy": True, "hmacV2": True, "replayProtection": True, "enrollment": True, "targetRegistration": True, "runtimeSecretSealing": True, }, } def enrollment_preflight(self, enrollment, *, secret): assert secret == "b" * 64 assert "target_credential" not in enrollment return {"status": "preflight_ok"} def enroll_target(self, enrollment, *, secret, idempotency_key): assert secret == "b" * 64 assert len(enrollment["target_credential"]) >= 48 assert enrollment["application_id"] == "wsbc-portal" return {"status": "enrolled", "targetId": enrollment["enrollment_id"], "applicationId": "wsbc-portal"} def configure_enrollment_runtime(self, runtime, *, secret): assert len(secret) >= 48 assert set(runtime["values"]) == {"APPOPS_PUBLIC_URL", "OIDC_CLIENT_ID", "OIDC_CLIENT_SECRET", "OIDC_ISSUER", "OIDC_ROLE_CLAIM"} return {"status": "configured", "enrollmentId": runtime["enrollment_id"], "applicationId": "wsbc-portal"} class _Portainer: def list_containers(self): return {"status": "ok", "containers": []} class _CollisionPortainer(_Portainer): def list_containers(self): return {"status": "ok", "containers": [{"name": "wsbc-portal"}]} class _Npm: def is_configured(self): return True def proxy_hosts(self): return {"success": True, "items": []} def ensure_managed_proxy_host(self, **kwargs): assert kwargs["hostname"] == "wsbc.example.com" assert kwargs["certificate_mode"] == "managed" return {"success": True, "status": "created", "hostId": 10, "hostname": kwargs["hostname"]} class _RepoPulseNpm(_Npm): def __init__(self, *, access_list_ok=True): self.access_list_ok = access_list_ok self.validated_ids = [] self.created_host = None def validate_managed_access_list(self, access_list_id): self.validated_ids.append(access_list_id) return { "success": self.access_list_ok, "status": "verified" if self.access_list_ok else "blocked", "message": "required_access_list_verified" if self.access_list_ok else "required_access_list_unavailable", **({"accessListId": access_list_id} if self.access_list_ok else {}), } def ensure_managed_proxy_host(self, **kwargs): assert kwargs["hostname"] == "repopulse.example.com" assert kwargs["forward_host"] == "repopulse" assert kwargs["forward_port"] == 8000 assert kwargs["access_list_id"] == 42 self.created_host = dict(kwargs) return {"success": True, "status": "created", "hostId": 10, "hostname": kwargs["hostname"]} class _RepoPulseBroker(_Broker): def __init__(self): self.runtime_values = None def enroll_target(self, enrollment, *, secret, idempotency_key): assert secret == "b" * 64 assert enrollment["application_id"] == "repopulse" assert enrollment["repository"] == "jens/repopulse" assert enrollment["profile"] == "repopulse-authenticated" assert enrollment["network"] == "itworx-mcp-hub-npm" assert enrollment["runtime_uid"] == enrollment["runtime_gid"] == 10001 assert enrollment["image_repository"] == "repopulse-managed" assert enrollment["image_tag"] == "production" return {"status": "enrolled", "targetId": enrollment["enrollment_id"], "applicationId": "repopulse"} def configure_enrollment_runtime(self, runtime, *, secret): assert len(secret) >= 48 self.runtime_values = dict(runtime["values"]) assert set(self.runtime_values) == { "REPOPULSE_GITEA_URL", "REPOPULSE_GITEA_TOKEN", "REPOPULSE_MAINTENANCE_TOKEN", } return {"status": "configured", "enrollmentId": runtime["enrollment_id"], "applicationId": "repopulse"} class _Authentik: def is_configured(self): return True def test_connection(self): return {"status": "connected"} def applications(self): return {"success": True, "items": []} def groups(self): return {"success": True, "items": [{"name": "Administrator"}, {"name": "SupportAgent"}, {"name": "ReadOnly"}]} def deployment_oidc_template_status(self, *, role_claim): assert role_claim == "roles" return {"status": "ready", "missing": []} def validate_deployment_oidc_template(self, *, role_claim, role_groups): return self.deployment_oidc_template_status(role_claim=role_claim) def ensure_deployment_oidc_application(self, **kwargs): assert kwargs["application_slug"] == "wsbc-portal" assert kwargs["client_secret"] return {"success": True, "status": "created", "applicationId": "application-1", "providerId": "provider-1"} def _session(tmp_path): engine = create_engine(f"sqlite:///{tmp_path / 'enrollment.db'}") SQLModel.metadata.create_all(engine) return Session(engine) def _plane(portainer=None, broker=None, npm=None, authentik=None): return EnrollmentPlane( broker=broker or _Broker(), portainer=portainer or _Portainer(), npm=npm or _Npm(), authentik=authentik or _Authentik(), ) class _CompensatingBroker(_Broker): def unenroll_target(self, _target, *, secret): assert secret == "b" * 64 return {"status": "rolled_back"} class _CompensatingNpm(_Npm): def remove_managed_proxy_host(self, *, host_id, ownership_id): return {"success": True, "status": "removed"} class _CompensatingAuthentik(_Authentik): def remove_deployment_oidc_application(self, **_kwargs): return {"success": True, "status": "removed"} def _compensating_plane(broker=None): return _plane( broker=broker or _CompensatingBroker(), npm=_CompensatingNpm(), authentik=_CompensatingAuthentik(), ) def _configure(session): set_secret(session, "ENROLLMENT_BOOTSTRAP", "b" * 64) EnrollmentPlane.set_trust_config(session, _trust()) @pytest.fixture(autouse=True) def _production_grade_secret_store(monkeypatch): """Plane unit tests exercise enrollment behavior with a managed key store.""" monkeypatch.setattr( enrollment_plane_module, "secret_storage_status", lambda: {"status": "ready", "productionReady": True}, ) def test_enrollment_schema_rejects_plaintext_secrets_and_untrusted_topology(): raw = _plan().model_dump() raw["target"]["command"] = "id" try: EnrollmentPlanV1.model_validate(raw) except ValueError as exc: assert "Extra inputs" in str(exc) else: # pragma: no cover - assertion clarity raise AssertionError("arbitrary target command must be rejected") raw = _plan().model_dump() raw["storage"]["appdata_subpath"] = "../etc" try: EnrollmentPlanV1.model_validate(raw) except ValueError as exc: assert "subpath" in str(exc) else: # pragma: no cover - assertion clarity raise AssertionError("appdata traversal must be rejected") raw = _plan().model_dump() raw["exposure"]["hostname"] = "*.example.com" try: EnrollmentPlanV1.model_validate(raw) except ValueError as exc: assert "hostname" in str(exc) else: # pragma: no cover - assertion clarity raise AssertionError("wildcard hostname must be rejected") def test_appdata_root_defaults_to_legacy_and_allows_only_the_fixed_managed_root(): plan = _plan() assert plan.trusted_appdata_root == "/srv/example/appdata" assert plan.appdata_path == "/srv/example/appdata/wsbc-portal" managed_root = "/var/lib/docker/volumes/itworx-managed-appdata/_data" managed = _plan(trusted_appdata_root=managed_root) assert managed.appdata_path == managed_root + "/wsbc-portal" plan_payload = _plan().model_dump() plan_payload["trusted_appdata_root"] = "/srv/example/other" trust_payload = _trust().model_dump() trust_payload["trusted_appdata_root"] = "/srv/example/other" with pytest.raises(ValueError): EnrollmentPlanV1.model_validate(plan_payload) with pytest.raises(ValueError): EnrollmentTrustConfigV1.model_validate(trust_payload) def test_repopulse_profile_accepts_only_its_fixed_authenticated_target(): plan = _repopulse_plan() assert plan.application_id == "repopulse" assert plan.repository == "jens/repopulse" assert plan.network.docker_network == "itworx-mcp-hub-npm" assert plan.secrets.additional_secret_refs == ("REPOPULSE_GITEA_TOKEN",) for field, value in ( ("application_id", "another-app"), ("repository", "Jens/another-app"), ("network", {"docker_network": "other-network", "internal_port": 8000}), ("artifact", {"release_version": "1.5.0", "image_repository": "repopulse-managed", "image_tag": "latest", "health_path": "/health"}), ("exposure", {"hostname": "other.example.com", "upstream_service": "repopulse"}), ("secrets", {"additional_secret_refs": ["OTHER_TOKEN"]}), ): raw = _repopulse_plan().model_dump(mode="json") raw[field] = value with pytest.raises(ValueError): EnrollmentPlanV1.model_validate(raw) def test_repopulse_preflight_requires_existing_secret_and_trusted_npm_access_list(tmp_path): with _session(tmp_path) as session: _configure(session) npm = _RepoPulseNpm(access_list_ok=False) plane = _plane(npm=npm) missing_secret = plane.preflight(session, _repopulse_plan()) assert missing_secret["status"] == "blocked" assert "repopulse_gitea_token_unavailable" in missing_secret["blockers"] assert npm.validated_ids == [42] set_secret(session, "REPOPULSE_GITEA_TOKEN", "existing-encrypted-gitea-token") bad_access_list = plane.preflight(session, _repopulse_plan()) assert bad_access_list["status"] == "blocked" assert "repopulse_access_list_unavailable" in bad_access_list["blockers"] assert session.exec(select(Enrollment)).all() == [] assert session.exec(select(EnrollmentJob)).all() == [] def test_repopulse_legacy_profile_remains_usable_when_new_target_root_is_managed(tmp_path): class RecordingBroker(_RepoPulseBroker): def __init__(self): super().__init__() self.preflight_payloads = [] def enrollment_preflight(self, enrollment, *, secret): self.preflight_payloads.append(dict(enrollment)) return super().enrollment_preflight(enrollment, secret=secret) broker = RecordingBroker() with _session(tmp_path) as session: _configure(session) set_secret(session, "REPOPULSE_GITEA_TOKEN", "opaque-gitea-token") EnrollmentPlane.set_trust_config( session, _trust().model_copy(update={ "trusted_appdata_root": "/var/lib/docker/volumes/itworx-managed-appdata/_data", "managed_appdata_backup_acknowledged": True, }), ) result = _plane(broker=broker, npm=_RepoPulseNpm()).preflight(session, _repopulse_plan()) assert result["status"] == "ready" assert "trusted_appdata_root_mismatch" not in result["blockers"] assert broker.preflight_payloads[0]["trusted_appdata_root"] == "/srv/example/appdata" def test_repopulse_enrollment_seals_exact_runtime_values_without_exposing_secrets(tmp_path): with _session(tmp_path) as session: _configure(session) gitea_token = "existing-encrypted-gitea-token" set_secret(session, "REPOPULSE_GITEA_TOKEN", gitea_token) npm = _RepoPulseNpm() broker = _RepoPulseBroker() result = _plane(broker=broker, npm=npm).enroll( session, _repopulse_plan(), idempotency_key="enroll-repopulse-0001", ) assert result["enrollment"]["status"] == "ready" assert npm.validated_ids == [42] assert npm.created_host["access_list_id"] == 42 assert broker.runtime_values["REPOPULSE_GITEA_URL"] == "https://gitea.example.com" assert broker.runtime_values["REPOPULSE_GITEA_TOKEN"] == gitea_token maintenance_token = get_secret(session, "ENROLL_REPOPULSE_MAINTENANCE_TOKEN") assert maintenance_token and broker.runtime_values["REPOPULSE_MAINTENANCE_TOKEN"] == maintenance_token assert gitea_token not in str(result) assert maintenance_token not in str(result) assert gitea_token not in str(result["job"]["evidence"]) assert maintenance_token not in str(result["job"]["evidence"]) def test_preflight_is_read_only_and_rejects_existing_container(tmp_path): with _session(tmp_path) as session: _configure(session) result = _plane(_CollisionPortainer()).preflight(session, _plan()) assert result["status"] == "blocked" assert "unmanaged_resource_collision" in result["blockers"] assert session.exec(select(Enrollment)).all() == [] assert session.exec(select(EnrollmentJob)).all() == [] def test_preflight_rejects_the_development_secret_fallback(tmp_path, monkeypatch): with _session(tmp_path) as session: _configure(session) monkeypatch.setattr( enrollment_plane_module, "secret_storage_status", lambda: {"status": "ready", "productionReady": False}, ) result = _plane().preflight(session, _plan()) assert result["status"] == "blocked" assert "secret_store" in result["blockers"] assert session.exec(select(Enrollment)).all() == [] def test_capabilities_require_a_live_bootstrap_controller_contract(tmp_path): with _session(tmp_path) as session: _configure(session) result = _plane().capabilities(session) assert result["targetEnrollment"]["status"] == "ready" assert result["controller"]["status"] == "ready" @pytest.mark.parametrize("failure", ["template", "groups", "unreachable"]) def test_capabilities_do_not_advertise_unusable_oidc_profile(tmp_path, failure): class UnavailableAuthentik(_Authentik): def deployment_oidc_template_status(self, *, role_claim): return {"status": "missing_config" if failure == "template" else "ready"} def groups(self): if failure == "groups": return {"success": True, "items": [{"name": "Administrator"}]} if failure == "unreachable": return {"success": False, "message": "SECRET_SENTINEL"} return super().groups() with _session(tmp_path) as session: _configure(session) result = _plane(authentik=UnavailableAuthentik()).capabilities(session) assert result["oidcProvisioning"] == { "status": "unavailable", "blockerCode": "oidc_profile_unavailable", } assert "SECRET_SENTINEL" not in str(result) assert result["targetEnrollment"]["status"] == "ready" assert session.exec(select(Enrollment)).all() == [] assert session.exec(select(Policy)).all() == [] def test_capabilities_advertise_oidc_only_with_template_and_all_role_groups(tmp_path): with _session(tmp_path) as session: _configure(session) assert _plane().capabilities(session)["oidcProvisioning"]["status"] == "ready" @pytest.mark.parametrize('issuer', ['https://auth.example.com/unsupported-path/', 'https://auth.example.com/application/o/bad/path/']) def test_capabilities_refuse_unusable_issuer_paths(tmp_path, issuer): with _session(tmp_path) as session: _configure(session) EnrollmentPlane.set_trust_config(session, _trust().model_copy(update={'oidc_issuer': issuer})) assert _plane().capabilities(session)['oidcProvisioning']['status'] == 'unavailable' def test_enrollment_refuses_another_applications_oidc_issuer(tmp_path): with _session(tmp_path) as session: _configure(session) EnrollmentPlane.set_trust_config( session, _trust().model_copy(update={ "oidc_issuer": "https://auth.example.com/application/o/another-project/", }), ) result = _plane().preflight(session, _plan()) assert result["status"] == "blocked" assert "oidc_issuer_mismatch" in result["blockers"] assert session.exec(select(Enrollment)).all() == [] @pytest.mark.parametrize("configured", [ "https://auth.example.com", "https://auth.example.com/application/o/", "https://auth.example.com/application/o/wsbc-portal", ]) def test_enrollment_seals_its_own_per_provider_issuer(tmp_path, configured): class RuntimeBroker(_Broker): def configure_enrollment_runtime(self, runtime, *, secret): self.issuer = runtime["values"]["OIDC_ISSUER"] return super().configure_enrollment_runtime(runtime, secret=secret) broker = RuntimeBroker() with _session(tmp_path) as session: _configure(session) EnrollmentPlane.set_trust_config(session, _trust().model_copy(update={"oidc_issuer": configured})) result = _plane(broker=broker).enroll(session, _plan(), idempotency_key="own-issuer-test-0001") assert result["enrollment"]["status"] == "ready" assert broker.issuer == "https://auth.example.com/application/o/wsbc-portal/" def test_capabilities_block_when_controller_root_differs_from_operator_trust(tmp_path): class AlternateRootBroker(_Broker): appdata_root = "/var/lib/docker/volumes/itworx-managed-appdata/_data" with _session(tmp_path) as session: _configure(session) result = _plane(broker=AlternateRootBroker()).capabilities(session) assert result["targetEnrollment"]["status"] == "unavailable" assert result["controller"]["status"] == "ready" assert result["controller"]["reason"] == "" assert result["enrollmentReadiness"]["reason"] == "trusted_appdata_root_unavailable" def test_capabilities_treat_roots_as_independent_availability_not_global_selection(tmp_path): legacy_root = "/srv/example/appdata" managed_root = "/var/lib/docker/volumes/itworx-managed-appdata/_data" class MultiRootBroker(_Broker): def enrollment_capabilities(self, *, secret): assert secret == "b" * 64 return { "status": "ready", "trustedAppdataRoot": managed_root, "availableTrustedAppdataRoots": [ {"root": legacy_root, "status": "ready", "reason": ""}, {"root": managed_root, "status": "ready", "reason": ""}, ], "managedStorage": {"status": "ready", "initializationAvailable": False}, "features": { "deploymentApply": True, "deploymentVerify": True, "rollback": True, "exactTargetPolicy": True, "hmacV2": True, "replayProtection": True, "enrollment": True, "targetRegistration": True, "runtimeSecretSealing": True, }, } with _session(tmp_path) as session: _configure(session) plane = _plane(broker=MultiRootBroker()) legacy = plane.capabilities(session) assert legacy["targetEnrollment"]["status"] == "ready" assert legacy["managedStorage"]["status"] == "not_selected" assert legacy["availableTrustedAppdataRoots"] == [ {"root": legacy_root, "status": "ready"}, {"root": managed_root, "status": "ready"}, ] EnrollmentPlane.set_trust_config( session, _trust().model_copy(update={ "trusted_appdata_root": managed_root, "managed_appdata_backup_acknowledged": True, }), ) managed = plane.capabilities(session) assert managed["targetEnrollment"]["status"] == "ready" assert managed["managedStorage"]["status"] == "ready" assert managed["availableTrustedAppdataRoots"] == [ {"root": legacy_root, "status": "ready"}, {"root": managed_root, "status": "ready"}, ] def test_runtime_capabilities_remain_available_when_selected_enrollment_root_is_unavailable(tmp_path): legacy_root = "/srv/example/appdata" managed_root = "/var/lib/docker/volumes/itworx-managed-appdata/_data" class CoreReadyBroker(_Broker): def enrollment_capabilities(self, *, secret): assert secret == "b" * 64 return { "status": "ready", "reason": "", "availableTrustedAppdataRoots": [ {"root": legacy_root, "status": "unavailable", "reason": "legacy_appdata_root_unavailable"}, {"root": managed_root, "status": "unavailable", "reason": "managed_appdata_volume_missing"}, ], "managedStorage": { "status": "unavailable", "reason": "managed_appdata_volume_missing", "initializationAvailable": False, }, "features": { "deploymentApply": True, "deploymentVerify": True, "rollback": True, "exactTargetPolicy": True, "hmacV2": True, "replayProtection": True, "enrollment": False, "targetRegistration": False, "runtimeSecretSealing": False, }, "enrollmentReadiness": { "status": "unavailable", "reason": "legacy_appdata_root_unavailable", }, "registry": {"status": "ready", "registeredProjectCount": 48}, } with _session(tmp_path) as session: _configure(session) result = _plane(broker=CoreReadyBroker()).capabilities(session) assert result["controller"] == {"status": "ready", "reason": ""} assert result["targetEnrollment"]["status"] == "unavailable" assert result["runtimeDeployment"]["status"] == "ready" assert result["rollback"]["status"] == "ready" assert result["verification"]["status"] == "ready" assert result["enrollmentReadiness"] == { "status": "unavailable", "reason": "legacy_appdata_root_unavailable", } assert result["availableTrustedAppdataRoots"] == [ {"root": legacy_root, "status": "unavailable", "reason": "legacy_appdata_root_unavailable"}, {"root": managed_root, "status": "unavailable", "reason": "managed_appdata_volume_missing"}, ] def test_runtime_capabilities_fail_closed_when_controller_feature_attestation_is_missing(tmp_path): class MissingFeaturesBroker(_Broker): def enrollment_capabilities(self, *, secret): return {"status": "ready", "trustedAppdataRoot": self.appdata_root} with _session(tmp_path) as session: _configure(session) result = _plane(broker=MissingFeaturesBroker()).capabilities(session) assert result["controller"]["status"] == "ready" assert result["runtimeDeployment"]["status"] == "unavailable" assert result["rollback"]["status"] == "unavailable" assert result["verification"]["status"] == "unavailable" assert result["targetEnrollment"]["status"] == "unavailable" def test_managed_storage_capability_preserves_only_bounded_controller_reason(tmp_path): managed_root = "/var/lib/docker/volumes/itworx-managed-appdata/_data" class ManagedStorageUnavailableBroker(_Broker): def enrollment_capabilities(self, *, secret): return { "status": "ready", "availableTrustedAppdataRoots": [ {"root": "/srv/example/appdata", "status": "ready"}, {"root": managed_root, "status": "unavailable", "reason": "managed_appdata_volume_missing"}, ], "managedStorage": { "status": "unavailable", "reason": "managed_appdata_volume_missing", "initializationAvailable": False, }, "features": { "deploymentApply": True, "deploymentVerify": True, "rollback": True, "exactTargetPolicy": True, "hmacV2": True, "replayProtection": True, "enrollment": False, "targetRegistration": False, "runtimeSecretSealing": False, }, } with _session(tmp_path) as session: _configure(session) EnrollmentPlane.set_trust_config( session, _trust().model_copy(update={ "trusted_appdata_root": managed_root, "managed_appdata_backup_acknowledged": True, }), ) result = _plane(broker=ManagedStorageUnavailableBroker()).capabilities(session) assert result["managedStorage"]["status"] == "unavailable" assert result["managedStorage"]["reason"] == "managed_appdata_volume_missing" assert result["enrollmentReadiness"]["reason"] == "managed_appdata_volume_missing" def test_capabilities_fail_closed_and_hide_unrecognized_controller_roots(tmp_path): class MalformedRootsBroker(_Broker): def enrollment_capabilities(self, *, secret): return { "status": "ready", "trustedAppdataRoot": "/srv/example/appdata", "availableTrustedAppdataRoots": [ {"root": "/srv/example/appdata", "status": "ready"}, {"root": "/etc/private", "status": "ready", "reason": "sensitive controller path"}, ], } with _session(tmp_path) as session: _configure(session) result = _plane(broker=MalformedRootsBroker()).capabilities(session) assert result["targetEnrollment"]["status"] == "unavailable" assert result["availableTrustedAppdataRoots"] == [ {"root": "/srv/example/appdata", "status": "unavailable"}, {"root": "/var/lib/docker/volumes/itworx-managed-appdata/_data", "status": "unavailable"}, ] assert "/etc/private" not in repr(result) assert "sensitive controller path" not in repr(result) def test_enrollment_preflight_binds_root_to_trust_and_controller_payload(tmp_path): class RecordingBroker(_Broker): def __init__(self): self.preflight_payloads = [] def enrollment_preflight(self, enrollment, *, secret): self.preflight_payloads.append(dict(enrollment)) return super().enrollment_preflight(enrollment, secret=secret) broker = RecordingBroker() with _session(tmp_path) as session: _configure(session) plane = _plane(broker=broker) managed_root = "/var/lib/docker/volumes/itworx-managed-appdata/_data" alternate = _plan(trusted_appdata_root=managed_root) blocked = plane.preflight(session, alternate) assert blocked["status"] == "blocked" assert "trusted_appdata_root_mismatch" in blocked["blockers"] assert broker.preflight_payloads == [] EnrollmentPlane.set_trust_config( session, _trust().model_copy(update={ "trusted_appdata_root": managed_root, "managed_appdata_backup_acknowledged": True, }), ) payload = plane._controller_payload( alternate, enrollment_id="enr-00000000000000000000", ) assert payload["trusted_appdata_root"] == "/var/lib/docker/volumes/itworx-managed-appdata/_data" assert payload["appdata_path"] == "/var/lib/docker/volumes/itworx-managed-appdata/_data/wsbc-portal" assert payload["data_path"] == "/var/lib/docker/volumes/itworx-managed-appdata/_data/wsbc-portal/data" def test_enrollment_creates_write_only_credentials_exact_policies_and_is_idempotent(tmp_path): with _session(tmp_path) as session: _configure(session) plane = _plane() result = plane.enroll(session, _plan(), idempotency_key="enroll-wsbc-portal-0001") assert result["enrollment"]["status"] == "ready" assert result["job"]["status"] == "ready" assert result["enrollment"]["targetSecretRef"] == "ENROLL_WSBC_PORTAL_TARGET_HMAC" assert result["enrollment"]["oidcSecretRef"] == "ENROLL_WSBC_PORTAL_OIDC_CLIENT_SECRET" target_secret = get_secret(session, "ENROLL_WSBC_PORTAL_TARGET_HMAC") oidc_secret = get_secret(session, "ENROLL_WSBC_PORTAL_OIDC_CLIENT_SECRET") assert target_secret and oidc_secret assert target_secret not in str(result) assert oidc_secret not in str(result) policies = session.exec(select(Policy).where(Policy.target == "wsbc-portal")).all() assert {(policy.action, policy.target, policy.manual, policy.voice) for policy in policies} == { ("deployment.apply", "wsbc-portal", "allowed", "blocked"), ("deployment.rollback", "wsbc-portal", "allowed", "blocked"), } assert all(policy.provenance_json["enrollmentId"] == result["enrollment"]["id"] for policy in policies) replay = plane.enroll(session, _plan(), idempotency_key="enroll-wsbc-portal-0001") assert replay["idempotentReplay"] is True assert replay["enrollment"]["id"] == result["enrollment"]["id"] assert len(session.exec(select(Enrollment)).all()) == 1 assert len(session.exec(select(EnrollmentJob)).all()) == 1 def test_enrollment_policy_id_collision_blocks_before_mutation_and_leaves_no_partial_policy(tmp_path): class RecordingBroker(_CompensatingBroker): enroll_calls = 0 def enroll_target(self, *args, **kwargs): self.enroll_calls += 1 return super().enroll_target(*args, **kwargs) with _session(tmp_path) as session: _configure(session) collision = Policy( id="enrollment-wsbc-portal-deployment-rollback", action="deployment.rollback", target="wsbc-portal", voice="blocked", manual="allowed", risk="high", reason="pre-existing exact-target policy", provenance_json={"owner": "pre-existing"}, ) session.add(collision) session.commit() broker = RecordingBroker() result = _compensating_plane(broker).enroll( session, _plan(), idempotency_key="enroll-policy-collision-0001" ) # A pre-existing rollback policy must never leave behind the apply half # of the enrollment-generated pair, even after failure compensation. assert session.get(Policy, "enrollment-wsbc-portal-deployment-apply") is None assert session.get(Policy, collision.id).provenance_json == {"owner": "pre-existing"} assert result["enrollment"]["status"] == "preflight_failed" assert result["job"]["status"] == "preflight_failed" assert "exact_policy_id_collision" in result["job"]["preflight"]["blockers"] assert broker.enroll_calls == 0 assert session.exec(select(Policy).where(Policy.target == "wsbc-portal")).all() == [collision] def test_late_policy_id_collision_rolls_back_the_whole_pair(tmp_path, monkeypatch): class FailedControllerCompensationBroker(_CompensatingBroker): def unenroll_target(self, _target, *, secret): assert secret == "b" * 64 return {"status": "rollback_failed"} collision = Policy( id="enrollment-wsbc-portal-deployment-rollback", action="deployment.rollback", target="wsbc-portal", voice="blocked", manual="allowed", risk="high", reason="concurrent exact-target policy", provenance_json={"owner": "concurrent"}, ) checkpoint = EnrollmentPlane._checkpoint def add_collision_after_preflight(session, enrollment, job, *, stage, evidence): checkpoint(session, enrollment, job, stage=stage, evidence=evidence) if stage == "installing_policy": session.add(collision) session.commit() monkeypatch.setattr( EnrollmentPlane, "_checkpoint", staticmethod(add_collision_after_preflight) ) with _session(tmp_path) as session: _configure(session) result = _compensating_plane(FailedControllerCompensationBroker()).enroll( session, _plan(), idempotency_key="fixture-" + "x" * 32 ) assert result["job"]["preflight"]["status"] == "ready" assert result["enrollment"]["status"] == "compensation_required" assert result["job"]["status"] == "compensation_required" assert result["job"]["evidence"]["compensation"] == { "completed": False, "outcomes": { "oidc": "removed", "proxy": "removed", "controller": "rollback_failed", }, } assert session.get(Policy, "enrollment-wsbc-portal-deployment-apply") is None assert session.get(Policy, collision.id).provenance_json == {"owner": "concurrent"} assert session.exec(select(Policy).where(Policy.target == "wsbc-portal")).all() == [collision] def test_final_ready_commit_failure_rolls_back_the_policy_pair(tmp_path): engine = create_engine(f"sqlite:///{tmp_path / 'enrollment.db'}") SQLModel.metadata.create_all(engine) with Session(engine) as session: _configure(session) plane = _plane() commit = session.commit def fail_after_policy_pair_is_staged(): ready = session.exec( select(Enrollment).where(Enrollment.status == "ready") ).first() policies = session.exec( select(Policy).where(Policy.target == "wsbc-portal") ).all() if ready is not None and {policy.action for policy in policies} == { "deployment.apply", "deployment.rollback", }: raise RuntimeError("simulated final enrollment commit failure") commit() session.commit = fail_after_policy_pair_is_staged with pytest.raises(RuntimeError, match="final enrollment commit failure"): plane.enroll(session, _plan(), idempotency_key="enroll-final-commit-failure-0001") # A request-scoped MCP session is closed when the operation raises; prove # the pending pair was not committed by reading through a fresh session. with Session(engine) as verification: enrollment = verification.exec(select(Enrollment)).one() job = verification.exec(select(EnrollmentJob)).one() assert enrollment.status == "provisioning" assert job.stage == "installing_policy" assert verification.exec( select(Policy).where(Policy.target == "wsbc-portal") ).all() == [] def test_concurrent_policy_id_collision_during_flush_rolls_back_pending_pair(tmp_path): collision = Policy( id="enrollment-wsbc-portal-deployment-rollback", action="deployment.rollback", target="wsbc-portal", voice="blocked", manual="allowed", risk="high", reason="concurrent exact-target policy", provenance_json={"owner": "concurrent"}, ) with _session(tmp_path) as session: _configure(session) flush = session.flush inserted = False def flush_with_concurrent_collision(*args, **kwargs): nonlocal inserted staged_ids = { policy.id for policy in session.new if isinstance(policy, Policy) } if not inserted and staged_ids == { "enrollment-wsbc-portal-deployment-apply", "enrollment-wsbc-portal-deployment-rollback", }: inserted = True with Session(session.get_bind()) as concurrent_session: concurrent_session.add(collision) concurrent_session.commit() return flush(*args, **kwargs) session.flush = flush_with_concurrent_collision result = _compensating_plane().enroll( session, _plan(), idempotency_key="enroll-policy-concurrent-0001" ) assert inserted is True assert result["job"]["preflight"]["status"] == "ready" assert result["enrollment"]["status"] == "failed" assert session.get(Policy, "enrollment-wsbc-portal-deployment-apply") is None concurrent_policy = session.get( Policy, "enrollment-wsbc-portal-deployment-rollback" ) assert concurrent_policy is not None assert concurrent_policy.provenance_json == {"owner": "concurrent"} policies = session.exec(select(Policy).where(Policy.target == "wsbc-portal")).all() assert [(policy.id, policy.action, policy.provenance_json) for policy in policies] == [ ( "enrollment-wsbc-portal-deployment-rollback", "deployment.rollback", {"owner": "concurrent"}, ) ] def test_interrupted_external_enrollment_requires_explicit_recovery(tmp_path): class InterruptedBroker(_Broker): def enroll_target(self, _enrollment, *, secret, idempotency_key): assert secret == "b" * 64 assert idempotency_key == "enroll-wsbc-interrupted-0001" raise RuntimeError("controller connection interrupted after request dispatch") with _session(tmp_path) as session: _configure(session) plane = _plane(broker=InterruptedBroker()) with pytest.raises(RuntimeError, match="interrupted"): plane.enroll(session, _plan(), idempotency_key="enroll-wsbc-interrupted-0001") job = session.exec(select(EnrollmentJob)).one() assert job.status == "running" assert job.stage == "registering_target" assert job.evidence_json["intent"]["controller"]["applicationId"] == "wsbc-portal" assert "target_credential" not in str(job.evidence_json) recovered = plane.enroll(session, _plan(), idempotency_key="enroll-wsbc-interrupted-0001") assert recovered["recoveryRequired"] is True assert recovered["job"]["status"] == "recovery_required" assert recovered["enrollment"]["status"] == "recovery_required" def test_preflight_blocks_hostname_outside_root_trust_boundary(tmp_path): with _session(tmp_path) as session: _configure(session) raw = _plan().model_dump() raw["exposure"]["hostname"] = "wsbc.example.net" result = _plane().preflight(session, EnrollmentPlanV1.model_validate(raw)) assert result["status"] == "blocked" assert "hostname_not_allowed" in result["blockers"] def test_preflight_rejects_an_oidc_role_model_outside_root_trust(tmp_path): with _session(tmp_path) as session: _configure(session) raw = _plan().model_dump() raw["authentication"]["roles"]["administrator"] = "TenantAdmin" result = _plane().preflight(session, EnrollmentPlanV1.model_validate(raw)) assert result["status"] == "blocked" assert "oidc_role_model" in result["blockers"] def test_failed_identity_provisioning_compensates_only_created_resources(tmp_path): class CompensatingBroker(_Broker): def unenroll_target(self, target, *, secret): assert target["application_id"] == "wsbc-portal" assert secret == "b" * 64 return {"status": "rolled_back"} class CompensatingNpm(_Npm): def remove_managed_proxy_host(self, *, host_id, ownership_id): assert host_id == 10 assert ownership_id.startswith("enr-") return {"success": True, "status": "removed"} class FailingAuthentik(_Authentik): def ensure_deployment_oidc_application(self, **_kwargs): return {"success": False, "status": "failed"} with _session(tmp_path) as session: _configure(session) result = _plane( broker=CompensatingBroker(), npm=CompensatingNpm(), authentik=FailingAuthentik(), ).enroll(session, _plan(), idempotency_key="enroll-wsbc-compensate-0001") assert result["enrollment"]["status"] == "failed" assert result["job"]["evidence"]["compensation"] == { "completed": True, "outcomes": {"proxy": "removed", "controller": "rolled_back"}, } assert get_secret(session, "ENROLL_WSBC_PORTAL_TARGET_HMAC") is None assert get_secret(session, "ENROLL_WSBC_PORTAL_OIDC_CLIENT_SECRET") is None assert session.exec(select(Policy).where(Policy.target == "wsbc-portal")).all() == []