# harden-books-vds-exposed-ports ## Goal Закрыть/ограничить публично торчащие host-порты на books VDS (89.253.255.133). Выявлены во время инцидента `restore-elasticsearch-indices-books-vds` (2026-05-29): ES был снесён ransom-ботом через открытый `0.0.0.0:9200` в обход traefik+firewalld. Тот же класс дыры (docker-publish обходит firewalld INPUT-зоны) зияет ещё на ряде stateful-сервисов. Цель — ни один stateful-сервис не должен быть доступен с публичного IP напрямую; доступ либо через traefik (под auth), либо по внутренней docker-сети, либо по SSH-туннелю для admin. ## Exposure audit (2026-05-29, `docker ps` + `ss -tlnp`) | Сервис | Порт наружу | Auth на сервисе | Действие | |---|---|---|---| | `elasticsearch` | ~~`0.0.0.0:9200`~~ | ❌ free-ES без auth | ✅ ЗАКРЫТ (Fix #3, инцидент) | | `mongo` | `0.0.0.0:27017` | ✅ enabled | убрать публикацию (app ходит по docker-сети) | | `books-db` (mariadb) | `0.0.0.0:3306` | ✅ root+pass | убрать публикацию / SSH-туннель для admin | | `bookva-db` (mariadb) | `0.0.0.0:33306` | ✅ | убрать публикацию | | `minio` | `0.0.0.0:9000` | ✅ | оставить только через traefik (minio.kzntsv.site) | | `bookva-minio` | `0.0.0.0:9001` | ✅ | то же | | rsync | `:873` | ? проверить | проверить нужен ли наружу | | imgproxy / imgproxy-nginx / proxy-chain | `:8787/8788/8778` | ? | проверить, internal? | ## Key files / endpoints - Portainer API `https://portainer.kzntsv.site` (key в `pass books-vds/full-env`), PUT `/api/stacks/?endpointId=1` — паттерн как в Fix #3 (убрать `ports:` блок из compose). - Stack ids: см. `.wiki/entities/books-vds.md` § Стек (22–33). mongo=31, books-db=32, minio=30, ES=33. bookva-* стек(и) — id уточнить через Portainer API list. - firewalld активен но docker его обходит — закрытие через docker port-publish, НЕ через firewalld rules. ## Decisions log - 2026-05-29: Не закрывать в emergency-режиме инцидента — БД credentialed (не дыра-нараспашку как free-ES), и публикация может использоваться для внешнего admin (DBeaver/Compass/mc). Нужно сперва проверить по каждому: ходит ли приложение через host-порт или по docker-сети, и нужен ли внешний admin-доступ (тогда SSH-туннель вместо публикации). ## Open questions - [ ] Каждый сервис: приложение коннектится через `service-name:port` (docker DNS) или через host-порт? Если через docker-сеть → публикацию убрать безопасно. - [ ] Нужен ли кому-то внешний admin к mongo/mariadb? Если да → SSH local-forward, не публичный порт. - [ ] rsync:873 — это backup-pipeline или забытый демон? Проверить. - [ ] imgproxy/proxy-chain порты — реально нужны наружу или artefact? ## Completed steps - [x] 2026-05-29: exposure audit (таблица выше) - [x] 2026-05-29: ES :9200 закрыт (в рамках инцидент-фикса) - [x] 2026-05-29: проверена auth — mongo/mariadb/minio требуют креды (не auth-less) ## Notes Главный learning из инцидента: auth должна быть **на самом сервисе**, не только в traefik-мидлваре. firewalld не защищает от docker-publish. См. `.wiki/concepts/es-destructive-delete-incident-2026-05-26.md` § «Рецидив 2026-05-29». ## Branch n/a (admin ops) ## Status ready 2026-05-29