Přeskočit obsah

S3 Key Pool — Backend dokumentace

Systém pro správu a distribuci S3 přihlašovacích údajů firmám. Admin nahraje dávku klíčů do S3 bucketu, backend je automaticky importuje a přiřadí každé firmě jeden klíč. Firma pak přes API získá vlastní S3 credentials.


Obsah

  1. Jak to funguje
  2. Databázové tabulky
  3. Formát dávkového souboru
  4. API endpointy
  5. Bezpečnostní model
  6. Celery task (automatický import)
  7. Chybové stavy

Jak to funguje

ADMIN                    S3 (s3klic/)              BACKEND DB               FIRMA
  │                           │                        │                       │
  │── nahraj batch_X.json ───►│                        │                       │
  │                           │                        │                       │
  │── POST /admin/s3keys/import ──────────────────��───►│                       │
  │                           │◄── list + download ────│                       │
  │                           │                        │── INSERT keys ────────│
  │                           │◄── delete batch_X.json─│                       │
  │◄── { imported: 3 } ───────│────────────────────────│                       │
  │                           │                        │                       │
  │── POST /admin/s3keys/assign/{company_id} ─────────►│                       │
  │◄── { assigned: true, ico: "12345678" } ────────────│                       │
  │                           │                        │                       │
  │                           │                   GET /storage/credentials ───►│
  │                           │              ◄── JWT check + role check ────────│
  │                           │              ◄── { access_key, secret, ... } ──│

Životní cyklus klíče

IMPORT          PŘIŘAZENÍ         VÝDEJ            REVOKACE
available  ──►  assigned    ──►  (last_accessed)  ──►  revoked
  • available — klíč je ve fondu, čeká na přiřazení
  • assigned — přiřazen konkrétní firmě (identifikováno IČO + company_id)
  • revoked — zneplatněn adminem, firma ho nadále nedostane

Databázové tabulky

s3_key_pool

