opened image

Docker против файервола: как закрыть уязвимости портов

Введение

 
Docker упрощает развёртывание приложений в контейнерах, но его поведение в отношении сетевых правил часто становится источником вопросов безопасности. Типичная ситуация: администратор настраивает UFW или добавляет правила в iptables для блокировки портов, но контейнеры остаются доступными извне. Причина кроется в том, как Docker управляет сетевым стеком Linux на низком уровне.
 

Данная статья рассматривает механизмы взаимодействия Docker с подсистемой netfilter (iptables/nftables), объясняет причины неожиданного поведения firewall-правил и предоставляет проверенные методы защиты контейнерной инфраструктуры. 
 

Ключевой вызов: Docker автоматически модифицирует правила iptables для обеспечения сетевой связности контейнеров, часто обходя стандартные firewall-механизмы на уровне приоритетов и порядка обработки правил.
 
 

Теория: как Docker работает с сетевым стеком Linux
 

Архитектура взаимодействия с netfilter
 

Docker напрямую интегрируется с netfilter - подсистемой ядра Linux для фильтрации и трансляции сетевых пакетов. При запуске Docker создаёт несколько пользовательских цепочек (chains) в таблицах nat и filter:
 

Таблица nat:

  • DOCKER - содержит правила DNAT для проброса портов с хост-системы в контейнеры
  • Правила в PREROUTING и OUTPUT перенаправляют трафик в цепочку DOCKER


Таблица filter:

  • DOCKER - правила фильтрации для трафика, направленного в контейнеры
  • DOCKER-ISOLATION-STAGE-1 и DOCKER-ISOLATION-STAGE-2 - обеспечивают изоляцию между различными Docker-сетями
  • DOCKER-USER - специальная цепочка для пользовательских правил (появилась в Docker 17.06)


Порядок обработки правил
 

Критически важен порядок прохождения пакета через цепочки. Для входящего трафика:

  1. PREROUTING (таблица nat) - здесь Docker вставляет переход в цепочку DOCKER перед любыми пользовательскими правилами
  2. FORWARD (таблица filter) - проходит через DOCKER-USER, затем DOCKER-ISOLATION, затем DOCKER
  3. POSTROUTING (таблица nat) - применяется SNAT/MASQUERADE


Docker использует операции -I (insert) для добавления правил в начало цепочек, что обеспечивает их приоритетное выполнение перед большинством пользовательских конфигураций.
 

Взаимодействие с nftables
 

Современные дистрибутивы Linux переходят с iptables на nftables — новую подсистему netfilter. Существует несколько вариантов конфигурации:

  • iptables-legacy - классическая реализация, использует отдельный набор правил ядра
  • iptables-nft - обёртка над nftables, транслирующая команды iptables в синтаксис nftables
  • nftables native - прямое использование команды nft


Проверить активный бэкенд можно командой:

iptables -V
# iptables v1.8.7 (nf_tables) — nftables is used
# iptables v1.8.7 (legacy) — uses legacy

update-alternatives --display iptables

 

Docker совместим с обоими вариантами, но возникают нюансы:

  • При использовании iptables-nft правила Docker видны через nft list ruleset, но имеют отдельную таблицу совместимости
  • Смешивание iptables-legacy и nftables приводит к работе с разными наборами правил, что создаёт иллюзию неработающих firewall-правил


 
Почему UFW может «не видеть» открытые Docker-порты
 

UFW (Uncomplicated Firewall) — это высокоуровневая обёртка над iptables, упрощающая управление правилами. UFW добавляет свои правила в стандартные цепочки INPUTOUTPUT и FORWARD.

 

Механизм обхода UFW
 

Когда Docker пробрасывает порт командой -p 8080:80, происходит следующее:

  1. Docker добавляет правило DNAT в таблице nat, цепочке DOCKER: DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
  2. Это правило применяется в цепочке PREROUTING, до того как пакет попадает в цепочку FORWARD, где работают правила UFW
  3. После трансляции адреса пакет направляется в FORWARD → DOCKER-USER → DOCKER
  4. Правила UFW в ufw-before-forward и ufw-after-forward проверяют уже изменённый адрес назначения (IP контейнера), а не исходный порт хоста

 

