import os from functools import lru_cache from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict ROOT = Path(__file__).resolve().parents[2] SCHEMA_PATH = ROOT / "schemas" / "event-schema-v1.json" DEFAULT_CONFIG_FILE = Path("/opt/security-alert-center/config/sac-api.env") def _resolve_config_files() -> tuple[str, ...]: """Файлы конфигурации для pydantic (корректный разбор кавычек и URL).""" candidates: list[Path] = [] explicit = os.environ.get("SAC_CONFIG_FILE", "").strip() if explicit: candidates.append(Path(explicit)) else: candidates.append(DEFAULT_CONFIG_FILE) candidates.append(Path(".env")) return tuple(str(p) for p in candidates if p.is_file()) def _settings_config() -> SettingsConfigDict: files = _resolve_config_files() return SettingsConfigDict( env_file=files if files else None, env_file_encoding="utf-8", extra="ignore", ) class Settings(BaseSettings): model_config = _settings_config() database_url: str = "postgresql+psycopg2://sac:sac@localhost:5432/sac" sac_public_url: str = "http://localhost:8000" jwt_secret: str = "change-me-in-production" jwt_algorithm: str = "HS256" jwt_expire_minutes: int = 60 * 24 sac_bootstrap_api_key: str = "" sac_admin_username: str = "admin" sac_admin_password: str = "" event_schema_path: str = str(SCHEMA_PATH) cors_origins: str = "*" telegram_enabled: bool = False telegram_bot_token: str = "" telegram_chat_id: str = "" telegram_min_severity: str = "high" webhook_enabled: bool = False webhook_url: str = "" webhook_secret_header: str = "" webhook_secret: str = "" webhook_min_severity: str = "high" smtp_enabled: bool = False smtp_host: str = "" smtp_port: int = 587 smtp_user: str = "" smtp_password: str = "" smtp_from: str = "" smtp_to: str = "" smtp_starttls: bool = True smtp_ssl: bool = False smtp_min_severity: str = "high" notify_min_severity: str = "warning" notify_channels: str = "telegram,webhook,email" # F-NOT-03: cooldown исходящих оповещений (dedup_key / fingerprint) sac_notify_cooldown_enabled: bool = True sac_notify_event_cooldown_sec: int = 90 sac_notify_problem_cooldown_sec: int = 300 # F-NOT-05: суточные отчёты из агрегации событий SAC (для exclusive / без отчёта агента) sac_daily_report_enabled: bool = True sac_daily_report_hour: int = 9 sac_daily_report_timezone: str = "Europe/Moscow" sac_daily_report_skip_if_agent_sent: bool = True sac_daily_report_require_activity: bool = True # Порог «живости» агента sac_heartbeat_stale_minutes: int = 780 # Периодическое сканирование stale heartbeat (proactive host_silence) sac_host_silence_scan_enabled: bool = True sac_host_silence_scan_interval_minutes: int = 5 # Окно корреляции Problems: host + type + rule в одном open Problem sac_problem_correlation_window_minutes: int = 60 # rule:brute_force_burst — ssh.login.failed / rdp.login.failed sac_brute_force_window_minutes: int = 15 sac_brute_force_threshold: int = 30 # rule:privilege_spike — privilege.sudo.command sac_privilege_spike_window_minutes: int = 10 sac_privilege_spike_threshold: int = 10 # Retention (app.jobs.retention / systemd timer) sac_events_retention_days: int = 90 sac_problems_retention_days: int = 180 # UI login brute-force protection sac_login_max_failures: int = 3 sac_login_failure_window_minutes: int = 15 sac_login_alert_telegram: bool = True # Seaca mobile (FCM push) sac_fcm_enabled: bool = False sac_fcm_project_id: str = "" sac_fcm_service_account_json: str = "" sac_mobile_refresh_expire_days: int = 90 @lru_cache def get_settings() -> Settings: return Settings()