sysadmin.ecommerce > bb-log-264-log-264-docker-vs-natywny-nftables-jak-przejac-cal.md
root@prod-01:~/blog# cat bb-log-264-log-264-docker-vs-natywny-nftables-jak-przejac-cal.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Data: 2026-06-17 | Kategoria: Bezpieczeństwo & Backup | Czas czytania: 8 min
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Docker vs natywny nftables: jak okiełznać sieci kontenerów bez dziur w zaporze

###02

Spis treści

  1. Wstęp – Gdzie podziały się moje reguły?
  2. Anatomia konfliktu, czyli jak Docker omija łańcuch input
  3. Droga na skróty czy pełna kontrola? Dwie strategie migracji
    • Strategia A: Przechwytywanie ruchu przed Dockerem (nftables z priorytetem ujemnym)
    • Strategia B: Pełna kontrola nftables ("iptables": false) i jej koszty
  4. Przygotowanie jądra Linuksa pod czysty routing kontenerów
  5. Produkcyjny, bezpieczny ruleset nftables (IPv6, conntrack i zaawansowane mapy portów)
  6. Diagnostyka i bezpieczne wdrażanie (Panic Switch)
  7. Podsumowanie

Wstęp – Gdzie podziały się moje reguły?

Większość administratorów Linuksa przechodzi przez ten sam bolesny proces: konfigurują restrykcyjny, elegancki firewall w nftables, po czym instalują Dockera, stawiają kontener np. z bazą danych i ze zdziwieniem odkrywają, że port 5432 jest w pełni dostępny z publicznego Internetu.

Docker traktuje systemowy firewall jak własne podwórko. Automatycznie wpina się w mechanizmy jądra, generuje własne tabele za pomocą warstwy kompatybilności iptables-nft i bezczelnie omija standardowe blokady. W tym artykule przeanalizujemy, dlaczego tak się dzieje i jak wdrożyć produkcyjne, bezpieczne rozwiązanie, które nie wyłoży sieci w Twoich kontenerach.

Anatomia konfliktu, czyli jak Docker omija łańcuch input

Problem wynika z architektury sieciowej jądra Linuksa. Kiedy pakiet z zewnątrz trafia na Twój interfejs sieciowy (np. eth0), a jego celem jest kontener, dla systemu operacyjnego nie jest to ruch lokalny. Maszyna hosta działa tutaj jak router.

W efekcie:

  • Pakiety całkowicie omijają łańcuch input.
  • Trafiają bezpośrednio do haków prerouting (gdzie Docker wykonuje DNAT, czyli przekierowanie portu na wirtualne IP kontenera) oraz forward.
  • Domyślnie Docker tworzy własne łańcuchy o najwyższych priorytetach (z poziomu iptables-nft, priorytet 0), przez co Twoje globalne reguły forward w nftables są ignorowane, chyba że wymusisz niższy priorytet.

Droga na skróty czy pełna kontrola? Dwie strategie migracji

Aby przejąć kontrolę, masz do wyboru dwie ścieżki. Wybór zależy od tego, jak bardzo cenisz automatyzację Dockera, a jak bardzo puryzm w konfiguracji firewalla.

CechaStrategia A: Przechwycenie przed DockeremStrategia B: "iptables": false
Poziom trudnościNiski (rekomendowany)Wysoki (tylko dla zaawansowanych)
Automatyzacja (--publish)Działa automatyczniePrzestaje działać (musisz pisać reguły DNAT ręcznie)
Wewnętrzny DNS DockeraDziała bezproblemowoWymaga ręcznej konfiguracji i potrafi się rwać
Sieci user-definedObsługiwane automatycznieWymagają dynamicznych zestawów (sets) w nftables

Strategia A: Przechwytywanie ruchu przed regułami Dockera (nftables z priorytetem ujemnym)

To najbezpieczniejsza metoda. Pozwalamy Dockerowi na zarządzanie NAT-em i sieciami w tle, ale wpinamy się przed jego regułami za pomocą łańcucha nftables z priorytetem niższym niż domyślny (0). Docker używa iptables-nft, które tworzy reguły w rodzinie ip (IPv4) w haku forward. Wystarczy dodać do swojego pliku nftables własny łańcuch w tej samej rodzinie z priorytetem -1.

bash