Результат: UFW показывает статус "порт закрыт", но трафик беспрепятственно проходит в контейнер через правила Docker в таблице nat.

 

Иллюстрация трудности:

 
UFW показывает закрытый порт:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 1

 

Но ss показывает слушающий порт

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 2


 

Почему простое добавление правил в iptables иногда НЕ эффективно

 

Распространённая ошибка конфигурации

 

Типичная попытка заблокировать порт контейнера:

sudo iptables -A INPUT -p tcp --dport 8080 -j DROP

 

Эта команда не работает по нескольким причинам:


 

Причина 1: Неправильная цепочка

 

Правило добавлено в цепочку INPUT, которая обрабатывает пакеты, предназначенные для самого хоста. Трафик в контейнеры проходит через цепочку FORWARD, поэтому правило просто не применяется.

 

Правильная попытка:

sudo iptables -A FORWARD -p tcp --dport 80 -d 172.17.0.2 -j DROP

Необходимо проверить и при необходимости изменить свой IP-адрес контейнера.

 

Но и это может не сработать из-за следующей причины.

 

Причина 2: Приоритет правил Docker

 

Docker использует операцию -I (insert) для добавления правил в начало цепочек. При использовании -A (append) пользовательское правило добавляется в конец, после правил Docker, которые уже разрешили трафик.

 

Просмотр цепочки FORWARD:

sudo iptables -L FORWARD -n --line-numbers

 

Типичный вывод:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 3

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 4
 

 

Правило DROP на позиции 99 бесполезно — трафик уже разрешён на позиции 4.

 

Причина 3: Конфликт бэкендов iptables

 

При использовании iptables-legacy на системе с активным nftables, правила добавляются в разные наборы:

  • Docker может использовать iptables-nft (если это дефолт системы)
  • Администратор добавляет правила через iptables-legacy

 

Проверка:

 

# Rules via nft backend
sudo iptables -L -n | wc -l

# Rules via legacy backend
sudo iptables-legacy -L -n | wc -l

# Native nftables rules
sudo nft list ruleset

 

Если значения различаются, системы работают параллельно, и правила одной не влияют на другую.

 

Причина 4: Перезапись правил при рестарте Docker

 

Docker пересоздаёт свои правила при:

  • Перезапуске демона Docker
  • Запуске/остановке контейнеров с публикацией портов
  • Создании/удалении сетей

 

Если пользовательские правила добавлены вручную, они могут быть удалены или "сдвинуты" вниз по приоритету. Для стабильности требуется либо использовать DOCKER-USER, либо автоматизировать восстановление правил (netfilter-persistent, скрипты).

 

Корректное решение

 

Не рекомендуется: вставка правил в начало цепочки FORWARD:

 

sudo iptables -I FORWARD 1 -p tcp --dport 80 -d 172.17.0.2 -j DROP

Необходимо проверить и при необходимости изменить свой IP-адрес контейнера.

 

Недостаток: при следующем запуске контейнера Docker вставит свои правила ещё раньше.

 

Рекомендуется: использование цепочки DOCKER-USER (описано в разделе "Надёжные способы защиты").


 

Практическая часть: как проверить, какие порты действительно открыты

 

Проверка слушающих портов на хосте

 

Команда ss (socket statistics) показывает активные сокеты:

 

sudo ss -lntp

 

Пример вывода:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 6

 

Интерпретация:

  • 0.0.0.0:8080 — порт доступен на всех сетевых интерфейсах (включая внешние)
  • 127.0.0.1:6379 — порт доступен только локально
  • docker-proxy — процесс, созданный Docker для проброса портов в userland (режим userland proxy)

 

Альтернатива с netstat:

 

sudo netstat -tulpn | grep docker-proxy


 

Анализ правил iptables

 

Таблица NAT (правила DNAT):

sudo iptables -t nat -L DOCKER -n --line-numbers

 

Пример вывода:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 7

 

