feat(securitate-CORE): redactare creds + auth API-key per cont

Redactare:
- handler RequestValidationError dropeaza input/ctx din 422 (vectorul de
  scurgere a rar_credentials.password pe /v1/prezentari); pastreaza type/loc/msg
- app/security.py: scrub/scrub_text + CredentialRedactingFilter pe root+uvicorn
- models.py: password cu repr=False

Auth API-key:
- app/auth.py: hash SHA-256 in api_keys (cheia in clar emisa o singura data),
  header X-API-Key / Authorization: Bearer, dependency resolve_account_id
- enforcement pe flag AUTOPASS_require_api_key (prod on->401, dev off->cont
  default id=1; cheie prezenta invalida->401 mereu)
- account_id real curge din cheie in ingestie + mapare
- tools/apikey.py: CLI create/rotate/revoke/list (fara endpoint HTTP admin)

16 teste noi (tests/test_security.py). 85 pass total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-15 20:02:07 +00:00
parent a6df3b636f
commit c17c1aa4f4
9 changed files with 647 additions and 13 deletions

View File

@@ -11,24 +11,41 @@ from __future__ import annotations
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse, PlainTextResponse
from . import __version__
from .api.v1.router import router as api_v1_router
from .config import get_settings
from .db import get_connection, init_db, queue_depth, read_heartbeat
from .security import install_log_redaction
from .web.routes import router as web_router
@asynccontextmanager
async def lifespan(app: FastAPI):
install_log_redaction()
init_db()
yield
app = FastAPI(title="Gateway RAR AUTOPASS", version=__version__, lifespan=lifespan)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""422 fara echo de credentiale.
Pydantic include implicit `input` (+ uneori `ctx`) in fiecare eroare — pe
/v1/prezentari asta ar reflecta inapoi `rar_credentials.password`. Pastram
type/loc/msg (clientul stie ce camp e gresit) si DROP-am input/ctx. Defense
in depth pe TOATE rutele, nu doar prezentari.
"""
cleaned = [{"type": e.get("type"), "loc": e.get("loc"), "msg": e.get("msg")} for e in exc.errors()]
return JSONResponse(status_code=422, content={"detail": cleaned})
app.include_router(api_v1_router)
app.include_router(web_router)