80320ae698
Close audit findings: anti-spoof client IP for login rate limit, JWT/CORS enforce on startup, SSH host-key verification and sudo via stdin, optional WinRM HTTPS, SSE httpOnly cookie auth, ingest body limit, ILIKE escaping, and HMAC API key hashing with legacy SHA-256 fallback.
170 lines
6.4 KiB
Python
170 lines
6.4 KiB
Python
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_db_pool_size: int = 15
|
||
sac_db_max_overflow: int = 25
|
||
sac_public_url: str = "http://localhost:8000"
|
||
# URL для скачивания RDP bundle с ПК (WinRM). По умолчанию = SAC_PUBLIC_URL.
|
||
sac_agent_bundle_base_url: str = ""
|
||
jwt_secret: str = "change-me-in-production"
|
||
jwt_algorithm: str = "HS256"
|
||
jwt_expire_minutes: int = 60 * 24
|
||
# Fail-fast on weak JWT/CORS defaults (disable only for local dev/tests).
|
||
sac_security_enforce: bool = True
|
||
sac_ingest_max_body_bytes: int = 2_097_152
|
||
|
||
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 = 300
|
||
|
||
# Периодическое сканирование stale heartbeat (proactive host_silence)
|
||
sac_host_silence_scan_enabled: bool = True
|
||
sac_host_silence_scan_interval_minutes: int = 5
|
||
# После ручного закрытия host_silence не открывать снова раньше N часов (auto-close по heartbeat — без паузы).
|
||
sac_host_silence_manual_resolve_cooldown_hours: int = 12
|
||
|
||
# Окно корреляции 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
|
||
|
||
# rule:rdg_session_flap — RD Gateway 302→303 within window
|
||
sac_rdg_flap_window_min_sec: int = 1
|
||
sac_rdg_flap_window_max_sec: int = 10
|
||
sac_rdg_flap_dedup_sec: int = 30
|
||
# Внешние IP HAProxy/RDG-прокси: если external_ip в списке — путь Haproxy-RDG-Comp, иначе RDG-Comp
|
||
sac_rdg_haproxy_external_ips: str = ""
|
||
|
||
# Windows admin for agent qwinsta/logoff (domain-wide)
|
||
sac_win_admin_user: str = ""
|
||
sac_win_admin_password: str = ""
|
||
sac_winrm_use_https: bool = False
|
||
sac_winrm_server_cert_validation: str = "validate"
|
||
|
||
# Linux SSH admin for remote agent update (fallback SSH, phase 5)
|
||
sac_linux_admin_user: str = ""
|
||
sac_linux_admin_password: str = ""
|
||
sac_ssh_known_hosts_file: str = "/opt/security-alert-center/config/ssh_known_hosts"
|
||
sac_ssh_auto_add_host_key: bool = False
|
||
|
||
# Agent updates (mode gpo|sac, recommended versions, WinRM fallback script)
|
||
sac_agent_update_mode: str = "gpo"
|
||
sac_agent_update_fallback_enabled: bool = True
|
||
sac_agent_update_fallback_minutes: int = 15
|
||
sac_agent_recommended_rdp_version: str = ""
|
||
sac_agent_recommended_ssh_version: str = ""
|
||
sac_agent_min_rdp_version: str = ""
|
||
sac_agent_min_ssh_version: str = ""
|
||
sac_win_agent_update_script: str = ""
|
||
sac_agent_rdp_git_repo_url: str = "https://git.kalinamall.ru/PapaTramp/RDP-login-monitor.git"
|
||
sac_agent_ssh_git_repo_url: str = "https://git.kalinamall.ru/PapaTramp/ssh-monitor.git"
|
||
sac_agent_git_branch: str = "main"
|
||
sac_agent_git_cache_dir: str = "/opt/security-alert-center/cache/agent-repos"
|
||
sac_agent_git_cache_ttl_minutes: int = 30
|
||
sac_agent_git_token: str = ""
|
||
|
||
# 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
|
||
sac_login_ip_whitelist: str = ""
|
||
sac_fail2ban_ssh_jail: str = "sshd"
|
||
sac_fail2ban_sync_enabled: bool = False
|
||
sac_fail2ban_ignoreip_file: str = "/etc/fail2ban/jail.d/sac-login-whitelist.local"
|
||
|
||
# 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()
|