table ip filter {
    chain przed_dockerem {
        type filter hook forward priority -1;   # Wykonuje się przed regułami Dockera

        # Akceptuj ruch nawiązany i powrotny
        ct state { established, related } accept

        # Przykład: zezwól na dostęp do kontenerów tylko z konkretnego IP
        # ip saddr 198.51.100.50 accept

        # Odrzuć całą resztę nieautoryzowanego ruchu z zewnątrz do kontenerów
        iifname "eth0" drop
    }
}

Dlaczego to działa? Domyślny łańcuch forward iptables-nft ma priorytet 0. Nasz łańcuch z priorytetem -1 jest przetwarzany wcześniej – jeśli ruch zostanie odrzucony tutaj, Docker nigdy nie zobaczy pakietu i nie wykona dla niego DNAT. Dzięki temu możesz precyzyjnie decydować, które porty kontenerów są widoczne z zewnątrz, nie rezygnując z wygody -p.

Strategia B: Podejście radykalne ("iptables": false) i jego koszty

Jeśli chcesz mieć 100% pewności, że żadna ukryta automatyzacja nie otworzy portu za Twoimi plecami, całkowicie odetnij Dockera od podsystemu iptables jądra. W pliku /etc/docker/daemon.json ustawiamy:

json

{
  "iptables": false,
  "userland-proxy": false
}

Uwaga: Wyłączenie userland-proxy jest tu kluczowe. Bez tego flagi -p nadal próbowałyby działać przez proces pomocniczy w przestrzeni użytkownika, co drastycznie obniża wydajność sieci.

⚠️ Krytyczne konsekwencje (o czym musisz wiedzieć!):

  • Flaga -p (--publish) podczas uruchamiania kontenerów przestaje działać automatycznie – cały NAT musisz obsłużyć samodzielnie w nftables.
  • Automatyczny serwer DNS Dockera w sieciach typu bridge zostaje uszkodzony – kontenery mogą tracić rozwiązywanie nazw (adres 127.0.0.11 przestaje odpowiadać).
  • Izolacja sieci user-defined znika – musisz sam opisać relacje między wirtualnymi interfejsami (docker0br-*) w regułach firewalla.

Przygotowanie jądra Linuksa pod czysty routing kontenerów

Zanim załadujesz restrykcyjny ruleset (zwłaszcza dla Strategii B), musisz odpowiednio przygotować kernel. Bez włączenia przekazywania pakietów i obsługi filtrowania na mostkach kontenery zostaną całkowicie odcięte od świata.

Wykonaj poniższe polecenia i zapisz je na stałe w /etc/sysctl.d/99-docker-nft.conf:

bash

# 1. Włączenie forwardingu dla IPv4 i IPv6
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

# 2. Wyłączenie rp_filter (Reverse Path Filtering)
# Zapobiega odrzucaniu pakietów powrotnych z kontenerów w konfiguracjach multi-interface
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.default.rp_filter=0

# 3. Moduł bridge i przekazywanie reguł do iptables/nftables
modprobe br_netfilter
sysctl -w net.bridge.bridge-nf-call-iptables=1
sysctl -w net.bridge.bridge-nf-call-ip6tables=1

Produkcyjny, bezpieczny ruleset nftables (IPv6, conntrack i mapy portów)

Poniższy zaawansowany szablon /etc/nftables.conf rozwiązuje problem routingu, obsługuje sieci user-defined, chroni tablicę conntrack przed wysyceniem (DoS) oraz implementuje wieloportowe mapowanie kontenerów za pomocą natywnych map nftables.

bash

#!/sbin/nft -f
flush ruleset

define WAN_IF = "eth0"

table inet global_firewall {

    # Dynamiczny zestaw przechowujący podsieci Dockerowe (uzupełniany skryptem)
    set DOCKER_NETS {
        type ipv4_addr
        flags interval
        elements = { 172.17.0.0/16, 172.18.0.0/16 }
    }

    # Opcjonalna ochrona Connection Tracking przed wysyceniem
    chain prerouting {
        type filter hook prerouting priority -300;
        # ct count > 100000 counter drop
    }

    chain wejscie {
        type filter hook input priority 0; policy drop;
        iifname "lo" accept
        ct state { established, related } accept
        tcp dport 22 accept
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept
    }

    chain przekazywanie {
        type filter hook forward priority 0; policy drop;

        # 1. Kontenery swobodnie wychodzą do Internetu
        oifname $WAN_IF ip saddr @DOCKER_NETS accept

        # 2. Odpowiedzi z Internetu wracają do kontenerów
        iifname $WAN_IF ct state { established, related } accept

        # 3. Ruch wewnątrzmostkowy (docker0 i sieci user-defined)
        iifname { "docker0", "br-*" } oifname { "docker0", "br-*" } accept

        # 4. Limit otwartych połączeń na pojedynczy kontener (Anti-DoS)
        ip saddr @DOCKER_NETS ct count over 65535 counter drop
    }

    chain wyjscie {
        type filter hook output priority 0; policy accept;
    }
}

table ip nat_firewall {
    # Priorytet -100 jest standardem dla DNAT (po conntracku)
    chain translacja_przed {
        type nat hook prerouting priority -100;

        # ZAAWANSOWANE MAPOWANIE PORTÓW (odpowiednik wielu flag -p)
        # Mapujemy: port publiczny hosta → IP kontenera . port docelowy kontenera
        dnat to tcp dport map {
            8080 : 172.17.0.2 . 80,
            9000 : 172.18.0.5 . 9000
        }
    }

    chain translacja_po {
        type nat hook postrouting priority 100;

        # Maskarada dla sieci Dockera przy wyjściu przez interfejs WAN
        ip saddr @DOCKER_NETS oifname $WAN_IF masquerade
    }
}

Automatyzacja dla dynamicznych sieci (skrypt Bash)
Jeżeli Twoje środowisko często tworzy nowe sieci, użyj skryptu, który dynamicznie aktualizuje zestaw DOCKER_NETS. Wymaga narzędzia jq.

bash

#!/bin/bash
if ! command -v jq &> /dev/null; then
    echo "Błąd: 'jq' jest wymagany do działania skryptu." >&2
    exit 1
fi

if ! systemctl is-active --quiet docker; then
    echo "Docker jest wyłączony. Pomijam aktualizację nftables."
    exit 0
fi

for subnet in $(docker network ls -q | xargs docker network inspect | jq -r '.[].IPAM.Config[].Subnet' 2>/dev/null); do
    if [[ $subnet =~ ^[0-9/.]+ ]]; then
        nft add element inet global_firewall DOCKER_NETS { $subnet } 2>/dev/null
    fi
done

Diagnostyka i bezpieczne wdrażanie (Panic Switch)

Zabawa z nftables na żywym organizmie przez SSH grozi widowiskowym odcięciem się od serwera. Oto Twój żelazny plan awaryjny:

1. Przełącznik bezpieczeństwa (Panic Switch)
Przed załadowaniem nowego pliku konfiguracyjnego uruchom zadanie at, które automatycznie przywróci dostęp po 10 minutach:

bash

sudo systemctl at now + 10 minutes <<< "nft flush ruleset; systemctl restart nftables"

Jeśli wszystko pójdzie dobrze i nie stracisz sesji SSH, anuluj zadanie komendą atrm.

2. Logowanie pakietów – gdzie podział się mój ruch?
Gdy kontenery tracą łączność, nie zgaduj. Wstrzyknij tymczasową regułę logującą tuż przed regułą drop:

bash

nft add rule inet global_firewall przekazywanie log prefix "DOCKER-DROP: " drop

Logi w journalctl -f lub /var/log/syslog natychmiast pokażą nagłówki pakietu, który został zablokowany – dowiesz się, czy przyczyną jest błędny interfejs, nieobjęta podsieć czy źle skonfigurowany priorytet.

Podsumowanie

Całkowite oddanie kontroli nad siecią nftables przez "iptables": false wymaga głębokiej znajomości mechanizmów jądra, takich jak rp_filter czy kolejność priorytetów netfiltera. Dla 90% wdrożeń produkcyjnych przechwytywanie ruchu łańcuchem nftables o ujemnym priorytecie (Strategia A) jest najlepszym, najbezpieczniejszym kompromisem – zachowujesz pełną kontrolę nad zewnętrznym dostępem do kontenerów, nie rezygnując z wygody --publish i wbudowanego DNS Dockera. Jeśli jednak wybierasz drogę całkowitego puryzmu (Strategia B), powyższy szablon zapewni Ci bezpieczeństwo klasy korporacyjnej – bez kompromisów i bez niespodzianek ze strony automatyki Dockera.

← Powrot do wszystkich artykulow

// alert_systemowy

Widzisz podobne symptomy na swoim serwerze e-commerce? Nie czekaj na awarię w szczycie ruchu.

→ Przejdź do formularza i zgłoś serwer do audytu zerowego