from __future__ import annotations import json import os from collections.abc import Mapping from dataclasses import dataclass from typing import Literal from cloud.auth import BearerCredential, EnrollmentCredential EnvironmentName = Literal["local", "test", "production"] SUPPORTED_DATABASE_PREFIXES = ( "sqlite:///", "postgresql://", "postgresql+psycopg://", ) class CloudConfigurationError(ValueError): """Raised when control-plane configuration is unsafe or invalid.""" @dataclass(frozen=True) class CloudControlConfig: environment: EnvironmentName = "local" database_url: str = "sqlite:///cloud/cloud.sqlite3" scheduler_interval_seconds: float = 1.0 lease_reaper_interval_seconds: float = 5.0 lease_duration_seconds: float = 60.0 max_task_attempts: int = 3 allow_insecure_anonymous: bool = False credentials: tuple[BearerCredential, ...] = () enrollment_credentials: tuple[EnrollmentCredential, ...] = () cors_allowed_origins: tuple[str, ...] = () def load_control_config( env: Mapping[str, str] | None = None, ) -> CloudControlConfig: values = os.environ if env is None else env environment = values.get("CLOUD_ENVIRONMENT", "local").strip().lower() if environment not in {"local", "test", "production"}: raise CloudConfigurationError( "CLOUD_ENVIRONMENT must be local, test, or production" ) database_url = values.get( "CLOUD_DATABASE_URL", "sqlite:///cloud/cloud.sqlite3", ).strip() if not database_url.startswith(SUPPORTED_DATABASE_PREFIXES): raise CloudConfigurationError( "CLOUD_DATABASE_URL must use sqlite or postgresql" ) config = CloudControlConfig( environment=environment, # type: ignore[arg-type] database_url=database_url, scheduler_interval_seconds=_positive_float( values, "CLOUD_SCHEDULER_INTERVAL_SECONDS", 1.0, ), lease_reaper_interval_seconds=_positive_float( values, "CLOUD_LEASE_REAPER_INTERVAL_SECONDS", 5.0, ), lease_duration_seconds=_positive_float( values, "CLOUD_LEASE_DURATION_SECONDS", 60.0, ), max_task_attempts=_positive_int( values, "CLOUD_MAX_TASK_ATTEMPTS", 3, ), allow_insecure_anonymous=_parse_bool( values.get("CLOUD_ALLOW_INSECURE_ANONYMOUS"), default=False, ), credentials=( *_parse_credentials(values.get("CLOUD_PUBLIC_CREDENTIALS_JSON")), *_parse_credentials( values.get("CLOUD_HOST_CREDENTIALS_JSON"), require_host_id=True, ), ), enrollment_credentials=_parse_enrollment_credentials( values.get("CLOUD_ENROLLMENT_TOKENS_JSON") ), cors_allowed_origins=_parse_cors_origins( values.get("CLOUD_CONSOLE_CORS_ORIGINS") ), ) validate_control_config(config) return config def validate_control_config(config: CloudControlConfig) -> None: if config.environment == "production" and config.allow_insecure_anonymous: raise CloudConfigurationError( "anonymous access cannot be enabled in production" ) if config.environment == "production" and not config.credentials: raise CloudConfigurationError( "production requires at least one configured bearer credential" ) def _parse_credentials( raw_value: str | None, *, require_host_id: bool = False, ) -> tuple[BearerCredential, ...]: if raw_value is None or not raw_value.strip(): return () try: payload = json.loads(raw_value) if not isinstance(payload, list): raise TypeError credentials: list[BearerCredential] = [] for item in payload: if not isinstance(item, dict): raise TypeError principal_id = item.get("principal_id") token = item.get("token") scopes = item.get("scopes", []) host_id = item.get("host_id") if ( not isinstance(principal_id, str) or not isinstance(token, str) or not isinstance(scopes, list) or not all(isinstance(scope, str) for scope in scopes) or (host_id is not None and not isinstance(host_id, str)) or (require_host_id and not isinstance(host_id, str)) ): raise TypeError credentials.append( BearerCredential( principal_id=principal_id, token=token, scopes=frozenset(scopes), host_id=host_id, ) ) return tuple(credentials) except (TypeError, ValueError, json.JSONDecodeError) as exc: raise CloudConfigurationError( "configured bearer credentials are invalid" ) from exc def _parse_enrollment_credentials( raw_value: str | None, ) -> tuple[EnrollmentCredential, ...]: if raw_value is None or not raw_value.strip(): return () try: payload = json.loads(raw_value) if not isinstance(payload, list): raise TypeError credentials: list[EnrollmentCredential] = [] for item in payload: if not isinstance(item, dict): raise TypeError principal_id = item.get("principal_id") token = item.get("token") if not isinstance(principal_id, str) or not isinstance(token, str): raise TypeError credentials.append( EnrollmentCredential(principal_id=principal_id, token=token) ) return tuple(credentials) except (TypeError, ValueError, json.JSONDecodeError) as exc: raise CloudConfigurationError( "configured enrollment credentials are invalid" ) from exc def _positive_float( values: Mapping[str, str], name: str, default: float, ) -> float: raw_value = values.get(name) if raw_value is None: return default try: value = float(raw_value) except ValueError as exc: raise CloudConfigurationError(f"{name} must be a number") from exc if value <= 0: raise CloudConfigurationError(f"{name} must be greater than zero") return value def _positive_int( values: Mapping[str, str], name: str, default: int, ) -> int: raw_value = values.get(name) if raw_value is None: return default try: value = int(raw_value) except ValueError as exc: raise CloudConfigurationError(f"{name} must be an integer") from exc if value <= 0: raise CloudConfigurationError(f"{name} must be greater than zero") return value def _parse_bool(value: str | None, *, default: bool) -> bool: if value is None: return default normalized = value.strip().lower() if normalized in {"1", "true", "yes", "on", "enabled"}: return True if normalized in {"0", "false", "no", "off", "disabled", ""}: return False raise CloudConfigurationError("boolean configuration value is invalid") def _parse_cors_origins(raw_value: str | None) -> tuple[str, ...]: if raw_value is None or not raw_value.strip(): return () return tuple(origin.strip() for origin in raw_value.split(",") if origin.strip())