資料包過濾和防火牆

在 Linux 上,Docker 建立 iptablesip6tables 規則來實現網路隔離、埠釋出和過濾。

由於這些規則是 Docker 橋接網路正常執行所必需的,因此您不應修改 Docker 建立的規則。

但是,如果您在暴露於網際網路的主機上執行 Docker,您可能需要新增 iptables 策略,以防止未經授權的訪問容器或主機上執行的其他服務。本頁描述瞭如何實現這一點,以及您需要注意的警告。

注意

Docker 為橋接網路建立 iptables 規則。

不會為 ipvlanmacvlanhost 網路建立 iptables 規則。

Docker 和 iptables 鏈

filter 表中,Docker 將預設策略設定為 DROP,並建立以下自定義 iptables 鏈:

  • DOCKER-USER
    • 一個佔位符,用於使用者定義的規則,這些規則將在 DOCKER-FORWARDDOCKER 鏈中的規則之前處理。
  • DOCKER-FORWARD
    • Docker 網路的處理第一階段。規則將與已建立連線無關的資料包傳遞到其他 Docker 鏈,以及接受屬於已建立連線的資料包的規則。
  • DOCKER
    • 根據執行中容器的埠轉發配置,確定是否應接受不屬於已建立連線的資料包的規則。
  • DOCKER-ISOLATION-STAGE-1DOCKER-ISOLATION-STAGE-2
    • 用於隔離 Docker 網路的規則。
  • DOCKER-INGRESS
    • 與 Swarm 網路相關的規則。

FORWARD 鏈中,Docker 添加了無條件跳轉到 DOCKER-USERDOCKER-FORWARDDOCKER-INGRESS 鏈的規則。

nat 表中,Docker 建立 DOCKER 鏈並新增規則以實現偽裝和埠對映。

在 Docker 規則之前新增 iptables 策略

被這些自定義鏈中的規則接受或拒絕的資料包,將不會被附加到 FORWARD 鏈的使用者定義規則看到。因此,要新增額外的規則來過濾這些資料包,請使用 DOCKER-USER 鏈。

附加到 FORWARD 鏈的規則將在 Docker 的規則之後處理。

匹配請求的原始 IP 和埠

當資料包到達 DOCKER-USER 鏈時,它們已經通過了目標網路地址轉換 (DNAT) 過濾器。這意味著您使用的 iptables 標誌只能匹配容器的內部 IP 地址和埠。

如果您想根據網路請求中的原始 IP 和埠來匹配流量,您必須使用 conntrack iptables 擴充套件。例如:

$ sudo iptables -I DOCKER-USER -p tcp -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$ sudo iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdst 198.51.100.2 --ctorigdstport 80 -j ACCEPT
重要

使用 conntrack 擴充套件可能會導致效能下降。

埠釋出和對映

預設情況下,對於 IPv4 和 IPv6,守護程序會阻止對未釋出埠的訪問。釋出的容器埠會對映到主機 IP 地址。為此,它使用 iptables 執行網路地址轉換 (NAT)、埠地址轉換 (PAT) 和偽裝。

例如,docker run -p 8080:80 [...] 在 Docker 主機上任何地址的 8080 埠和容器的 80 埠之間建立了一個對映。來自容器的出站連線將使用 Docker 主機的 IP 地址進行偽裝。

限制到容器的外部連線

預設情況下,所有外部源 IP 地址都被允許連線到已釋出到 Docker 主機地址的埠。

要只允許特定的 IP 或網路訪問容器,請在 DOCKER-USER 過濾鏈的頂部插入一條否定規則。例如,以下規則會丟棄除 192.0.2.2 之外所有 IP 地址的資料包:

$ iptables -I DOCKER-USER -i ext_if ! -s 192.0.2.2 -j DROP

您需要將 ext_if 更改為您主機的實際外部介面。您也可以允許來自某個源子網的連線。以下規則只允許來自子網 192.0.2.0/24 的訪問:

$ iptables -I DOCKER-USER -i ext_if ! -s 192.0.2.0/24 -j DROP

