Critical (1-3): - Replace hardcoded admin credentials with secure auto-generation (seed.py) - Enforce SECRET_KEY configuration, fail in production if missing (config.py) - Add Zip Slip and Zip Bomb protection to all ZIP import services High/Medium (4-9): - Add 50MB file size limit and extension whitelist to evidence uploads - Configure CORS origins via environment variable instead of hardcoded - Migrate JWT storage from localStorage to HttpOnly cookies (frontend+backend) - Add rate limiting (5/min) on login endpoint via slowapi - Replace generic dict payloads with Pydantic schemas (mass assignment) Medium (10-17): - Check is_active on login to prevent disabled users from authenticating - Sanitize exception messages in API responses (system, data_sources) - Escape LIKE wildcards in all ilike search filters across 8 routers - Run Docker container as non-root user (appuser) - Make MINIO_SECURE configurable via environment variable - Add password complexity policy (12+ chars, upper/lower/digit/special) - Implement JWT token revocation via in-memory blacklist + reduce TTL to 15min - Replace xml.etree with defusedxml to prevent Billion Laughs attacks Low (18-20): - Add security headers to Nginx (CSP, X-Frame-Options, HSTS-ready, etc.) - Disable Swagger UI/ReDoc/OpenAPI in production - Restrict /health endpoint to internal networks via Nginx ACL Also: rewrite install.sh as interactive wizard for guided deployment, fix test-from-template validation error (technique_id UUID vs MITRE ID)
84 lines
2.7 KiB
Python
84 lines
2.7 KiB
Python
"""
|
|
Seed script — creates the initial admin user if it does not already exist.
|
|
|
|
On first run the admin credentials are generated securely:
|
|
- Username is read from ``ADMIN_USERNAME`` env var (default: ``admin``).
|
|
- Password is read from ``ADMIN_PASSWORD`` env var. When the variable is
|
|
**not set**, a cryptographically random 16-character password is generated
|
|
automatically and printed to the startup logs so the operator can copy it.
|
|
|
|
Usage:
|
|
python -m app.seed
|
|
"""
|
|
|
|
import os
|
|
import secrets
|
|
import string
|
|
|
|
from app.auth import hash_password
|
|
from app.database import SessionLocal
|
|
from app.models.user import User
|
|
|
|
# Characters for auto-generated passwords (alphanumeric + safe symbols)
|
|
_PW_ALPHABET = string.ascii_letters + string.digits + "!@#$%&*-_+"
|
|
|
|
|
|
def _generate_password(length: int = 16) -> str:
|
|
"""Return a cryptographically random password of *length* characters."""
|
|
return "".join(secrets.choice(_PW_ALPHABET) for _ in range(length))
|
|
|
|
|
|
def seed_admin() -> None:
|
|
"""Create the initial admin user when it is missing.
|
|
|
|
Reads ``ADMIN_USERNAME`` and ``ADMIN_PASSWORD`` from the environment.
|
|
If ``ADMIN_PASSWORD`` is empty or unset a secure random password is
|
|
generated and displayed in the logs.
|
|
"""
|
|
db = SessionLocal()
|
|
try:
|
|
admin_username = os.environ.get("ADMIN_USERNAME", "admin").strip() or "admin"
|
|
|
|
existing = db.query(User).filter(User.username == admin_username).first()
|
|
if existing:
|
|
print(f"Admin user '{admin_username}' already exists — skipping.")
|
|
return
|
|
|
|
admin_password = os.environ.get("ADMIN_PASSWORD", "").strip()
|
|
password_was_generated = False
|
|
|
|
if not admin_password:
|
|
admin_password = _generate_password()
|
|
password_was_generated = True
|
|
|
|
admin = User(
|
|
username=admin_username,
|
|
hashed_password=hash_password(admin_password),
|
|
role="admin",
|
|
)
|
|
db.add(admin)
|
|
db.commit()
|
|
|
|
# ── Display credentials in startup logs ──────────────────────
|
|
print()
|
|
print("=" * 60)
|
|
print(" AEGIS — Initial Admin User Created")
|
|
print("=" * 60)
|
|
print(f" Username : {admin_username}")
|
|
if password_was_generated:
|
|
print(f" Password : {admin_password}")
|
|
print()
|
|
print(" ** This password was auto-generated because")
|
|
print(" ADMIN_PASSWORD was not set in the environment. **")
|
|
print(" ** Save it now — it will NOT be shown again. **")
|
|
else:
|
|
print(" Password : (set via ADMIN_PASSWORD env var)")
|
|
print("=" * 60)
|
|
print()
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
seed_admin()
|