Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Jira — PoC custom field: - Add customfield_10309 (Proof of Concept) to issue fields when creating test tickets so the attack procedure appears in the dedicated Jira field Tempo — blue team exclusion: - Remove blue_team_evaluation from _TEMPO_ACTIVITY_TYPES; blue team time is tracked internally (worklogs) for SLA but never sent to Tempo since blue team has no Jira access Evidence — uploaded_at NULL fix: - Set uploaded_at=datetime.utcnow() explicitly in upload_evidence router; the DB column has no server default so it was saving as NULL Evidence — presigned URL browser access: - Add MINIO_PUBLIC_ENDPOINT setting (config.py, docker-compose.prod.yml) - storage.py uses a dedicated _public_client for presigned URL generation so browsers receive URLs with the publicly accessible hostname instead of the internal Docker service name (minio:9000) - Expose MinIO port 9000 in docker-compose.prod.yml Evidence — Jira attachment: - After upload to MinIO, call jira.add_attachment() to attach the file to the linked Jira ticket (non-fatal; errors are logged and swallowed) Settings — hide Jira/Tempo from blue team: - ProfileSection checks user role; blue_lead and blue_tech do not see the Jira Integration or Tempo Integration personal settings sections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
144 lines
6.9 KiB
Python
144 lines
6.9 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"
|
|
# Public hostname used in presigned URLs returned to browsers.
|
|
# In production set this to <server-ip>:9000 (or a public FQDN) so
|
|
# the browser can reach MinIO directly. Defaults to MINIO_ENDPOINT.
|
|
MINIO_PUBLIC_ENDPOINT: str = ""
|
|
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" # tests (campaign or standalone)
|
|
JIRA_ISSUE_TYPE_CAMPAIGN: str = "Epic" # campaigns (under Initiative)
|
|
|
|
# ── Tempo Integration ─────────────────────────────────────────────
|
|
TEMPO_ENABLED: bool = False
|
|
TEMPO_API_TOKEN: str = ""
|
|
TEMPO_API_VERSION: int = 4
|
|
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"
|
|
|
|
# ── Email / SMTP ──────────────────────────────────────────────────
|
|
SMTP_ENABLED: bool = False
|
|
SMTP_HOST: str = ""
|
|
SMTP_PORT: int = 587
|
|
SMTP_USERNAME: str = ""
|
|
SMTP_PASSWORD: str = ""
|
|
SMTP_FROM_EMAIL: str = "aegis@company.com"
|
|
SMTP_USE_TLS: bool = True
|
|
PLATFORM_URL: str = "http://localhost:5173" # base URL for links in emails
|
|
|
|
# ── Scoring weights (must sum to 100) ────────────────────────────
|
|
SCORING_WEIGHT_TESTS: int = 40
|
|
SCORING_WEIGHT_DETECTION_RULES: int = 25
|
|
SCORING_WEIGHT_D3FEND: int = 15
|
|
SCORING_WEIGHT_RECENCY: int = 10
|
|
SCORING_WEIGHT_SEVERITY: int = 10
|
|
# Legacy env names (mapped in scoring_config_service)
|
|
SCORING_WEIGHT_FRESHNESS: int = 10
|
|
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."
|
|
)
|