Přeskočit obsah

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ář

s3://claudeai/system/key-imports/

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

  1. Správce nahraje JSON soubor do s3://claudeai/system/key-imports/
  2. V AVAX Admin UI klikne Importovat klíče (nebo backend periodicky skenuje, viz Celery)
  3. Backend:
  4. Načte všechny soubory z adresáře
  5. Přeskočí soubory kde imported_file již existuje v DB (s3_import_files)
  6. Pro každý klíč: validace formátu → INSERT do s3_keys (secret šifrovaně AES)
  7. Označí soubor jako zpracovaný v s3_import_files
  8. Vrátí počet nově importovaných klíčů
  9. 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

{
  "files_processed": 2,
  "keys_imported": 18,
  "keys_skipped": 2,
  "errors": []
}

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

009_s3_key_manager.sql
-- 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í na s3_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