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¶
- Jak to funguje
- Databázové tabulky
- Formát dávkového souboru
- API endpointy
- Bezpečnostní model
- Celery task (automatický import)
- 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¶
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_encje š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¶
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:
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:
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:
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í) |
Klíč nesmí být po importu klíčů změněn — secrets v DB by se staly nečitelnými.
S3_KEY_ENCRYPTION_KEYvygeneruj příkazem:
Aktualizováno: 2026-04-26 | Testováno: 14/14 testů OK