Incident: W4 File Download Broken (hardcoded Wasabi key deleted)
| Pole | Wartość |
|---|---|
| ID | INCIDENT-2026-07-01-002 |
| Platforma | W4 (w4.pinbox24.com, bms-1) |
| Severność | P0 (download plików z rejestru niedostępny) |
| Status | RESOLVED (hotfix in-container) / Długoterminowy fix PENDING |
| Wykryto | 2026-07-01T~12:00Z |
| Hotfix | 2026-07-01T~15:00Z |
| GH Issue | radieu/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:00 | Wykryto: user zgłasza broken download po rotacji |
| ~12:30 | Error: “AWS Access Key Id does not exist” w logach s3-v2-v42-prod |
| ~13:00 | Analiza: storage.config.js — znalezione hardcoded key VCUC7... |
| ~14:00 | Znaleziono nowy klucz w s3-environment.env (NLDX...) |
| ~14:30 | patch_storage_config2.py — regex pattern na unquoted JS props |
| ~15:00 | Patch zastosowany, 3 instancje zamienione, PM2 reload × 4 |
| ~15:00 | GH Issue #2418 założony |
Zmienione pliki / kontenery
| Kontener/Plik | Zmiana | Rollback |
|---|---|---|
s3-v2-v42-prod:/app/dist/config/storage.config.js | VCUC7X6A1GINU54MDWON (×3) → NLDX... (×3), secretAccessKey (×3) zaktualizowany | Rebuild 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 onlineFunkcjonalny test: GET /api/offices/files/:fileId/getBase64File powinien zwrócić 200.
Security findings
storage.config.jsw obrazie Docker zawiera hardcoded Wasabi accessKeyId + secretAccessKey- Klucz
VCUC7X6A1GINU54MDWONbył widoczny w każdymdocker inspectlubdocker exec cat - Nowy klucz
NLDX...jest teraz w storage.config.js wewnątrz kontenera — to jest tymczasowe - 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.jsz kontenera - Re-apply
patch_storage_config2.pypo 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.envnigdy nie dostał aktualnego klucza (VCUC był skasowany, NLDX nie był propagowany do v42-prod)pinbox24-bms1-s3IAM 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
- Hardcoded credentials w skompilowanych obrazach to tykająca bomba — rotacja kluczy ich nie obejmuje
- Rotacja Wasabi powinna sprawdzać czy klucz jest hardcoded w jakimkolwiek obrazie przed usunięciem
docker recreateniszczy hotfixy — każdy hotfix w kontenerze musi mieć script re-apply- OVH Image Registry vs env vars:
storage.config.jsignorował env vars całkowicie