Короткий ответ

Снимите ss -tulnp, сверьте с матрицей «кто с каких сетей». Всё, что слушает 0.0.0.0/:: и не должно быть на WAN, закрывайте на двух уровнях: bind приложения на 127.0.0.1 или LAN 10.0.20.10, плюс UFW/nft allow только с нужного CIDR. Firewall без смены bind оставляет сервис уязвимым при ошибке правила. Не публикуйте Docker -p 80:80 если нужен только localhost. Не отключайте firewall, чтобы «проверить порт».

Хост host.example (10.0.20.10), админ admin.

Симптомы и как отличить

  • Внешний nmap: 22, 80, 443, 3306, 6379;
  • curl http://WAN_IP:9200 отвечает Elasticsearch без пароля;
  • с localhost «всё и так работает», с офиса база открыта «удобно».
СлушаетСмысл
127.0.0.1:3306не WAN, если нет туннеля
10.0.20.10:3306LAN-интерфейс
0.0.0.0:3306все IPv4, включая WAN NIC
[::]:3306все IPv6

Отличие от «firewall не включён»: UFW может быть active с 3306 Anywhere. Тогда фильтр пропускает лишнее — чините allow и bind, не радуйтесь Status: active. Методика съёмки: открытые порты. Базовый deny: firewall.

Возможные причины

  1. Дефолт Redis/Postgres bind * / listen_addresses = '*'.
  2. Docker ports: - "6379:6379" без 127.0.0.1:.
  3. Node exporter / cadvisor на :9100 для «удобного Grafana из дома».
  4. Старый ufw allow 3306.
  5. Kubelet/профилировщики забыли.
  6. ssh -L не при чём: это локальный форвард на вашей станции.

Диагностика

ss -tulnp
sudo ss -tulnp | awk 'NR==1 || $1 ~ /LISTEN/'
ip -br addr

Разбор Docker/podman:

docker ps -a --format '{{.ID}} {{.Names}} {{.Ports}}' 2>/dev/null
sudo lsof -iTCP -sTCP:LISTEN -P -n

Конфиги (примеры, читайте свой пакет):

