fix(security): F30 (Astra P1) — kein bekanntes Admin-Standardpasswort mehr
Vorher: seed_admin.py und docker-compose.yaml enthielten einen festen
Passwort-Fallback (Admin123!) — ein frisches Volume erzeugte ein
nutzbares Konto mit bekanntem Zugang. Auch die laufende Produktion
nutzte diesen Default (im Container verifiziert).
Fix:
- seed_admin.py: Bei NEUER Admin-Anlage ohne gesetztes ADMIN_PASSWORD
bricht der Start in Produktion AB (vor Benutzeranlage); in Dev wird
ein einmaliges Zufallspasswort generiert und ausgegeben. Bestehende
Admin-Accounts werden uebersprungen (kein Passwortgebrauch) — der
naechste Deploy laeuft also auch ohne gesetzte Variable weiter.
- docker-compose.yaml: ${ADMIN_PASSWORD:-Admin123!} -> required
(${ADMIN_PASSWORD:?...}) — kein Default mehr.
- .env.example/.env.docker.example: Default durch CHANGE_ME-Hinweis
ersetzt.
Abnahme (Astra): Ein frisches Volume ohne gesetztes Geheimnis erzeugt
kein nutzbares Konto mit festem Standardpasswort — erfuellt.
This commit is contained in:
+1
-1
@@ -54,7 +54,7 @@ BCRYPT_ROUNDS=12
|
||||
|
||||
# --- Admin user (seeded on first start) --------------------------------------
|
||||
ADMIN_EMAIL=admin@example.com
|
||||
ADMIN_PASSWORD=Admin123!
|
||||
ADMIN_PASSWORD=CHANGE_ME_generate_a_strong_password
|
||||
|
||||
# --- MAIL_ENCRYPTION_KEY (REQUIRED) -------------------------------------------
|
||||
# AES-256 encryption key for mail account passwords (Fernet).
|
||||
|
||||
+1
-1
@@ -134,4 +134,4 @@ API_GIT_BRANCH=main
|
||||
|
||||
# === Admin User (auto-seeded on first start) ===
|
||||
ADMIN_EMAIL=admin@media-on.de
|
||||
ADMIN_PASSWORD=Admin123!
|
||||
ADMIN_PASSWORD=CHANGE_ME_generate_a_strong_password
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ services:
|
||||
SMTP_TLS: ${SMTP_TLS:-true}
|
||||
BCRYPT_ROUNDS: ${BCRYPT_ROUNDS:-12}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@media-on.de}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-Admin123!}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?ADMIN_PASSWORD must be set — no default credentials (Astra F30)}
|
||||
MAIL_ENCRYPTION_KEY: ${MAIL_ENCRYPTION_KEY:?MAIL_ENCRYPTION_KEY is required}
|
||||
volumes:
|
||||
- storage:/data/storage
|
||||
|
||||
+33
-8
@@ -16,20 +16,21 @@ for initial bootstrap on a fresh database.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Ensure app is importable
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app.core.db import get_migration_engine, set_tenant_context
|
||||
from app.core.auth import hash_password
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User, UserTenant
|
||||
from app.models.role import Role
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||
|
||||
from app.core.auth import hash_password
|
||||
from app.core.db import get_migration_engine, set_tenant_context
|
||||
from app.models.role import Role
|
||||
from app.models.tenant import Tenant
|
||||
from app.models.user import User, UserTenant
|
||||
|
||||
|
||||
async def seed():
|
||||
# Use migration engine to bypass RLS for bootstrap
|
||||
@@ -73,10 +74,34 @@ async def seed():
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user is None:
|
||||
# F30 (Astra P1): no known default password on fresh installs.
|
||||
# - production: refuse to create an admin account with a guessable
|
||||
# password — the operator must set ADMIN_PASSWORD.
|
||||
# - non-production (dev/testing): generate a strong random
|
||||
# password ONCE and print it; never fall back to a constant.
|
||||
admin_password = os.environ.get("ADMIN_PASSWORD", "").strip()
|
||||
from app.config import get_settings
|
||||
|
||||
if not admin_password:
|
||||
if get_settings().environment == "production":
|
||||
print(
|
||||
"ERROR: ADMIN_PASSWORD is not set. Refusing to create an admin "
|
||||
"account with a known default password in production. Set the "
|
||||
"ADMIN_PASSWORD environment variable and restart.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise SystemExit(1)
|
||||
import secrets
|
||||
|
||||
admin_password = secrets.token_urlsafe(16)
|
||||
print(
|
||||
"NOTICE: ADMIN_PASSWORD not set — generated a one-time random "
|
||||
f"password for the new admin account: {admin_password}",
|
||||
)
|
||||
user = User(
|
||||
email=os.environ.get("ADMIN_EMAIL", "admin@media-on.de"),
|
||||
name="Administrator",
|
||||
password_hash=hash_password(os.environ.get("ADMIN_PASSWORD", "Admin123!")),
|
||||
password_hash=hash_password(admin_password),
|
||||
is_active=True,
|
||||
is_system_admin=True,
|
||||
preferences={},
|
||||
@@ -95,7 +120,7 @@ async def seed():
|
||||
)
|
||||
db.add(ut)
|
||||
await db.flush()
|
||||
print(f"Created user_tenant link with admin role")
|
||||
print("Created user_tenant link with admin role")
|
||||
else:
|
||||
print(f"User exists: {user.email} (id: {user.id})")
|
||||
# Ensure existing admin has is_system_admin=True
|
||||
|
||||
Reference in New Issue
Block a user