Files
admin/.tasks/restore-elasticsearch-indices-books-vds.md
vitya 063910e265 incident(books-vds-es): true RCA — ransom-бот через открытый :9200, не оператор
Рецидив вчерашнего ES-инцидента вскрыл истинную причину. Вчерашняя
гипотеза «оператор в cutover попал на canonical» опровергнута.

Root cause: ES (stack 33) публиковал 0.0.0.0:9200 мимо traefik. Free-ES
7.10 без auth → порт открыт всему интернету. Ransom-бот сносил индексы
by-name (мимо Control #1 destructive_requires_name), оставлял read_me с
BTC-выкупом. accessLog (Control #2) пуст — бот шёл прямо в порт, не через
traefik. firewalld бесполезен (docker-publish обходит INPUT-зоны).

Fix (Control #3): убрана публикация host-порта из stack 33 (Portainer
PUT), дыра закрыта; re-restore epz/products/artmone из daily-2026-05-25.

Отдельный баг: epz-поиск падал у ОБОИХ тенантов — getTenantIdSeller(
config.get("tenant")) через node-config, а tenant не задан ни в
default.json, ни в env-маппинге (TENANT env = мёртвый груз). Добавлен
tenant в overlay default.json (slovo/bookva). products работал —
отдельный код-путь.

accessLog откатан (сторожил не ту дверь).

- wiki concept: correction-блок + секция «Рецидив 2026-05-29» + exposure-audit
- tasks: restore-es reopened+reclosed; new  harden-books-vds-exposed-ports
- NEXT_SESSION handoff

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:31:25 +03:00

9.5 KiB
Raw Permalink Blame History

restore-elasticsearch-indices-books-vds

Goal

Восстановить 3 ES индекса (epz, products, artmone) на canonical endpoint elasticsearch.kzntsv.site (books VDS, Portainer stack 33). Сейчас target ES пустой → весь поиск в books-app (slovo) сломан (404 index_not_found_exception для epz и products).

Симптомы (зафиксировано 2026-05-28 ~15:50 MSK)

  • bookva.kzntsv.site/api/products/search?q=... → 500 no such index [products].
  • bookva.kzntsv.site/api/epz/search?q=... → 500 no such index [epz].
  • books-api + books-web логи (stderr) — десятки ResponseError: index_not_found_exception за час.
  • User-facing impact: страница /epz/search и поиск товаров в slovo UI не работают.

Closure note (2026-05-28 ~20:10 MSK)

🟢 Восстановлено за 46 сек через snapshot restore + два preventive фикса в один заход.

Restore

POST /_snapshot/kreknin/daily-2026-05-25/_restore?wait_for_completion=true
{"indices":"epz,products,artmone","include_global_state":false,"include_aliases":true,"index_settings":{"number_of_replicas":0}}
  • artmone: 2621 docs ✓ (source: 2621)
  • epz: 820604 docs ✓ (source: 820604)
  • products: 105922 docs ✓ (source: 105922)
  • cluster: green
  • 3 consumers (books-api/task-runner/job-scheduler) рестартованы — 0 index_not_found_exception в логах за 5 мин
  • public smoke bookva.kzntsv.site/api/{epz,products}/search → 401 от auth middleware (поиск-pipeline жив, до wipe было 500)

Ключевая удача: в исходной задаче было записано «pre-migration-2026-05-25 содержит только read_me placeholder — не годится для восстановления» — это правда, но это был другой snapshot (он был до reindex'а). А daily-2026-05-25 (сделан кроном books-vds-backup-daily-kreknin в 09:51 UTC через 2ч после reindex'а) содержал full corpus — [read_me, products, epz, artmone, .tasks], 25 sec duration, 5 шардов SUCCESS. Snapshot pipeline (стояший с 25.05) автоматически создал safety net.

Root cause (forensics из ES container log)

Container elasticsearch restart_count=0, created=2026-05-25T07:51:38никогда не рестартовал, volume bind не задет, индексы существовали и были удалены через ES API:

2026-05-26 10:21:43.526 UTC  [read_me/IfM2V1pTQk-...]  deleting index
2026-05-26 10:21:44.075 UTC  [artmone/xQdFWkafSKy-...] deleting index
2026-05-26 10:21:44.257 UTC  [epz/AV8inT4YS-e-...]     deleting index
2026-05-26 10:21:44.450 UTC  [.tasks/EbutxIzdTPSq-...] deleting index
2026-05-26 10:21:44.576 UTC  [products/6VRXsuP5QV-...] deleting index

5 индексов (включая system .tasks) удалены за 1 секунду = DELETE _all / DELETE * / Kibana DevTools «delete index».

Timeline:

  • 2026-05-25 09:51 UTC — daily snapshot SUCCESS [read_me, products, epz, artmone, .tasks] (full data)
  • между ~10:00 и 26.05 03:02 UTC — Wipe #1 (snapshot 26.05 содержит только [read_me])
  • 2026-05-26 05:4906:44 UTC — re-reindex (artmone+epz+products, full counts восстановлены)
  • 2026-05-26 09:10:18 UTCbookva-es container created (cutover-prep Step 2)
  • 2026-05-26 10:21:43 UTC — Wipe #2 (1ч 11мин после создания bookva-es)

bookva-es использует named volume bookva-es-data, БЕЗ traefik labels (internal-only). Не задел canonical через mount или routing. Best guess: оператор в момент cutover-prep хотел очистить новый bookva-es:9200, но команда ушла на elasticsearch.kzntsv.site (canonical, slovo). Bookva-es internal-only → DELETE с воркстейшна туда не дойти; canonical через traefik basicAuth → доходит.

Caller identity unrecoverable: ES audit log = X-Pack платная фича (нет на free 7.10). Traefik accessLog был отключён (нет секции accessLog: в traefik.yml). Portainer audit = CE без enterprise. Только timestamp + DELETE fact в ES log.

Preventive fixes (applied 2026-05-28 ~20:10 MSK)

Fix #1: action.destructive_requires_name=true на stack 33 (env var добавлен через Portainer PUT API). Verified:

  • DELETE /_all → 400 «Wildcard expressions or all indices are not allowed»
  • DELETE /ep* → 400 same
  • DELETE /probe-canary (by exact name) → 200 (легитимные операции работают)

Pattern удаления который случился (5 индексов в 1 сек) теперь физически невозможен.

Fix #2: traefik accessLog в JSON формат, /letsencrypt/access.log (bind-mounted, persistent). Backup traefik.yml.bak-pre-accesslog-2026-05-28 рядом. Verified entries содержат RequestMethod, RequestHost, RequestPath, ClientHost, ClientUsername, DownstreamStatus, TLSCipher. Будущие DELETE запросы оставят след — даже если кто-то снова попадёт не на тот endpoint, retrospective forensics возможен.

Artifacts

  • .scratch/stack33-put-2026-05-28.json — Portainer PUT payload (новый env + сохранён reindex.remote.whitelist).
  • .wiki/concepts/es-destructive-delete-incident-2026-05-26.md — incident concept (написан в эту сессию).
  • ES container env post-fix: reindex.remote.whitelist=elasticold.kzntsv.site:443 + action.destructive_requires_name=true.
  • Traefik static config: accessLog: { filePath: /letsencrypt/access.log, format: json, bufferingSize: 100 }.

Follow-ups (не блокеры)

  • Logrotate на access.log — при ~1 req/sec будет ~3050 MB/day. Disk free 63G, ressedimption через 12 года максимум — но добавить logrotate стоит. Отдельная micro-task.
  • Переезд access.log из /letsencrypt/ в отдельный bind (/usr/docker/traefik/logs/) — косметика, текущее место persistent и работает.
  • Pass entry update — комментарий в books-vds/full-env устарел: «NOT Portainer-managed (legacy SSH-compose): elasticsearch, mongo, minio, books-db…». Migration 25.05 (books-vds-stacks-to-portainer) уже мигрировала их. Поправить при следующем pass-edit.

Текущее состояние ES endpoint'ов

Source (Windows host, rollback — не выключен): https://elasticold.kzntsv.site/

index    health docs.count store.size
artmone  yellow       2621      1.4mb
epz      yellow     820604    699.7mb
products yellow     105922     26.6mb

Target (books VDS, Portainer stack 33, canonical): https://elasticsearch.kzntsv.site/ — restored 2026-05-28, counts == source, green.

RECURRENCE 2026-05-29 — RCA был НЕВЕРНЫМ, дыра не закрыта

Через ~3ч после вчерашнего restore индексы снова исчезли (artmone/epz/products удалены 28.05 20:13 UTC, остался ransom-read_me). Вчерашний best-guess «оператор в cutover» опровергнут.

True root cause: stack 33 публиковал ports: - 9200:9200 → docker прокинул 0.0.0.0:9200 на публичный IP в обход traefik+firewalld. ES 7.10 free без authcurl http://89.253.255.133:9200/ без кредов возвращал cluster info. Ransom-бот сканил порт, удалял индексы by-name (мимо вчерашнего Fix #1, который ловит только _all/*), оставлял read_me с требованием 0.0041 BTC. accessLog (Fix #2) пуст по DELETE — бот шёл прямо в :9200, не через traefik. bookva не задет (bookva-es порт не публикует).

Fix #3 (applied 2026-05-29): убран ports: блок из stack 33 (Portainer PUT /api/stacks/33?endpointId=1). Порт больше не публикуется (docker ps9200/tcp без 0.0.0.0:, внешний curl :9200 refused), traefik route жив. Затем DELETE /read_me + restore epz,products,artmone из daily-2026-05-25 (counts 820604/105922/2621, green) + restart books-api books-task-runner books-job-scheduler. Поиск slovo через traefik отдаёт хиты. Полный разбор: .wiki/concepts/es-destructive-delete-incident-2026-05-26.md § «Рецидив 2026-05-29».

Spawned follow-up: harden-books-vds-exposed-ports — exposure-audit нашёл ещё 5 сервисов с публичными host-портами (mongo/books-db/bookva-db/minio/bookva-minio, все credentialed) + rsync:873. Не emergency, но закрыть.

Branch

n/a (admin ops)

Status

closed 2026-05-28 → reopened+reclosed 2026-05-29 (true RCA: exposed port, Fix #3 applied)