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