Add Redis 7 to Docker Compose with healthcheck and persistence, separate logical DBs for blacklist and cache, singleton redis client helpers, and unit tests with fakeredis.
126 lines
6.0 KiB
Python
126 lines
6.0 KiB
Python
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"
|
|
|
|
|
|
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"
|
|
# Logical DB indices on the same Redis instance (PATH in URL is overridden).
|
|
REDIS_TOKEN_BLACKLIST_DB: int = 1
|
|
REDIS_CACHE_DB: int = 2
|
|
|
|
# ── 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"
|
|
|
|
# ── OSINT / Intelligence ────────────────────────────────────────
|
|
NVD_API_KEY: str = "" # optional; increases NVD rate limit from 5/30s to 50/30s
|
|
STALE_THRESHOLD_DAYS: int = 365 # days before coverage is considered stale
|
|
|
|
# ── 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,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SEC-002: Reject default credentials in production
|
|
# ---------------------------------------------------------------------------
|
|
if _is_production:
|
|
_DEFAULT_CREDS = {
|
|
("MINIO_ACCESS_KEY", settings.MINIO_ACCESS_KEY, "minioadmin"),
|
|
("MINIO_SECRET_KEY", settings.MINIO_SECRET_KEY, "minioadmin"),
|
|
}
|
|
for name, current, default in _DEFAULT_CREDS:
|
|
if current == default:
|
|
raise RuntimeError(
|
|
f"CRITICAL: {name} is using the default value '{default}'. "
|
|
f"Set a strong value via the {name} environment variable "
|
|
f"before running in production."
|
|
)
|