Incident: W4 File Download Broken (hardcoded Wasabi key deleted)

PoleWartość
IDINCIDENT-2026-07-01-002
PlatformaW4 (w4.pinbox24.com, bms-1)
SevernośćP0 (download plików z rejestru niedostępny)
StatusRESOLVED (hotfix in-container) / Długoterminowy fix PENDING
Wykryto2026-07-01T~12:00Z
Hotfix2026-07-01T~15:00Z
GH Issueradieu/p24-infra#2418

Symptom

GET /api/offices/files/:fileId/getBase64File zwracał HTTP 500:

{
  "AccessDenied": {
    "statusCode": 403,
    "message": "AWS Access Key Id does not exist in our records."
  },
  "status": 500
}

Użytkownicy nie mogli otworzyć ani pobrać plików z rejestru W4.

Root cause

Obraz Docker s3-v2-v42-prod (budowany listopad 2025) ma Wasabi credentials hardcoded w skompilowanym pliku /app/dist/config/storage.config.js:

// 3 osobne konfiguracje S3 — każda ma hardcoded klucz:
{
  accessKeyId: "VCUC7X6A1GINU54MDWON",   // ← usunięty przez rotation agent
  secretAccessKey: "hVnc..."
}

Klucz VCUC7X6A1GINU54MDWON został usunięty z Wasabi podczas rotacji credentiali (incydent #2350). Env vars z s3-environment.env (z nowym kluczem) są ładowane przez PM2 do process.env, ale storage.config.js ignoruje je — używa literalnych stringów.

Dlaczego upload działał, a download nie?

storage.controller.js (upload) był wcześniej patchowany osobnym hotfixem i reload PM2 zaczął używać process.env.*. storage.config.js (download) NIE był patchowany — używał hardcoded wartości.

Oś czasu

Czas (UTC)Zdarzenie
~12:00Wykryto: user zgłasza broken download po rotacji
~12:30Error: “AWS Access Key Id does not exist” w logach s3-v2-v42-prod
~13:00Analiza: storage.config.js — znalezione hardcoded key VCUC7...
~14:00Znaleziono nowy klucz w s3-environment.env (NLDX...)
~14:30patch_storage_config2.py — regex pattern na unquoted JS props
~15:00Patch zastosowany, 3 instancje zamienione, PM2 reload × 4
~15:00GH Issue #2418 założony

Zmienione pliki / kontenery

Kontener/PlikZmianaRollback
s3-v2-v42-prod:/app/dist/config/storage.config.jsVCUC7X6A1GINU54MDWON (×3) → NLDX... (×3), secretAccessKey (×3) zaktualizowanyRebuild obrazu lub ponowny patch ze starym kluczem (nie działa — klucz usunięty)

Script re-apply po docker recreate

docs/pinbox24/patch_storage_config2.py — odczytuje nowy klucz z s3-environment.env i patchuje storage.config.js w s3-v2-v42-prod.

WAŻNE: Patch NIE przeżyje docker-compose up --force-recreate s3-v2-v42-prod. Musi być re-apply po każdym recreate.

Weryfikacja fix

Po patchach PM2 reload:

docker exec s3-v2-v42-prod grep -c 'NLDX' /app/dist/config/storage.config.js
# → 3 (powinno być 3 instancje)
 
docker exec s3-v2-v42-prod pm2 status
# → s3-v2-v42-prod_backend: 4 workers online

Funkcjonalny test: GET /api/offices/files/:fileId/getBase64File powinien zwrócić 200.

Security findings

  1. storage.config.js w obrazie Docker zawiera hardcoded Wasabi accessKeyId + secretAccessKey
  2. Klucz VCUC7X6A1GINU54MDWON był widoczny w każdym docker inspect lub docker exec cat
  3. Nowy klucz NLDX... jest teraz w storage.config.js wewnątrz kontenera — to jest tymczasowe
  4. Issue #2397 (credential exposure) powinien obejmować storage.config.js

Długoterminowy fix

Opcja A (rekomendowana): Nowy obraz Docker

Zmienić storage.config.js w źródle żeby używał process.env:

// Zamiast hardcoded:
accessKeyId: "VCUC7X6A1GINU54MDWON",
// Używać:
accessKeyId: process.env.s3Bucket_api_accessKeyId || process.env.s3Bucket_api_accessKeyId_secondary,

Opcja B (tymczasowa): Pre-recreate checklist

Przed każdym docker recreate s3-v2-v42-prod:

# Na bms-1 po recreate:
python3 /root/patch_storage_config2.py
# Następnie:
docker exec s3-v2-v42-prod grep -c 'NLDX' /app/dist/config/storage.config.js
# → 3 (weryfikacja)

Checklist przed docker recreate s3-v2-v42-prod

  • Backup bieżącej wersji storage.config.js z kontenera
  • Re-apply patch_storage_config2.py po recreate
  • Re-apply upload format hotfix (res.json({ result: filesAdded }))
  • PM2 status: 4 workers online
  • Test: download pliku z rejestru W4 → 200

Follow-up: 2026-07-06 — Inline Preview

Problem ten wrócił w kontekście nowego endpointu getSignedUrl w v42-prod:

  • backend-environment.env nigdy nie dostał aktualnego klucza (VCUC był skasowany, NLDX nie był propagowany do v42-prod)
  • pinbox24-bms1-s3 IAM user ma dwa aktywne klucze (WKRQ****, LGQ2****) — jeden należy wyrotować żeby uzyskać secret i zaktualizować env
  • Szczegóły: incident-2026-07-06-w4-pm2-crash-inline-preview.md

Lekcje

  1. Hardcoded credentials w skompilowanych obrazach to tykająca bomba — rotacja kluczy ich nie obejmuje
  2. Rotacja Wasabi powinna sprawdzać czy klucz jest hardcoded w jakimkolwiek obrazie przed usunięciem
  3. docker recreate niszczy hotfixy — każdy hotfix w kontenerze musi mieć script re-apply
  4. OVH Image Registry vs env vars: storage.config.js ignorował env vars całkowicie