Sloupec Typ Popis
id UUID PK Interní identifikátor
access_key_id VARCHAR(128) UNIQUE MinIO/S3 access key (veřejná část)
secret_access_key_enc TEXT Secret klíč zašifrovaný Fernetem (AES-128-CBC)
endpoint_url VARCHAR(255) S3 endpoint (vždy https://s3.avaxis.cz)
bucket VARCHAR(255) Přiřazený bucket (nepovinný)
status VARCHAR(20) available | assigned | revoked
assigned_company_id UUID FK→companies Interní ID firmy
assigned_company_ico VARCHAR(8) IČO firmy — uloženo při přiřazení (denormalizováno pro audit)
assigned_at TIMESTAMPTZ Kdy byl klíč přiřazen
last_accessed_at TIMESTAMPTZ Kdy naposledy firma klíč vyžádala
imported_at TIMESTAMPTZ Kdy byl importován
source_file VARCHAR(255) Ze kterého souboru pochází (audit stopa)

Poznámka k šifrování: secret_access_key_enc je šifrován symetrickým klíčem (Fernet / AES-128-CBC-HMAC), uloženým v env proměnné S3_KEY_ENCRYPTION_KEY. V DB nikdy není plaintext. IČO je záměrně denormalizováno — i po smazání záznamu firmy zůstane audit stopa čitelná.

s3_key_access_log

Každý pokus o výdej klíče (úspěšný i neúspěšný) se loguje.

Sloupec Typ Popis
id UUID PK
key_pool_id UUID FK→s3_key_pool Ke kterému klíči (NULL při selhání před lookup)
company_ico VARCHAR(8) IČO firmy
accessed_by_user_id UUID FK→users Kdo klíč vyžádal
accessed_at TIMESTAMPTZ Čas přístupu
ip_address VARCHAR(45) IP adresa žadatele (IPv4 i IPv6)
user_agent VARCHAR(512) User-Agent hlavička
success BOOLEAN true = klíč byl vydán, false = přístup odepřen
fail_reason VARCHAR(255) Důvod odmítnutí: insufficient_role | no_key_assigned | decrypt_error

Rozšíření tabulky company_roles

ALTER TABLE company_roles
    ADD COLUMN can_access_s3_credentials BOOLEAN DEFAULT FALSE;

Umožňuje delegovat přístup ke credentials na granulární roli (viz sekce Bezpečnost).


Formát dávkového souboru

Admin nahraje JSON soubor do prefixu s3klic/ v systémovém bucketu (avaxis). Soubor může obsahovat jeden nebo více klíčů.

Příklad s3klic/batch_2026Q2.json:

[
  {
    "access_key_id":     "AVXKEY001",
    "secret_access_key": "supersecret001",
    "endpoint":          "https://s3.avaxis.cz",
    "bucket":            "avaxis-firma-12345678"
  },
  {
    "access_key_id":     "AVXKEY002",
    "secret_access_key": "supersecret002",
    "endpoint":          "https://s3.avaxis.cz",
    "bucket":            "avaxis-firma-87654321"
  }
]
Pole Povinné Popis
access_key_id MinIO access key ID
secret_access_key MinIO secret key
endpoint S3 endpoint (výchozí: S3_ENDPOINT z env)
bucket Bucket přiřazený tomuto klíči

Pravidla importu: - Soubor je po úspěšném importu smazán ze S3 (atomicky — smazání proběhne až po DB commitu) - Duplicitní access_key_id se přeskočí (nezpůsobí chybu) - Existující klíče importem nelze přepsat — secret se neaktualizuje


API endpointy

Základní URL: https://api.avaxis.cz (dev: http://192.168.1.55:8000)


Uživatelský endpoint

GET /storage/credentials

Vrátí S3 přihlašovací údaje přiřazené firmě přihlášeného uživatele.

Autentizace: Bearer token (JWT)

Požadovaná oprávnění (stačí jedno): - system_role = super_admin - system_role = company_admin - Vlastní role s can_access_s3_credentials = true

Response 200:

{
  "access_key_id":     "AVXKEY001",
  "secret_access_key": "supersecret001",
  "endpoint_url":      "https://s3.avaxis.cz",
  "bucket":            "avaxis-firma-12345678",
  "assigned_at":       "2026-04-26T08:22:43Z"
}

Chybové odpovědi:

HTTP Důvod
401 Chybí nebo neplatný Bearer token
403 Uživatel nemá požadovanou roli
404 Firmě nebyl přiřazen žádný S3 klíč

Každý přístup (úspěšný i neúspěšný) je zaznamenán do s3_key_access_log.


Admin endpointy

Všechny admin endpointy vyžadují system_role = super_admin.


POST /admin/s3keys/import

Okamžitě importuje klíče ze S3 (bez čekání na Celery Beat). Lze spustit opakovaně — idempotentní.

Response 200:

{
  "imported":        3,
  "skipped":         1,
  "files_processed": ["s3klic/batch_2026Q2.json"],
  "files_deleted":   ["s3klic/batch_2026Q2.json"]
}


GET /admin/s3keys/pool

Přehled celého fondu klíčů.

Response 200:

{
  "available": 2,
  "assigned":  1,
  "revoked":   0,
  "keys": [
    {
      "id":                    "882809f6-...",
      "access_key_id":         "AVXKEY001",
      "endpoint_url":          "https://s3.avaxis.cz",
      "bucket":                "avaxis-firma-12345678",
      "status":                "assigned",
      "assigned_company_ico":  "12345678",
      "assigned_at":           "2026-04-26T08:22:43Z",
      "last_accessed_at":      "2026-04-26T09:10:00Z",
      "imported_at":           "2026-04-26T08:00:00Z",
      "source_file":           "s3klic/batch_2026Q2.json"
    }
  ]
}


POST /admin/s3keys/assign/{company_id}

Přiřadí první volný klíč z poolu firmě. IČO firmy se uloží přímo do záznamu klíče.

Path param: company_id — UUID firmy

Response 200:

{
  "assigned":     true,
  "access_key_id": "AVXKEY001",
  "company_ico":  "12345678",
  "assigned_at":  "2026-04-26T08:22:43Z"
}

Chybové odpovědi:

HTTP Důvod
404 Firma s daným ID neexistuje
409 Firma již má přiřazený klíč
503 Fond je prázdný — nahrajte novou dávku do s3klic/

POST /admin/s3keys/{key_id}/revoke

Zneplatní klíč. Firma ho nadále nedostane přes /storage/credentials.

Path param: key_id — UUID záznamu v s3_key_pool

Response 200:

{
  "revoked":       true,
  "access_key_id": "AVXKEY004",
  "status":        "revoked"
}


GET /admin/s3keys/{key_id}/access-log

Historie přístupů ke konkrétnímu klíči (max. 200 posledních záznamů, sestupně).

Response 200:

[
  {
    "id":                    "abc123...",
    "company_ico":           "12345678",
    "accessed_by_user_id":   "user-uuid...",
    "accessed_at":           "2026-04-26T09:10:00Z",
    "ip_address":            "10.0.1.5",
    "success":               true,
    "fail_reason":           null
  },
  {
    "id":                    "def456...",
    "company_ico":           "12345678",
    "accessed_by_user_id":   "user-uuid-2...",
    "accessed_at":           "2026-04-26T08:55:00Z",
    "ip_address":            "1.2.3.4",
    "success":               false,
    "fail_reason":           "insufficient_role"
  }
]


Bezpečnostní model

Tři vrstvy kontroly při výdeji klíče

1. JWT ověření
   Bearer token → platný + neexpirovaný
2. Membership check
   user_company_memberships → user patří do firmy která klíč vlastní
   AND is_active = true
3. Role check (stačí jedno)
   system_role IN ('super_admin', 'company_admin')
   OR company_roles.can_access_s3_credentials = true
4. Výdej + audit log

Pokud jakákoliv vrstva selže → 403 Forbidden bez detailu o existenci klíče.

Šifrování v databázi

  • Secret klíč je šifrován pomocí Fernet (AES-128-CBC + HMAC-SHA256)
  • Šifrovací klíč je uložen pouze v env proměnné S3_KEY_ENCRYPTION_KEY
  • V databázi nikdy není plaintext
  • Env proměnná musí být při migraci na produkci rotována oproti dev hodnotě

Granulární role can_access_s3_credentials

Pokud chcete udělit přístup k S3 credentials specifické roli (např. IT správce), nastavte příznak na roli:

UPDATE company_roles
SET    can_access_s3_credentials = true
WHERE  id = '<role-uuid>';

company_admin a super_admin mají přístup automaticky.


Celery task

Automatický import běží každých 12 hodin přes Celery Beat.

Spuštění workeru (dev):

cd ~/avax-platform/backend
source .venv/bin/activate

# Worker
celery -A app.workers.celery_app worker --loglevel=info

# Beat scheduler (v druhém terminálu)
celery -A app.workers.celery_app beat --loglevel=info

Ruční spuštění tasku:

celery -A app.workers.celery_app call s3keys.import_from_bucket

Beat schedule (definováno v app/workers/celery_app.py):

beat_schedule = {
    "import-s3-keys-every-12h": {
        "task":     "s3keys.import_from_bucket",
        "schedule": crontab(hour="*/12", minute="0"),
    },
}


Chybové stavy

Stav Příčina Řešení
503 při /assign Fond je prázdný Nahraj novou dávku do s3klic/ a spusť import
409 při /assign Firma již má klíč Revokuj starý klíč, pak přiraď nový
404 při /credentials Firmě nebyl přiřazen klíč Přiřaď klíč přes POST /admin/s3keys/assign/{id}
500 při /credentials Chyba dešifrování Zkontroluj S3_KEY_ENCRYPTION_KEY v .env — nesmí se měnit po importu
Import vrátí imported: 0 Žádný soubor v s3klic/ Nahraj JSON soubor na správný prefix

Env proměnné

Proměnná Popis Příklad
S3_ENDPOINT URL S3 serveru https://s3.avaxis.cz
S3_BUCKET Systémový bucket avaxis
S3_ACCESS_KEY Admin přístupový klíč avaxis
S3_SECRET_KEY Admin tajný klíč ...
S3_KEY_ENCRYPTION_KEY Fernet klíč pro šifrování secrets fjpkNOGL...=
S3_KEY_IMPORT_PREFIX Prefix pro batch soubory s3klic/ (výchozí)

S3_KEY_ENCRYPTION_KEY vygeneruj příkazem:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Klíč nesmí být po importu klíčů změněn — secrets v DB by se staly nečitelnými.


Aktualizováno: 2026-04-26 | Testováno: 14/14 testů OK