Введение
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)
Порядок обработки правил
Критически важен порядок прохождения пакета через цепочки. Для входящего трафика:
PREROUTING(таблицаnat) - здесь Docker вставляет переход в цепочкуDOCKERперед любыми пользовательскими правиламиFORWARD(таблицаfilter) - проходит черезDOCKER-USER, затемDOCKER-ISOLATION, затемDOCKERPOSTROUTING(таблица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 добавляет свои правила в стандартные цепочки INPUT, OUTPUT и FORWARD.
Механизм обхода UFW
Когда Docker пробрасывает порт командой -p 8080:80, происходит следующее:
- 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 - Это правило применяется в цепочке
PREROUTING, до того как пакет попадает в цепочкуFORWARD, где работают правила UFW - После трансляции адреса пакет направляется в
FORWARD→DOCKER-USER→DOCKER - Правила UFW в
ufw-before-forwardиufw-after-forwardпроверяют уже изменённый адрес назначения (IP контейнера), а не исходный порт хоста
Результат: UFW показывает статус "порт закрыт", но трафик беспрепятственно проходит в контейнер через правила Docker в таблице nat.
Иллюстрация трудности:
UFW показывает закрытый порт:

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

Почему простое добавление правил в 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
Типичный вывод:

![]()
Правило 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
Пример вывода:

Интерпретация:
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
Пример вывода:

Строка 2: любой TCP-трафик на порт 8080 хоста перенаправляется на 172.17.0.3:80.
Таблица FILTER (правила доступа):
sudo iptables -L DOCKER -n --line-numbers
Пример:

Цепочка DOCKER-USER (пользовательские правила):
sudo iptables -L DOCKER-USER -n --line-numbers
По умолчанию (до добавления правил):

RETURN означает возврат в вызывающую цепочку без блокировки.
Анализ конфигурации контейнеров
Получение информации о проброшенных портах:
docker ps --format "table {{.Names}}\t{{.Ports}}"
Пример:

Детальная информация о контейнере:
docker inspect webapp | jq '.[0].HostConfig.PortBindings'
Вывод:

"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— список портов для сканирования
Пример вывода:

Интерпретация:
open— порт открыт и отвечаетfiltered— пакеты отбрасываются firewall (нет ответа)closed— порт закрыт, сервис не слушает (RST-пакет от хоста)
Netcat — простая проверка доступности:
nc -vz 203.0.113.50 8080Необходимо изменить IP адрес на свой сервер, и также указать необходимый Вам порт.
Вывод:
![]()
Telnet — альтернатива netcat:
telnet 203.0.113.50 8080Необходимо изменить IP адрес на свой сервер, и также указать необходимый Вам порт.
Успешное подключение:

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
Вывод:
![]()
Порт доступен только локально. Внешний доступ настраивается через 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
Вывод:

Сохранение правил
Debian/Ubuntu:
sudo apt install iptables-persistent
sudo netfilter-persistent saveRHEL/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 -n3. Анализ конфигурации контейнеров:
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. Основные трудности безопасности возникают не из-за фундаментальных уязвимостей, а из-за неправильных конфигурационных предположений.
Ключевые принципы
- Docker НЕ обходит iptables - он активно использует netfilter, но управляет правилами на более высоком приоритете
- UFW работает на другом уровне - его правила применяются после NAT-трансляции Docker
- Порядок правил критичен - ручные правила должны добавляться в
DOCKER-USERили через-I(insert) - Привязка к localhost - простейший и надёжный способ защиты внутренних сервисов
- Регулярный аудит обязателен - конфигурация со временем дрейфует, необходим автоматический контроль
Рекомендуемый подход
Для 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-адрес своего сервера.
Если все проверки показывают ожидаемые результаты, сетевая безопасность контейнерной инфраструктуры находится на приемлемом уровне. Однако защита - это непрерывный процесс, требующий регулярного пересмотра и обновления мер безопасности.