Строка 2: любой TCP-трафик на порт 8080 хоста перенаправляется на 172.17.0.3:80.

 

Таблица FILTER (правила доступа):

sudo iptables -L DOCKER -n --line-numbers

 
Пример:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 8

 

Цепочка DOCKER-USER (пользовательские правила):

sudo iptables -L DOCKER-USER -n --line-numbers

 
По умолчанию (до добавления правил):

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 9

 

RETURN означает возврат в вызывающую цепочку без блокировки.


Анализ конфигурации контейнеров
 

Получение информации о проброшенных портах:

docker ps --format "table {{.Names}}\t{{.Ports}}"


Пример:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 10


Детальная информация о контейнере:

docker inspect webapp | jq '.[0].HostConfig.PortBindings'

 

Вывод:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 11

 

  • "HostIp": "0.0.0.0" - порт доступен на всех интерфейсах (небезопасно)
  • "HostIp": "127.0.0.1" - порт доступен только локально (безопасно)
  • Отсутствие PortBindings - контейнер в пользовательской сети без публикации портов

 
Проверка через nftables
 

Если система использует nftables:

sudo nft list ruleset | grep -A 10 "chain DOCKER"

 

Правила Docker будут в таблице ip filter или inet filter (для совместимости).

 
 
Практическая часть: проверка доступности портов извне
 

Локальное сканирование с другого хоста
 

Nmap — наиболее функциональный инструмент:

nmap -Pn -p 8080,3306,6379 203.0.113.50

Необходимо изменить IP адрес на свой сервер, и также указать необходимые Вам порты.

 

Параметры:

  • -Pn — пропустить ping-проверку (если ICMP заблокирован)
  • -p — список портов для сканирования

 

Пример вывода:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 12

 

Интерпретация:

  • open — порт открыт и отвечает
  • filtered — пакеты отбрасываются firewall (нет ответа)
  • closed — порт закрыт, сервис не слушает (RST-пакет от хоста)

 
Netcat
 — простая проверка доступности:

nc -vz 203.0.113.50 8080

Необходимо изменить IP адрес на свой сервер, и также указать необходимый Вам порт.

 

Вывод:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 13

 

Telnet — альтернатива netcat:

telnet 203.0.113.50 8080

Необходимо изменить IP адрес на свой сервер, и также указать необходимый Вам порт.

 

Успешное подключение:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 14

 

cURL — проверка HTTP-сервисов:

curl -v --connect-timeout 5 http://203.0.113.50:8080/

Необходимо изменить IP адрес на свой сервер, и также указать необходимый Вам порт.

 

Успешный ответ означает открытый порт с HTTP-сервисом.

 

Использование онлайн-сканеров
 

Для проверки из внешних сетей (если локальная проверка может дать ложные результаты из-за внутренних firewall-правил):

  • Can You See Me (canyouseeme.org) - простой веб-интерфейс
  • Shodan (shodan.io) - база проиндексированных портов (проверка через CLI: shodan host 203.0.113.50)
  • Censys (censys.io) - аналог Shodan

Внимание: использование внешних сканеров может нарушать политику провайдера или законодательство. Применять только для собственных серверов.


Интерпретация результатов
 

РезультатЗначениеДействие
Порт открыт снаружи, но должен быть закрытDocker публикует порт на 0.0.0.0Изменить привязку на 127.0.0.1 или добавить правила в DOCKER-USER
Порт фильтруется, но сервис должен быть доступенFirewall корректно работаетДобавить исключение для нужных IP
Порт закрыт, контейнер запущенКонтейнер в пользовательской сети без -pНормальное поведение для внутренних сервисов

 
 
Надёжные способы защиты и предотвращения утечек портов

 

Метод 1: Привязка портов к localhost
 

Наиболее простой и безопасный способ для сервисов, доступ к которым должен осуществляться через reverse proxy:

docker run -d -p 127.0.0.1:8080:80 nginx


В docker-compose.yml:

services:
  webapp:
    image: nginx
    ports:
      - "127.0.0.1:8080:80"

 

После применения:

