feat(admin): Audit-Logging schreibender Admin-Aktionen — Plan-Schritt 5
- app/audit.py: eigener Logger "va.audit" (eigener stdout-Handler -> Journal/Log-Tab). log_admin_action() protokolliert Aktion + Auslöser (SSO-Name oder admin-key). - Verdrahtet in: config_set/config_reset (PUT/DELETE /admin/config), llm_backend_switch (+ _rejected), gateway_restart. - Tests: caplog prüft Audit-Zeilen für config_set und abgelehnten Backend-Switch. - Doku §7.5: Audit-Format im Log-Tab. Schließt den Plan (Admin-gesteuerte LLM-/Gateway-Verwaltung, Schritte 1–5) ab. 167 grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b2576fd465
commit
33d7189418
4 changed files with 119 additions and 7 deletions
|
|
@ -12,6 +12,7 @@ from app.admin_llm import (
|
|||
restart_gateway_detached,
|
||||
switch_backend,
|
||||
)
|
||||
from app.audit import log_admin_action
|
||||
from app.auth import is_admin_user, require_admin, require_admin_or_user
|
||||
from app.config import settings
|
||||
from app.dependencies import get_store
|
||||
|
|
@ -207,18 +208,24 @@ class BackendSwitch(BaseModel):
|
|||
|
||||
|
||||
@router.post("/admin/llm/backend", dependencies=[Depends(require_admin)])
|
||||
async def post_llm_backend(payload: BackendSwitch):
|
||||
async def post_llm_backend(payload: BackendSwitch, request: Request):
|
||||
"""Wechselt das LLM-Backend (Allowlist-validiert, detached). Greift voll erst,
|
||||
wenn das Gateway als systemd-Dienst läuft; sonst muss es manuell neu starten."""
|
||||
try:
|
||||
return await switch_backend(payload.backend, payload.model)
|
||||
result = await switch_backend(payload.backend, payload.model)
|
||||
except LlmControlError as exc:
|
||||
log_admin_action(request, "llm_backend_switch_rejected",
|
||||
backend=payload.backend, model=payload.model, error=str(exc))
|
||||
raise HTTPException(status_code=422, detail=str(exc))
|
||||
log_admin_action(request, "llm_backend_switch",
|
||||
backend=payload.backend, model=payload.model)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/admin/gateway/restart", dependencies=[Depends(require_admin)])
|
||||
async def post_gateway_restart():
|
||||
async def post_gateway_restart(request: Request):
|
||||
"""Startet das Gateway (systemd-User-Dienst) neu — losgelöst, Self-Restart-sicher."""
|
||||
log_admin_action(request, "gateway_restart")
|
||||
return restart_gateway_detached()
|
||||
|
||||
|
||||
|
|
@ -317,17 +324,19 @@ async def get_runtime_config():
|
|||
|
||||
|
||||
@router.put("/admin/config/{key}", dependencies=[Depends(require_admin)])
|
||||
async def set_runtime_config(key: str, body: ConfigValue):
|
||||
async def set_runtime_config(key: str, body: ConfigValue, request: Request):
|
||||
"""Setzt eine Laufzeit-Einstellung (wirkt sofort, kein Neustart nötig)."""
|
||||
if key not in RUNTIME_SETTABLE:
|
||||
raise HTTPException(status_code=400, detail=f"Nicht überschreibbar: {key!r}")
|
||||
get_store().set_config_override(key, body.value.strip())
|
||||
value = body.value.strip()
|
||||
get_store().set_config_override(key, value)
|
||||
invalidate_cache()
|
||||
return {"key": key, "value": body.value.strip()}
|
||||
log_admin_action(request, "config_set", key=key, value=value)
|
||||
return {"key": key, "value": value}
|
||||
|
||||
|
||||
@router.delete("/admin/config/{key}", dependencies=[Depends(require_admin)])
|
||||
async def delete_runtime_config(key: str):
|
||||
async def delete_runtime_config(key: str, request: Request):
|
||||
"""Entfernt eine Laufzeit-Einstellung (fällt auf .env-Wert zurück)."""
|
||||
if key not in RUNTIME_SETTABLE:
|
||||
raise HTTPException(status_code=400, detail=f"Nicht überschreibbar: {key!r}")
|
||||
|
|
@ -335,6 +344,7 @@ async def delete_runtime_config(key: str):
|
|||
invalidate_cache()
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail=f"Kein Override für {key!r} gesetzt.")
|
||||
log_admin_action(request, "config_reset", key=key)
|
||||
return {"deleted": key}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue