import os import secrets import warnings from pydantic_settings import BaseSettings # --------------------------------------------------------------------------- # Detect environment: "production" when AEGIS_ENV or common indicators are set # --------------------------------------------------------------------------- _is_production = os.environ.get("AEGIS_ENV", "").lower() == "production" or bool( os.environ.get("SECRET_KEY") # having an explicit SECRET_KEY hints prod ) class Settings(BaseSettings): DATABASE_URL: str = "postgresql://postgres:postgres@postgres:5432/attackdb" # ── Security ────────────────────────────────────────────────────── # SECRET_KEY has NO safe default. In development a random key is # generated at startup (tokens invalidate on restart — acceptable # for local dev). In production it MUST be supplied via env/.env # so tokens survive restarts. SECRET_KEY: str = "" ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 15 # short-lived for security; configurable via env # ── Redis ───────────────────────────────────────────────────────── REDIS_URL: str = "redis://redis:6379/0" # ── CORS ───────────────────────────────────────────────────────── # Comma-separated list of allowed origins, or a JSON array. # In dev this defaults to common local ports; in production set it # to the actual frontend domain(s). CORS_ORIGINS: str = "http://localhost:3000,http://localhost:5173" # ── MinIO / S3 ─────────────────────────────────────────────────── MINIO_ENDPOINT: str = "minio:9000" MINIO_ACCESS_KEY: str = "minioadmin" MINIO_SECRET_KEY: str = "minioadmin" MINIO_BUCKET: str = "evidence" MINIO_SECURE: bool = False # True → use HTTPS to connect to MinIO # ── Re-testing ─────────────────────────────────────────────────── MAX_RETEST_COUNT: int = 3 # maximum automatic retests per original test # ── Jira Integration ──────────────────────────────────────────── JIRA_ENABLED: bool = False JIRA_URL: str = "" JIRA_USERNAME: str = "" JIRA_API_TOKEN: str = "" JIRA_IS_CLOUD: bool = True JIRA_DEFAULT_PROJECT: str = "" JIRA_ISSUE_TYPE_TEST: str = "Task" JIRA_ISSUE_TYPE_CAMPAIGN: str = "Epic" # ── Tempo Integration ───────────────────────────────────────────── TEMPO_ENABLED: bool = False TEMPO_API_TOKEN: str = "" TEMPO_DEFAULT_WORK_TYPE: str = "Red Team" # ── Reporting ───────────────────────────────────────────────────── REPORT_TEMPLATES_DIR: str = "app/templates/reports" REPORT_OUTPUT_DIR: str = "/tmp/aegis_reports" COMPANY_NAME: str = "Organization" COMPANY_LOGO_PATH: str = "app/templates/reports/assets/logo.png" # ── Scoring weights (must sum to 100) ──────────────────────────── SCORING_WEIGHT_TESTS: int = 40 SCORING_WEIGHT_DETECTION_RULES: int = 20 SCORING_WEIGHT_D3FEND: int = 15 SCORING_WEIGHT_FRESHNESS: int = 15 SCORING_WEIGHT_PLATFORM_DIVERSITY: int = 10 class Config: env_file = ".env" settings = Settings() # --------------------------------------------------------------------------- # Post-init validation for SECRET_KEY # --------------------------------------------------------------------------- _UNSAFE_SECRETS = { "", "change-me-in-production", "change-me-in-production-use-a-long-random-string", } if settings.SECRET_KEY in _UNSAFE_SECRETS: if _is_production: raise RuntimeError( "CRITICAL: SECRET_KEY is not configured. " "Set a strong random value (>= 32 chars) via the SECRET_KEY " "environment variable or in your .env file before running in " "production. Example: openssl rand -hex 32" ) # Development: auto-generate an ephemeral key and warn settings.SECRET_KEY = secrets.token_hex(32) warnings.warn( "SECRET_KEY was not set — using an auto-generated ephemeral key. " "JWT tokens will be invalidated on every restart. " "Set SECRET_KEY in your environment for persistent sessions.", stacklevel=2, )