sudo ss -lntp | grep :8080

 
Вывод:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 15


Порт доступен только локально. Внешний доступ настраивается через Nginx/Traefik/Caddy с SSL-терминацией и дополнительной защитой.

Применимость: веб-приложения, API, базы данных - любые сервисы, не требующие прямого доступа из интернета.

 
Метод 2: Использование цепочки DOCKER-USER
 

Docker гарантирует, что цепочка DOCKER-USER обрабатывается перед любыми правилами разрешения Docker. Это официальный механизм для добавления пользовательских правил.
 

Базовая конфигурация
 

Разрешить established/related соединения:

sudo iptables -I DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

 
Разрешить доступ с конкретной подсети:

sudo iptables -I DOCKER-USER -s 203.0.113.0/24 -j ACCEPT

Необходимо указать свой вариант подсети.


Разрешить конкретный порт контейнера с определённого IP:

sudo iptables -I DOCKER-USER -p tcp -s 198.51.100.50 -d 172.17.0.2 --dport 80 -j ACCEPT

Необходимо указать свой порт и соответствующий IP-адрес.


Заблокировать всё остальное:

sudo iptables -A DOCKER-USER -j DROP

 

Полный пример с логированием

#!/bin/bash

# Clear existing DOCKER-USER rules (except RETURN)
sudo iptables -F DOCKER-USER

# Established/Related
sudo iptables -I DOCKER-USER 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# Allow access from the office network
sudo iptables -I DOCKER-USER 2 -s 203.0.113.0/24 -j ACCEPT

# Allow access from monitoring servers
sudo iptables -I DOCKER-USER 3 -s 198.51.100.10 -j ACCEPT
sudo iptables -I DOCKER-USER 4 -s 198.51.100.11 -j ACCEPT

# Log blocked connections
sudo iptables -I DOCKER-USER 5 -m limit --limit 5/min -j LOG --log-prefix "DOCKER-BLOCK: " --log-level 4

# Default blocking
sudo iptables -A DOCKER-USER -j DROP

Необходимо указать свои варианты IP-адресов.


Проверка:

sudo iptables -L DOCKER-USER -n --line-numbers -v


Вывод:

 

Docker и сетевая безопасность Linux взаимодействие с iptables, UFW и nftables 16

 

Сохранение правил
 

Debian/Ubuntu:

sudo apt install iptables-persistent
sudo netfilter-persistent save

RHEL/CentOS:

sudo service iptables save

 

Альтернатива - скрипт в /etc/docker/docker-firewall.sh, вызываемый из systemd-юнита.

 
Метод 3: Пользовательские Docker-сети без публикации портов
 

Для микросервисной архитектуры, где сервисы общаются внутри кластера:

docker network create --driver bridge internal-net
docker run -d --name webapp --network internal-net nginx
docker run -d --name api --network internal-net python:app

 

Контейнеры доступны друг другу по именам (DNS внутри сети), но не имеют публичных портов. Доступ из интернета только через gateway-контейнер (Nginx, Traefik) с -p 127.0.0.1:443:443.


В docker-compose.yml:

version: '3.8'

services:
  webapp:
    image: nginx
    networks:
      - internal

  database:
    image: postgres
    networks:
      - internal

  gateway:
    image: traefik
    ports:
      - "127.0.0.1:443:443"
    networks:
      - internal

networks:
  internal:
    driver: bridge
    internal: false  # false for internet access, true for complete isolation

 

Метод 4: Отключение автоматического управления iptables
 

Внимание: требует глубоких знаний сетевой конфигурации. Не рекомендуется для большинства случаев.

 
Файл /etc/docker/daemon.json:

{
  "iptables": false
}


После применения:

sudo systemctl restart docker


Последствия:

  • Docker не создаёт правила NAT, маршрутизацию и изоляцию сетей
  • Необходимо вручную настроить DNAT/SNAT для каждого контейнера
  • Контейнеры теряют доступ в интернет без ручной настройки MASQUERADE

 

Пример ручной конфигурации:

# Enable IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# MASQUERADE for outgoing traffic
sudo iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