最後,您可以使用 --src-range 指定一個 IP 地址範圍來接受(請記住,在使用 --src-range--dst-range 時也要新增 -m iprange):

$ iptables -I DOCKER-USER -m iprange -i ext_if ! --src-range 192.0.2.1-192.0.2.3 -j DROP

您可以將 -s--src-range-d--dst-range 結合使用,以同時控制源和目標。例如,如果 Docker 主機有 2001:db8:1111::22001:db8:2222::2 地址,您可以制定特定於 2001:db8:1111::2 的規則,並保持 2001:db8:2222::2 開放。

您可能需要允許來自允許的外部地址範圍之外的伺服器的響應。例如,容器可能會向不允許訪問容器服務的主機發送 DNS 或 HTTP 請求。以下規則接受任何屬於已被其他規則接受的流的入站或出站資料包。它必須放在限制來自外部地址範圍訪問的 DROP 規則之前。

$ iptables -I DOCKER-USER -m state --state RELATED,ESTABLISHED -j ACCEPT

iptables 很複雜。更多資訊請參見 Netfilter.org HOWTO

直接路由

埠對映確保已釋出的埠可以在主機的網路地址上訪問,這些地址很可能對任何外部客戶端都是可路由的。通常在主機的網路中不會為存在於主機內的容器地址設定路由。

但是,特別是在 IPv6 中,您可能更喜歡避免使用 NAT,而是安排外部路由到容器地址(“直接路由”)。

要從 Docker 主機外部訪問橋接網路中的容器,您必須首先透過 Docker 主機上的一個地址設定到橋接網路的路由。這可以透過靜態路由、邊界閘道器協議 (BGP) 或任何其他適合您網路的方式來實現。例如,在本地第 2 層網路中,遠端主機可以透過 Docker 守護程序主機在本地網路上的地址設定到容器網路的靜態路由。

直接路由到橋接網路中的容器

預設情況下,遠端主機不允許直接訪問 Docker Linux 橋接網路中的容器 IP 地址。它們只能訪問釋出到主機 IP 地址的埠。

要允許直接訪問任何 Linux 橋接網路中任何容器上的任何已釋出埠,請在 /etc/docker/daemon.json 中使用守護程序選項 "allow-direct-routing": true 或等效的 --allow-direct-routing

要允許從任何地方直接路由到特定橋接網路中的容器,請參閱 閘道器模式

或者,要允許透過特定主機介面直接路由到特定橋接網路,請在建立網路時使用以下選項:

  • com.docker.network.bridge.trusted_host_interfaces

示例

建立一個網路,其中容器 IP 地址上釋出的埠可以直接從介面 vxlan.1eth3 訪問:

$ docker network create --subnet 192.0.2.0/24 --ip-range 192.0.2.0/29 -o com.docker.network.bridge.trusted_host_interfaces="vxlan.1:eth3" mynet

在該網路中執行一個容器,將其 80 埠釋出到主機的環回介面上的 8080 埠:

$ docker run -d --ip 192.0.2.100 -p 127.0.0.1:8080:80 nginx

現在可以從 Docker 主機透過 http://127.0.0.1:8080 訪問容器 80 埠上執行的 Web 伺服器,或者直接透過 http://192.0.2.100:80 訪問。如果連線到介面 vxlan.1eth3 的網路上的遠端主機有到 Docker 主機內 192.0.2.0/24 網路的路由,它們也可以透過 http://192.0.2.100:80 訪問 Web 伺服器。

閘道器模式

橋接網路驅動程式具有以下選項:

  • com.docker.network.bridge.gateway_mode_ipv6
  • com.docker.network.bridge.gateway_mode_ipv4

這些選項中的每一個都可以設定為以下閘道器模式之一:

  • nat
  • nat-unprotected
  • routed
  • isolated

預設是 nat,為每個已釋出的容器埠設定 NAT 和偽裝規則。離開主機的資料包將使用主機地址。

使用 routed 模式,不設定 NAT 或偽裝規則,但仍然設定 iptables,以便只有已釋出的容器埠是可訪問的。來自容器的出站資料包將使用容器的地址,而不是主機地址。

