S3 Key Manager — Specifikace¶
Verze: 1.0 | Stav: Draft | Fáze: F4-pre (před chatem)
Přehled¶
S3 Key Manager spravuje přihlašovací údaje (access key + secret key) k S3/MinIO pro firmy, systémové komponenty a chat. Komponenta běží v záložce AVAX Admin (viditelná jen pro super_admin/company_admin firmy s IČO 01695541).
Správce klíče nevytváří — vytváří je ručně v Rados/MinIO konzoli a pak je importuje přes JSON soubor nahraný na S3. Backend je automaticky přiřazuje při registraci nových firem; spravovaná infrastruktura tak vždy ví, který klíč komu patří.
Typy klíčů¶
| Typ | Hodnota key_type |
Popis |
|---|---|---|
| Firemní (volný) | company_free |
Čeká na přiřazení nové firmě |
| Firemní (přiřazený) | company_assigned |
Přiřazen konkrétní firmě |
| Chat sdílený | system_chat |
Jeden klíč, bucket avax-chat |
| GC worker | system_gc |
Celery GC job — přístup ke všem bucketům |
| Distributor appek | system_appdist |
Read-only přístup k app-distribution bucketu |
| Vlastní systémový | system_custom |
Libovolná další systémová role |
Systémové klíče (pevně pojmenované komponenty)¶
| Komponenta | component |
Buckety |
|---|---|---|
| Chat service | chat-service |
avax-chat |
| GC worker | gc-worker |
backup-*, avax-chat |
| App distribution | app-distribution |
app-distribution |
Import klíčů¶
S3 import adresář¶
Správce nahraje JSON soubor s libovolným názvem (doporučeno keys-YYYYMMDD.json).
Backend při spuštění importu tento adresář proskenuje a zpracuje všechny dosud
neimorptované soubory.
Formát JSON importního souboru¶
{
"format": "avax-s3-keys-v1",
"created_at": "2026-05-03T12:00:00Z",
"endpoint": "https://s3.avaxis.cz",
"keys": [
{
"access_key": "XXXXXXXXXXXXXXXX",
"secret_key": "yyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"key_type": "company_free",
"label": "pool-batch-2026-05-03",
"notes": "Batch 20 klíčů pro firmy"
},
{
"access_key": "AAAAAAAAAAAAAAA1",
"secret_key": "zzzzzzzzzzzzzzzzzzzzzzzzzzzz",
"key_type": "system_chat",
"component": "chat-service",
"label": "chat-service-key",
"notes": "Hlavní klíč pro chat service"
},
{
"access_key": "AAAAAAAAAAAAAAA2",
"secret_key": "zzzzzzzzzzzzzzzzzzzzzzzzzzzz",
"key_type": "system_gc",
"component": "gc-worker",
"label": "gc-worker-key"
},
{
"access_key": "AAAAAAAAAAAAAAA3",
"secret_key": "zzzzzzzzzzzzzzzzzzzzzzzzzzzz",
"key_type": "system_appdist",
"component": "app-distribution",
"label": "app-dist-key"
}
]
}
Povinné pole pro všechny klíče: access_key, secret_key, key_type
Povinné pro systémové klíče: component
Nepovinné: label, notes
Import flow¶
- Správce nahraje JSON soubor do
s3://claudeai/system/key-imports/ - V AVAX Admin UI klikne Importovat klíče (nebo backend periodicky skenuje, viz Celery)
- Backend:
- Načte všechny soubory z adresáře
- Přeskočí soubory kde
imported_filejiž existuje v DB (s3_import_files) - Pro každý klíč: validace formátu → INSERT do
s3_keys(secret šifrovaně AES) - Označí soubor jako zpracovaný v
s3_import_files - Vrátí počet nově importovaných klíčů
- Původní soubory zůstávají v S3 (manuální mazání správcem)
Databázové tabulky¶
s3_keys¶
CREATE TABLE s3_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key_type VARCHAR(30) NOT NULL, -- company_free | company_assigned | system_*
access_key VARCHAR(100) NOT NULL UNIQUE,
secret_key_enc BYTEA NOT NULL, -- AES-256-GCM šifrovaný secret
endpoint VARCHAR(200) NOT NULL DEFAULT 'https://s3.avaxis.cz',
status VARCHAR(20) NOT NULL DEFAULT 'free', -- free | assigned | disabled
label VARCHAR(100),
notes TEXT,
component VARCHAR(50), -- chat-service | gc-worker | app-distribution | NULL
company_id UUID REFERENCES companies(id),
bucket_names TEXT[], -- buckety tohoto klíče
imported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
assigned_at TIMESTAMPTZ,
disabled_at TIMESTAMPTZ
);
CREATE INDEX idx_s3_keys_status ON s3_keys(status);
CREATE INDEX idx_s3_keys_company ON s3_keys(company_id);
CREATE INDEX idx_s3_keys_component ON s3_keys(component);
s3_import_files¶
CREATE TABLE s3_import_files (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
s3_key VARCHAR(500) NOT NULL UNIQUE, -- cesta v S3 (system/key-imports/xxx.json)
imported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
keys_count INTEGER NOT NULL DEFAULT 0,
imported_by UUID REFERENCES users(id) -- NULL = automatický import
);
Přiřazení klíčů při registraci firmy¶
Při POST /auth/register/company:
async def assign_s3_key_to_company(db, company_id: UUID, ico: str) -> S3Key:
# 1. Zamkni a vyber volný klíč
key = await db.execute(
select(S3Key)
.where(S3Key.key_type == "company_free", S3Key.status == "free")
.with_for_update(skip_locked=True)
.limit(1)
)
if not key:
raise HTTPException(503, "Nejsou dostupné S3 klíče — kontaktujte administrátora")
# 2. Vytvoř buckety přes boto3
s3 = boto3.client(
"s3",
endpoint_url=key.endpoint,
aws_access_key_id=key.access_key,
aws_secret_access_key=decrypt_secret(key.secret_key_enc),
)
buckets = [f"backup-{ico}", f"sync-{ico}"]
for bucket in buckets:
s3.create_bucket(Bucket=bucket)
# 3. Aktualizuj klíč v DB
key.status = "assigned"
key.key_type = "company_assigned"
key.company_id = company_id
key.bucket_names = buckets
key.assigned_at = datetime.utcnow()
await db.commit()
return key
Buckety firemního klíče¶
| Bucket | Účel |
|---|---|
backup-{ico} |
S3 zálohy firmy (BackupScreen) |
sync-{ico} |
SyncAgent sdílené adresáře |
Upozornění na volné klíče (fronta)¶
- Pokud počet
status='free'klíčů klesne pod 5, backend loguje WARNING - Pokud klesne pod 2, AVAX Admin UI zobrazí červené upozornění v S3 Key Manager
API endpointy¶
Všechny pod /admin/s3keys — vyžadují super_admin.
| Metoda | Endpoint | Popis |
|---|---|---|
| GET | /admin/s3keys |
Seznam všech klíčů (bez secret) |
| GET | /admin/s3keys/stats |
Statistiky: volné/přiřazené/systémové |
| POST | /admin/s3keys/import |
Spustí import z S3 key-imports adresáře |
| GET | /admin/s3keys/{id} |
Detail klíče |
| POST | /admin/s3keys/{id}/disable |
Označí klíč jako disabled |
| GET | /admin/s3keys/import-history |
Historie importních souborů |
GET /admin/s3keys — Response¶
[
{
"id": "uuid",
"key_type": "company_assigned",
"access_key": "XXXXXXXXXX",
"status": "assigned",
"label": "pool-batch-01",
"component": null,
"company_id": "uuid",
"company_name": "Moje Firma s.r.o.",
"company_ico": "12345678",
"bucket_names": ["backup-12345678", "sync-12345678"],
"imported_at": "2026-05-03T12:00:00Z",
"assigned_at": "2026-05-04T08:30:00Z"
}
]
POST /admin/s3keys/import — Response¶
AVAX Admin UI — S3 klíče¶
Nová záložka S3 klíče v AvaxAdminScreen.
Sub-tabs¶
| Tab | Obsah |
|---|---|
| Přehled | Statistiky: volné / přiřazené / systémové / disabled |
| Klíče firem | Tabulka přiřazených firemních klíčů |
| Volný pool | Tabulka volných klíčů + upozornění pokud málo |
| Systémové klíče | Klíče komponent (chat-service, gc-worker, app-distribution) |
| Import | Tlačítko import + tabulka historie importů |
Přehled — statistiky (barevné karty)¶
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Volné klíče │ │ Přiřazené firmám│ │ Systémové klíče │
│ 12 │ │ 47 │ │ 3 │
│ ● Dostatek │ │ │ │ ● Vše OK │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Červená karta "Volné klíče" pokud ≤ 2.
Import tab¶
[ Spustit import z S3 ] → tlačítko → volá POST /admin/s3keys/import
Historie importů:
Soubor | Klíčů | Datum
system/key-imports/k-may.json | 20 | 2026-05-03 12:00
system/key-imports/k-apr.json | 15 | 2026-04-15 09:30
Šifrování secret klíčů¶
Secret klíče jsou v DB šifrované pomocí AES-256-GCM. Šifrovací klíč je v env proměnné:
S3_KEY_ENCRYPTION_KEY=<32 bytes base64> # generovat: python -c "import secrets,base64; print(base64.b64encode(secrets.token_bytes(32)).decode())"
Dešifrování probíhá pouze v paměti při assign_s3_key_to_company nebo při vracení
klíče konkrétní komponentě při startu (GET /admin/s3keys/{id} interní endpoint).
Celery task — auto-import¶
@celery.task(name="s3keys.auto_import")
async def auto_import_s3_keys():
"""Skenuje s3://claudeai/system/key-imports/ každých 5 minut."""
...
Schedule: každých 5 minut. Přeskočí soubory které již byly importovány.
Migrace¶
-- s3_keys
CREATE TABLE s3_keys (...);
CREATE INDEX idx_s3_keys_status ON s3_keys(status);
CREATE INDEX idx_s3_keys_company ON s3_keys(company_id);
CREATE INDEX idx_s3_keys_component ON s3_keys(component);
-- s3_import_files
CREATE TABLE s3_import_files (...);
Závislosti¶
- Registrace firmy (
/auth/register/company) závisí nas3_keys(musí existovat volný klíč) - Chat service při startu načte svůj klíč z
s3_keys WHERE component='chat-service' - GC worker načte svůj klíč z
s3_keys WHERE component='gc-worker' - App distribution načte klíč z
s3_keys WHERE component='app-distribution'
Aktualizováno: 2026-05-02