# DNAT for incoming traffic
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80

# Allow forwarding
sudo iptables -A FORWARD -d 172.17.0.2 -p tcp --dport 80 -j ACCEPT
sudo iptables -A FORWARD -s 172.17.0.2 -j ACCEPT


Применимость: специфичные сценарии с нестандартной сетевой архитектурой, использование внешних SDN-решений.

 
Метод 5: Комбинация UFW + DOCKER-USER
 

UFW можно использовать совместно с DOCKER-USER для управления доступом к хосту и контейнерам:

 

Файл /etc/ufw/after.rules (добавить в конец):

# Rules for Docker
*filter
:DOCKER-USER - [0:0]

# Allow established
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# Allow access from the office
-A DOCKER-USER -s 203.0.113.0/24 -j ACCEPT

# Logging
-A DOCKER-USER -m limit --limit 3/min -j LOG --log-prefix "UFW-DOCKER-BLOCK: "

# Blocking
-A DOCKER-USER -j DROP

COMMIT

 

Применение:

 

sudo ufw reload

 

UFW управляет доступом к хосту, DOCKER-USER - к контейнерам.

 
Метод 6: Мониторинг и автоматические проверки
 

Скрипт для проверки открытых портов (сохранить как /usr/local/bin/check-docker-ports.sh):

#!/bin/bash
TELEGRAM_BOT_TOKEN="your_token"
TELEGRAM_CHAT_ID="your_chat_id"

EXPOSED_PORTS=$(docker ps --format '{{.Ports}}' | grep -E '0\.0\.0\.0:[0-9]' || true)

if [ -n "$EXPOSED_PORTS" ]; then
    MESSAGE="⚠️ WARNING: Docker containers with exposed ports:%0A$EXPOSED_PORTS"
    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
         -d "chat_id=${TELEGRAM_CHAT_ID}" \
         -d "text=${MESSAGE}"
fi

Необходимо указать токен Вашего телеграм бота(TELEGRAM_BOT_TOKEN) и Ваш телеграм ID(TELEGRAM_CHAT_ID), чтобы получить оповещения от скрипта в свой Telegram-аккаунт.

 

Добавить в cron:

0 */6 * * * /usr/local/bin/check-docker-ports.sh


Мониторинг новых слушающих портов (сохранить в /usr/local/bin/port-monitor.sh):

#!/bin/bash

TELEGRAM_BOT_TOKEN="your_token"
TELEGRAM_CHAT_ID="your_chat_id"

BASELINE="/var/log/listening-ports-baseline.txt"

mkdir -p /var/log
touch "$BASELINE" 2>/dev/null || { echo "Error: sudo privileges required"; exit 1; }

CURRENT=$(docker ps --format '{{.Names}}: {{.Ports}}' | grep -E '(0\.0\.0\.0|\[::\])' | sort)

if [ -z "$CURRENT" ]; then
        echo "ℹ️  No Docker containers with exposed ports found at $(date)"
        exit 0
fi

if [ -f "$BASELINE" ] && [ -s "$BASELINE" ]; then
    DIFF=$(diff <(cat "$BASELINE") <(echo "$CURRENT"))
    
    if [ -n "$DIFF" ]; then
        MESSAGE="🚨 Docker port change:%0A%0A$DIFF%0A%0A⏰ $(date '+%Y-%m-%d %H:%M:%S')"
                    
        curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
            -d "chat_id=${TELEGRAM_CHAT_ID}" \
            -d "text=${MESSAGE}" > /dev/null
                            
        echo "⚠️  New listening ports detected at $(date)"
    else
        echo "✓ No changes detected"
    fi
else
    echo "📝 Creating baseline at $(date)"
fi

echo "$CURRENT" > "$BASELINE"

Необходимо указать свои данные в TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID.


Рекомендации по тестированию и CI/CD
 

Интеграция проверок безопасности в pipeline:

 

GitLab CI пример:

stages:
  - test