nat 模式下,當一個埠被髮布到特定的主機地址時,該埠只能透過具有該地址的主機介面訪問。因此,例如,將埠釋出到環回介面上的地址意味著遠端主機無法訪問它。

然而,使用直接路由,已釋出的容器埠總是可以從遠端主機訪問,除非 Docker 主機的防火牆有額外的限制。本地第 2 層網路上的主機可以設定直接路由,而無需任何額外的網路配置。本地網路之外的主機只有在網路的路由器配置為啟用它時才能使用直接路由到容器。

nat-unprotected 模式下,未釋出的容器埠也可以使用直接路由訪問,不設定埠過濾規則。此模式是為了與舊的預設行為相容而包含的。

閘道器模式還影響連線到同一主機上不同 Docker 網路的容器之間的通訊。

  • natnat-unprotected 模式下,其他橋接網路中的容器只能透過它們釋出到的主機地址訪問已釋出的埠。不允許從其他網路進行直接路由。
  • routed 模式下,其他網路中的容器可以使用直接路由來訪問埠,而無需透過主機地址。

routed 模式下,-p--publish 埠對映中的主機埠不被使用,主機地址僅用於決定是將對映應用於 IPv4 還是 IPv6。因此,當對映僅適用於 routed 模式時,只應使用地址 0.0.0.0::,並且不應給出主機埠。如果給出了特定的地址或埠,它將對已釋出的埠沒有影響,並且會記錄一條警告訊息。

isolated 模式只能在網路同時使用 CLI 標誌 --internal 或等效標誌建立時使用。通常會為 internal 網路中的橋接裝置分配一個地址。因此,docker 主機上的程序可以訪問該網路,並且該網路中的容器可以訪問在該橋接地址上監聽的主機服務(包括在“任何”主機地址 0.0.0.0:: 上監聽的服務)。當網路以 isolated 閘道器模式建立時,不會為橋接裝置分配地址。

示例

建立一個適合 IPv6 直接路由的網路,併為 IPv4 啟用 NAT:

$ docker network create --ipv6 --subnet 2001:db8::/64 -o com.docker.network.bridge.gateway_mode_ipv6=routed mynet

建立一個帶有已釋出埠的容器:

$ docker run --network=mynet -p 8080:80 myimage

然後:

  • 只有容器的 80 埠會對 IPv4 和 IPv6 開放。
  • 對於 IPv6,使用 routed 模式,埠 80 將在容器的 IP 地址上開放。埠 8080 不會在主機的 IP 地址上開放,並且出站資料包將使用容器的 IP 地址。
  • 對於 IPv4,使用預設的 nat 模式,容器的 80 埠將可以透過主機 IP 地址上的 8080 埠訪問,也可以從 Docker 主機內部直接訪問。但是,容器的 80 埠不能從主機外部直接訪問。源自容器的連線將使用主機的 IP 地址進行偽裝。

docker inspect 中,此埠對映將如下所示。請注意,IPv6 沒有 HostPort,因為它使用的是 routed 模式:

$ docker container inspect <id> --format "{{json .NetworkSettings.Ports}}"
{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"},{"HostIp":"::","HostPort":""}]}

或者,要使對映僅適用於 IPv6,停用對容器 80 埠的 IPv4 訪問,請使用未指定的 IPv6 地址 [::] 並且不要包含主機埠號:

$ docker run --network mynet -p '[::]::80'

為容器設定預設繫結地址

預設情況下,當容器的埠對映沒有任何特定的主機地址時,Docker 守護程序會將已釋出的容器埠繫結到所有主機地址(0.0.0.0[::])。

例如,以下命令將埠 8080 釋出到主機上的所有網路介面,包括 IPv4 和 IPv6 地址,這可能會使其對外界可用。

docker run -p 8080:80 nginx

您可以更改已釋出容器埠的預設繫結地址,以便它們預設情況下只能由 Docker 主機訪問。為此,您可以將守護程序配置為使用環回地址 (127.0.0.1)。

警告

在 28.0.0 之前的版本中,同一 L2 網段內的主機(例如,連線到同一網路交換機的主機)可以訪問釋出到 localhost 的埠。有關更多資訊,請參閱 moby/moby#45610

要為使用者定義的橋接網路配置此設定,請在建立網路時使用 com.docker.network.bridge.host_binding_ipv4 驅動程式選項

$ docker network create mybridge \
  -o "com.docker.network.bridge.host_binding_ipv4=127.0.0.1"
注意
  • 將預設繫結地址設定為 :: 意味著未指定主機地址的埠繫結將適用於主機上的任何 IPv6 地址。但是,0.0.0.0 意味著任何 IPv4 或 IPv6 地址。
  • 更改預設繫結地址對 Swarm 服務沒有任何影響。Swarm 服務總是暴露在 0.0.0.0 網路介面上。

預設橋接網路

要為預設橋接網路設定預設繫結,請在 daemon.json 配置檔案中配置 "ip" 鍵:

{
  "ip": "127.0.0.1"
}

這將預設橋接網路上已釋出容器埠的預設繫結地址更改為 127.0.0.1。重啟守護程序以使此更改生效。或者,您可以在啟動守護程序時使用 dockerd --ip 標誌。

在路由器上執行 Docker

在 Linux 上,Docker 需要在主機上啟用“IP 轉發”。因此,如果 sysctl 設定 net.ipv4.ip_forwardnet.ipv6.conf.all.forwarding 尚未啟用,它會在啟動時啟用它們。當它這樣做時,它還會將 iptables FORWARD 鏈的策略設定為 DROP

如果 Docker 將 FORWARD 鏈的策略設定為 DROP,這將阻止您的 Docker 主機充當路由器,這是啟用 IP 轉發時的推薦設定。

要阻止 Docker 將 FORWARD 鏈的策略設定為 DROP,請在 /etc/docker/daemon.json 中包含 "ip-forward-no-drop": true,或在 dockerd 命令列中新增選項 --ip-forward-no-drop

或者,您可以為您想要轉發的資料包向 DOCKER-USER 鏈新增 ACCEPT 規則。例如:

$ iptables -I DOCKER-USER -i src_if -o dst_if -j ACCEPT
警告

在 28.0.0 之前的版本中,Docker 總是將 IPv6 FORWARD 鏈的預設策略設定為 DROP。在 28.0.0 及更新版本中,它只會在自己啟用 IPv6 轉發時才設定該策略。這一直是 IPv4 轉發的行為。

如果在 Docker 啟動前您的主機上啟用了 IPv6 轉發,請檢查您的主機配置以確保其仍然安全。

阻止 Docker 操作 iptables

可以在守護程序配置中將 iptablesip6tables 鍵設定為 false,但此選項不適合大多數使用者。這很可能會破壞 Docker 引擎的容器網路。

所有容器的所有埠都將可以從網路訪問,並且沒有一個會從 Docker 主機 IP 地址進行對映。

完全阻止 Docker 建立 iptables 規則是不可能的,事後建立規則非常複雜,超出了這些說明的範圍。

與 firewalld 整合

如果您在執行 Docker 時將 iptables 選項設定為 true,並且系統上啟用了 firewalld,Docker 會自動建立一個名為 dockerfirewalld 區域,其目標為 ACCEPT

Docker 建立的所有網路介面(例如,docker0)都會被插入到 docker 區域中。

Docker 還會建立一個名為 docker-forwarding 的轉發策略,允許從 ANY 區域轉發到 docker 區域。

Docker 和 ufw

Uncomplicated Firewall (ufw) 是 Debian 和 Ubuntu 附帶的前端,它允許您管理防火牆規則。Docker 和 ufw 使用 iptables 的方式使它們彼此不相容。

當您使用 Docker 釋出容器埠時,進出該容器的流量會在透過 ufw 防火牆設定之前被轉移。Docker 在 nat 表中路由容器流量,這意味著資料包在到達 ufw 使用的 INPUTOUTPUT 鏈之前就被轉移了。資料包在防火牆規則可以應用之前就被路由了,實際上忽略了您的防火牆配置。