Release / verify (push) Canceled after 0s
CI / checks (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / backend (push) Canceled after 0s
Release / test (push) Canceled after 0s
Release / publish (backend) (push) Canceled after 0s
Release / publish (frontend) (push) Canceled after 0s
Release / publish (proxy) (push) Canceled after 0s
Release / publish (updater) (push) Canceled after 0s
Automatisierung und Sicherheitslage. Ende-zu-Ende gegen einen Test-Stack und drei Test-Firewalls geprüft, siehe Handbuch „Sicherheitslage“ und „Automatisierung“. - **API-Tokens** (System › API-Tokens): persönliche Tokens für Skripte, CI, Prometheus und KI-Assistenten, Bereiche *Lesen*, *Ausrollen*, *Admin*. Nur als Hash gespeichert, einmal angezeigt, widerrufbar, mit Ablaufdatum; Anlegen verlangt das Passwort. Ein Token überschreitet nie die Rechte seines Besitzers. Tokens verwalten und das eigene Konto geht nur in einer normalen Anmeldung. Geheimnisse (Roh-Backups, Benachrichtigungseinstellungen) liest nur der Bereich *Admin*. Das Audit nennt das Token. Neue Tabelle `api_tokens` (Migration `0008`). - **Prometheus-Metriken** unter `GET /api/metrics` (Host erreichbar, Last, Speicher, Platte, CARP-Rolle, offene Aktionen und Probleme, Sicherheitswert), nur aus der Datenbank, ohne Anfragen an die Firewalls. - **MCP-Endpunkt** `POST /api/mcp` (nur lesend) für KI-Assistenten: Flottenübersicht, Hosts, Auslastung, CARP-Ereignisse, Audit, Suche und ein Leser für jeden GET-Endpunkt, mit den Rechten des Tokens. - **OpenAPI-Schema** jetzt unter `/api/openapi.json` (bisher unter `/openapi.json`, vom Proxy nicht erreichbar); die Swagger-/ReDoc-Seiten entfallen (ihre CDN-Skripte verbietet die CSP). - **Sicherheitslage** (Monitoring › Sicherheitslage): Härtungs- und Regel-Hygiene-Prüfungen je Firewall mit Wert 0–100: Allow-all am WAN, HTTPS, SSH, SNMP, verdeckte/doppelte/unbeschriftete Regeln, Zeitserver, externe Logs, Firmware, Backup-Alter. Liest die Konfiguration nur lesend, speichert sie nicht, alle 6 Stunden und auf Knopfdruck. Neue Tabelle `compliance_results` (Migration `0009`). - Behoben: Änderungen am Benutzer in einer Anfrage (z. B. 2FA aktivieren) gingen verloren, wenn die Middleware den Benutzer aus einer anderen Datenbank-Sitzung lieferte; er wird jetzt in der Sitzung der Anfrage neu geladen.
68 lines
2.6 KiB
Python
68 lines
2.6 KiB
Python
"""Recovery: give an account a new random password and print it once.
|
|
|
|
For a fresh install whose generated first-start password scrolled out of the
|
|
log, and for an admin who forgot theirs. Needs shell access to the server,
|
|
which is the trust boundary for this tool anyway (same as app.reset_2fa).
|
|
All sessions and API tokens of the account are ended. Normally called through the host
|
|
CLI:
|
|
|
|
./opnexus password [username]
|
|
|
|
or directly:
|
|
|
|
docker compose exec backend python -m app.reset_password [username]
|
|
|
|
Without a username the configured ADMIN_USERNAME is used; if that account does
|
|
not exist and there is exactly one admin, that one.
|
|
"""
|
|
import asyncio
|
|
import secrets
|
|
import sys
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.config import settings
|
|
from app.database import SessionLocal
|
|
from app.models.user import AdminUser
|
|
from app.services import api_token_service, auth_service
|
|
|
|
|
|
async def main(username: str | None) -> int:
|
|
async with SessionLocal() as db:
|
|
if username is None:
|
|
username = settings.admin_username
|
|
user = (await db.execute(select(AdminUser).where(AdminUser.username == username))).scalar_one_or_none()
|
|
if user is None:
|
|
admins = (await db.execute(select(AdminUser).where(AdminUser.role == "admin"))).scalars().all()
|
|
if len(admins) != 1:
|
|
names = ", ".join(sorted(a.username for a in admins)) or "-"
|
|
print(f"No account named '{username}'. Pass the username (admins: {names}).", file=sys.stderr)
|
|
return 1
|
|
user = admins[0]
|
|
else:
|
|
user = (await db.execute(select(AdminUser).where(AdminUser.username == username))).scalar_one_or_none()
|
|
if user is None:
|
|
print(f"No such user: {username}", file=sys.stderr)
|
|
return 1
|
|
|
|
password = secrets.token_urlsafe(18)
|
|
user.password_hash = await auth_service.hash_password_async(password)
|
|
await auth_service.revoke_sessions(db, user.id, commit=False)
|
|
await api_token_service.revoke_all(db, user.id, commit=False)
|
|
await db.commit()
|
|
name = user.username
|
|
has_totp = user.totp_enabled
|
|
|
|
print(f"Username: {name}")
|
|
print(f"Password: {password}")
|
|
if has_totp:
|
|
print("Two-factor sign-in is still on (lost the code? python -m app.reset_2fa).", file=sys.stderr)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) > 2:
|
|
print("usage: python -m app.reset_password [username]", file=sys.stderr)
|
|
sys.exit(2)
|
|
sys.exit(asyncio.run(main(sys.argv[1] if len(sys.argv) == 2 else None)))
|