security-audit:
  stage: test
  image: alpine:latest
  before_script:
    - apk add --no-cache nmap docker-cli docker-compose grep bash
  variables:
    DEPLOY_HOST: "scanme.nmap.org"
  script:
    - |
      if grep -E '^\s*-\s*"[0-9]+:' docker-compose.yml | grep -v '127.0.0.1'; then
        echo "ERROR: Found port bindings without localhost restriction"
        exit 1
      fi
    - |
      nmap -Pn -p 1-10000 $DEPLOY_HOST | grep -E '^[0-9]+/tcp\s+open' > open_ports.txt
      if [ -s open_ports.txt ]; then
        echo "WARNING: Open ports detected:"
        cat open_ports.txt
      fi
  only:
    - main
    - production

Перед использованием измените DEPLOY_HOST на свой IP-адрес продакшена. Также проверьте названия веток в элементе only.

 

GitHub Actions пример:

name: Security Audit

on:
  push:
    branches: [main]
  pull_request:

jobs:
  docker-security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Check for unsafe port bindings
        run: |
          if grep -E '^\s*-\s*"[0-9]+:' docker-compose.yml | grep -v '127.0.0.1'; then
            echo "::error::Unsafe port binding detected in file (not bound to 127.0.0.1)"
            exit 1
          fi

 

Скрипт для автоматической проверки после деплоя:

#!/bin/bash
# deploy-security-check.sh

set -e

DEPLOY_HOST="${1:-localhost}"
EXPECTED_PORTS="${2:-443}"

echo "=== Running post-deploy security check on $DEPLOY_HOST ==="

# 1. Checking docker inspect on 0.0.0.0 bindings
echo "[*] Checking container port bindings..."
UNSAFE_BINDINGS=$(docker ps -q | xargs -I {} docker inspect {} \
  | jq -r '.[].HostConfig.PortBindings | to_entries[] | select(.value[].HostIp == "0.0.0.0") | .key' \
  | wc -l)

if [ "$UNSAFE_BINDINGS" -gt 0 ]; then
    echo "[!] ERROR: Found $UNSAFE_BINDINGS containers with 0.0.0.0 port bindings"
    docker ps -q | xargs -I {} docker inspect {} \
      | jq -r '.[].Name, .[].HostConfig.PortBindings | to_entries[] | select(.value[].HostIp == "0.0.0.0")'
    exit 1
fi

# 2. Checking DOCKER-USER rules
echo "[*] Checking DOCKER-USER chain..."
DOCKERUSER_RULES=$(sudo iptables -L DOCKER-USER -n | grep -c "DROP\|REJECT" || true)

if [ "$DOCKERUSER_RULES" -eq 0 ]; then
    echo "[!] WARNING: No blocking rules in DOCKER-USER chain"
fi

# 3. External port scanning
if [ "$DEPLOY_HOST" != "localhost" ]; then
    echo "[*] Scanning $DEPLOY_HOST for open ports..."
    OPEN_PORTS=$(nmap -Pn -p 1-10000 --open "$DEPLOY_HOST" 2>/dev/null \
      | grep -E '^[0-9]+/tcp\s+open' \
      | grep -v "$EXPECTED_PORTS" \
      | wc -l)
    
    if [ "$OPEN_PORTS" -gt 0 ]; then
        echo "[!] ERROR: Found unexpected open ports:"
        nmap -Pn -p 1-10000 --open "$DEPLOY_HOST" | grep -E '^[0-9]+/tcp\s+open'
        exit 1
    fi
fi

# 4. Checking docker-proxy processes on 0.0.0.0
echo "[*] Checking docker-proxy listeners..."
EXPOSED=$(sudo ss -lntp | grep docker-proxy | grep '0.0.0.0:' | grep -v '127.0.0.1:' || true)

if [ -n "$EXPOSED" ]; then
    echo "[!] ERROR: docker-proxy listening on 0.0.0.0:"
    echo "$EXPOSED"
    exit 1
fi

echo "[✓] Security check passed"


Использование в деплой-скрипте:

#!/bin/bash
# deploy.sh

