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>
92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""CLI lifecycle chei API (CORE securitate).
|
|
|
|
Emitere/rotire/revocare/listare chei per cont. Adminul ruleaza pe masina gateway
|
|
— nicio suprafata HTTP de admin. Cheia in clar se afiseaza O SINGURA DATA la
|
|
creare/rotire; in DB traieste doar hash-ul SHA-256.
|
|
|
|
Utilizare:
|
|
python -m tools.apikey create --account 1
|
|
python -m tools.apikey rotate --account 1
|
|
python -m tools.apikey revoke --key-id 3
|
|
python -m tools.apikey list [--account 1]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
from app.auth import create_api_key, list_keys, revoke_api_key, rotate_api_key
|
|
from app.db import get_connection, init_db
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Lifecycle chei API gateway RAR AUTOPASS")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
|
|
p_create = sub.add_parser("create", help="emite o cheie noua pentru cont")
|
|
p_create.add_argument("--account", type=int, required=True, help="account_id")
|
|
|
|
p_rotate = sub.add_parser("rotate", help="revoca cheile active ale contului + emite una noua")
|
|
p_rotate.add_argument("--account", type=int, required=True, help="account_id")
|
|
|
|
p_revoke = sub.add_parser("revoke", help="revoca o cheie dupa id")
|
|
p_revoke.add_argument("--key-id", type=int, required=True, help="api_keys.id")
|
|
|
|
p_list = sub.add_parser("list", help="listeaza chei (fara hash)")
|
|
p_list.add_argument("--account", type=int, default=None, help="filtreaza pe cont")
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
init_db() # asigura schema (api_keys) + cont default
|
|
conn = get_connection()
|
|
try:
|
|
if args.cmd == "create":
|
|
try:
|
|
key = create_api_key(conn, args.account)
|
|
except ValueError as exc:
|
|
print(f"eroare: {exc}", file=sys.stderr)
|
|
return 2
|
|
print(f"Cheie creata pentru cont {args.account} (pastreaz-o, nu se mai afiseaza):")
|
|
print(key)
|
|
return 0
|
|
|
|
if args.cmd == "rotate":
|
|
try:
|
|
key = rotate_api_key(conn, args.account)
|
|
except ValueError as exc:
|
|
print(f"eroare: {exc}", file=sys.stderr)
|
|
return 2
|
|
print(f"Chei vechi revocate. Cheie noua pentru cont {args.account}:")
|
|
print(key)
|
|
return 0
|
|
|
|
if args.cmd == "revoke":
|
|
ok = revoke_api_key(conn, args.key_id)
|
|
if ok:
|
|
print(f"Cheie {args.key_id} revocata.")
|
|
return 0
|
|
print(f"Cheie {args.key_id} inexistenta sau deja revocata.", file=sys.stderr)
|
|
return 1
|
|
|
|
if args.cmd == "list":
|
|
rows = list_keys(conn, args.account)
|
|
if not rows:
|
|
print("(nicio cheie)")
|
|
return 0
|
|
print(f"{'id':>4} {'cont':>4} {'activa':>6} {'creata':<20} revocata")
|
|
for r in rows:
|
|
print(
|
|
f"{r['id']:>4} {r['account_id']:>4} {('da' if r['active'] else 'nu'):>6} "
|
|
f"{(r['created_at'] or ''):<20} {r['revoked_at'] or ''}"
|
|
)
|
|
return 0
|
|
finally:
|
|
conn.close()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|