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:
91
tools/apikey.py
Normal file
91
tools/apikey.py
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user