docker-compose up -d
sleep 5
./deploy-security-check.sh $(hostname -I | awk '{print $1}') "443,22"

 

 
План действий и чек-лист для администраторов
 

Немедленные действия (первый аудит)
 

1. Инвентаризация открытых портов:

sudo ss -lntp | grep docker-proxy | grep '0.0.0.0:' | awk '{print $4}'
sudo docker ps --format 'table {{.Names}}\t{{.Ports}}'

2. Проверка правил iptables:

sudo iptables -t nat -L DOCKER -n
sudo iptables -L DOCKER-USER -n

3. Анализ конфигурации контейнеров:

docker ps -q | xargs docker inspect | jq '.[].HostConfig.PortBindings'

4. Внешнее сканирование (с другого хоста):

nmap -Pn -p 1-65535 your-server-ip

Вместо your-server-ip нужно указать IP-адрес своего сервера.

 
Краткосрочные меры 

 

5. Переконфигурация критичных сервисов:

  • Изменить биндинги на 127.0.0.1 для внутренних сервисов
  • Обновить docker-compose.yml файлы
  • Перезапустить контейнеры

6. Настройка DOCKER-USER:

  • Создать и применить скрипт с правилами
  • Протестировать доступность сервисов
  • Сохранить правила через netfilter-persistent

7. Настройка мониторинга:

  • Установить скрипты проверки портов
  • Добавить задачи в cron
  • Настроить алерты


Долгосрочные улучшения (постоянная практика)
 

8. Интеграция в CI/CD:

  • Добавить проверки в pipeline
  • Автоматизировать сканирование после деплоя
  • Документировать процедуры

9. Регулярный аудит (ежемесячно):

  • Проверка новых контейнеров
  • Обновление правил firewall
  • Анализ логов блокировок

10. Обучение команды:

  • Распространение best practices
  • Код-ревью docker-compose файлов
  • Обновление документации

 


Заключение и ключевые выводы

 

Docker существенно упрощает развёртывание приложений, но его прямая интеграция с netfilter требует глубокого понимания механизмов работы с iptables/nftables. Основные трудности безопасности возникают не из-за фундаментальных уязвимостей, а из-за неправильных конфигурационных предположений.

 

Ключевые принципы

 

  1. Docker НЕ обходит iptables - он активно использует netfilter, но управляет правилами на более высоком приоритете
  2. UFW работает на другом уровне - его правила применяются после NAT-трансляции Docker
  3. Порядок правил критичен - ручные правила должны добавляться в DOCKER-USER или через -I (insert)
  4. Привязка к localhost - простейший и надёжный способ защиты внутренних сервисов
  5. Регулярный аудит обязателен - конфигурация со временем дрейфует, необходим автоматический контроль


Рекомендуемый подход

 

Для production-окружений рекомендуется комбинированная стратегия:

  • Внутренние сервисы (БД, кеш, очереди) - привязка к 127.0.0.1 без исключений
  • Веб-приложения - доступ через reverse proxy (Nginx/Traefik) на localhost с SSL-терминацией
  • API и микросервисы - пользовательские Docker-сети без публикации портов
  • Внешний доступ - строгая фильтрация через DOCKER-USER по IP-адресам/подсетям
  • Мониторинг - автоматические проверки и алерты на изменения конфигурации

 

Финальная проверка

 

После применения защитных мер необходимо провести полный цикл проверок:

# 1. Local check
sudo ss -lntp | grep '0.0.0.0:' | grep docker-proxy

# 2. Check iptables
sudo iptables -L DOCKER-USER -n -v | grep DROP

# 3. Container check
docker ps -q | xargs docker inspect | jq -r '.[] | select(.HostConfig.PortBindings != null) | .Name, .HostConfig.PortBindings'

# 4. External scan
nmap -Pn -p- your-server-ip

Вместо your-server-ip нужно указать IP-адрес своего сервера.

 

Если все проверки показывают ожидаемые результаты, сетевая безопасность контейнерной инфраструктуры находится на приемлемом уровне. Однако защита - это непрерывный процесс, требующий регулярного пересмотра и обновления мер безопасности.