sudo grep -E '^bind |^protected-mode' /etc/redis/redis.conf 2>/dev/null
sudo grep -E '^listen_addresses|^port' /etc/postgresql/*/main/postgresql.conf 2>/dev/null
sudo grep -R listen /etc/mysql/ 2>/dev/null | grep -v '#'

С hop:

nmap -Pn -p 22,80,443,3306,5432,6379,2375,9200,9100 10.0.20.10

Не сканируйте чужие сети. Для WAN подставьте публичный IP, не RFC1918, если речь про интернет.

UFW:

sudo ufw status numbered

Ищите Anywhere на портах данных.

Решение

Сценарий A. Приложение bind на все интерфейсы

Redis: bind 127.0.0.1 и protected-mode yes. Если нужен LAN:

bind 10.0.20.10

плюс пароль/ACL. Перезапуск unit в окно. Postgres: listen_addresses = '10.0.20.10' и pg_hba.conf только с 10.0.20.0/24, не 0.0.0.0/0.

Проверка:

ss -tlnp | grep -E '5432|6379|3306'

Не должно быть 0.0.0.0 если политика — LAN/local.

Сценарий B. Docker publish

В Compose:

ports:
  - "127.0.0.1:6379:6379"

не "6379:6379". Пересоздание контейнера. Для доступа с другого хоста LAN — publish на 10.0.20.10:6379 плюс UFW CIDR, или overlay без хостового порта.

Не открывайте TCP 2375 без TLS. Сокет: docker.sock.

Сценарий C. Firewall режет WAN, bind пока не трогали

Временный слой, не финал:

sudo ufw delete allow 3306/tcp
sudo ufw allow from 10.0.20.0/24 to any port 3306 proto tcp

Оставьте консоль. Затем всё равно сузьте bind: ошибка ufw delete не должна снова выставить базу в мир.

Сценарий D. systemd unit слушает *

Environment= или ExecStart= с --port 0.0.0.0. Override:

sudo systemctl edit myservice

ExecStart= сброс и новый bind. Не запускайте этот сервис от root без нужды: сервис от root.

Сценарий E. Экспортёры и «внутренние» админки

Node exporter, cAdvisor, phpMyAdmin, RabbitMQ management, Kubernetes metrics — их часто оставляют на :9100/:15672 «пока настроим VPN». С точки зрения ss это тот же WAN-риск, что и Redis. Пока VPN нет — bind 10.0.20.10 плюс UFW только с admin-CIDR, либо SSH-туннель. Не ufw allow 9100 в Anywhere «для Grafana из дома»: Grafana должна ходить в LAN или через jump.

Проверьте IPv6 отдельно: закрытый 0.0.0.0:6379 при живом [::]:6379 означает, что скан с AAAA всё ещё находит сервис. В redis/postgres слушайте явно оба семейства или отключите ненужный стек на сервисе, не net.ipv6.conf.all.disable_ipv6=1 «вместо firewall».

Сценарий F. Откат, если закрыли лишнее и упало приложение

Если после смены listen_addresses клиенты в LAN не коннектятся — верните предыдущий конфиг из снимок/git и разберите pg_hba / redis ACL, не bind *. Типичная ошибка: слушаете 10.0.20.10, а клиенты ходят на hostname, который резолвится в другой NIC (VIP, docker0, 172.17.0.1). Сверьте ss и ip addr на тот адрес, который в connection string.

Для Docker после смены publish контейнер мог остаться со старым маппингом: docker ps важнее ss на секунду гонки. docker compose up -d без recreate не всегда пересоздаёт ports. Явно up -d --force-recreate в окно.

Зафиксируйте в заявке: кто имеет право слушать WAN (обычно 22/443), кто только LAN, кто только localhost. Этот документ — вход для скана портов через квартал: diff ss без тикета = регрессия, не «так вышло».

Как проверить, что проблема устранена

  1. ss -tulnp — нет лишних 0.0.0.0/::.
  2. nmap с WAN-hop — только согласованные 22/443/…
  3. nmap с LAN — порты, нужные приложению.
  4. UFW numbered без Anywhere на БД.
  5. Приложение с клиента LAN живо.

Сохраните два файла ss до/после в заявку.

Если не помогло

  • Порт снова 0.0.0.0 после reboot: конфиг пакета перетёрт, docker-compose без 127.0.0.1, kubernetes NodePort.
  • Виден порт, ss пустой: это DNAT на роутере, не этот хост. Смотрите периметр.
  • IPv6 :::3306 при закрытом IPv4.
  • Restart=always контейнера со старым publish.
  • SELinux на Ubuntu не штатно; не отключайте MAC. На RHEL semanage port — не setenforce 0 навсегда.

Профилактика

  • В шаблоне compose запрещён publish без bind-адреса.
  • CI-проверка: ss/nmap в пайплайне выкладки.
  • Экспортёры только на admin-сети.
  • Квартальный проход baseline.

FAQ

Достаточно ли закрыть UFW и не трогать bind?

Нет. Следующий ufw allow 3306 или сбой стека откроет сервис. Bind — источник правды.

localhost и docker-proxy

127.0.0.1:6379 на хосте недоступен с LAN — это цель. Для LAN явно публикуйте LAN IP.

Зачем тогда firewall?

Defense in depth, чужие процессы, ошибка bind после обновления.

nmap показывает filtered vs closed

filtered — drop (UFW/nft). closed — RST, порт не слушает, фильтр пропускает. Для WAN лучше не светить лишние closed на данных: и bind, и drop.

Можно ли оставить 22 Anywhere?

Лучше CIDR/VPN. См. SSH. Не взамен закрытия 3306.