封包過濾與防火牆
在 Linux 上,Docker 會建立 iptables 和 ip6tables 規則,以實作網路隔離、連接埠發佈和過濾。
由於這些規則對於 Docker 橋接網路的正常運作至關重要,因此您不應修改 Docker 所建立的規則。
但是,如果您在暴露於網際網路的主機上執行 Docker,您可能希望新增 iptables 策略,以防止未經授權地存取容器或在主機上執行的其他服務。本頁面說明如何實現這一點,以及您需要注意的事項。
注意Docker 會為橋接網路建立
iptables規則。對於
ipvlan、macvlan或host網路,不會建立任何iptables規則。
Docker 與 iptables 鏈
在 filter 表中,Docker 會將預設策略設定為 DROP,並建立以下自訂 iptables 鏈
DOCKER-USER- 使用者定義規則的預留位置,這些規則將在
DOCKER-FORWARD和DOCKER鏈中的規則之前處理。
- 使用者定義規則的預留位置,這些規則將在
DOCKER-FORWARD- Docker 網路處理的第一階段。這些規則會將與已建立連線無關的封包傳遞到其他 Docker 鏈,並接受屬於已建立連線的封包。
DOCKER- 根據執行中容器的連接埠轉送設定,決定是否應接受不屬於已建立連線的封包的規則。
DOCKER-ISOLATION-STAGE-1和DOCKER-ISOLATION-STAGE-2- 用於將 Docker 網路彼此隔離的規則。
DOCKER-INGRESS- 與 Swarm 網路相關的規則。
在 FORWARD 鏈中,Docker 會新增無條件跳轉到 DOCKER-USER、DOCKER-FORWARD 和 DOCKER-INGRESS 鏈的規則。
在 nat 表中,Docker 會建立鏈 DOCKER 並新增規則,以實作偽裝 (masquerading) 和連接埠映射 (port-mapping)。
在 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,守護進程 (daemon) 會阻止存取未發佈的連接埠。已發佈的容器連接埠會映射到主機 IP 位址。為此,它使用 iptables 執行網路位址轉譯 (NAT)、連接埠位址轉譯 (PAT) 和偽裝 (masquerading)。
例如,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::2 和 2001: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.1 和 eth3 介面存取
$ 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 上運行的網頁伺服器,或直接透過 http://192.0.2.100:80 存取。如果連線到 vxlan.1 和 eth3 介面的網路上的遠端主機具有到 Docker 主機內部 192.0.2.0/24 網路的路由,它們也可以透過 http://192.0.2.100:80 存取網頁伺服器。
閘道模式
橋接網路驅動程式有以下選項
com.docker.network.bridge.gateway_mode_ipv6com.docker.network.bridge.gateway_mode_ipv4
這些選項都可以設定為其中一種閘道模式
natnat-unprotectedroutedisolated
預設為 nat,會為每個已發佈的容器連接埠設定 NAT 和偽裝規則。離開主機的封包將使用主機位址。
在 routed 模式下,不設定 NAT 或偽裝規則,但仍會設定 iptables 以確保只有已發佈的容器連接埠可存取。從容器發出的封包將使用容器的位址,而不是主機位址。
在 nat 模式下,當連接埠發佈到特定的主機位址時,該連接埠只能透過具有該位址的主機介面存取。因此,例如,將連接埠發佈到迴圈介面上的位址意味著遠端主機無法存取它。
然而,使用直接路由時,已發佈的容器連接埠總是可從遠端主機存取,除非 Docker 主機的防火牆有額外限制。本地第 2 層網路上的主機可以設定直接路由,而無需任何額外的網路設定。本地網路以外的主機只有在網路路由器配置為啟用直接路由的情況下,才能使用直接路由到容器。
在 nat-unprotected 模式下,未發佈的容器連接埠也可以使用直接路由存取,並且沒有設定連接埠過濾規則。此模式是為了與舊版預設行為相容而包含的。
閘道模式也會影響連接到同一主機上不同 Docker 網路的容器之間的通訊。
- 在
nat和nat-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,禁用 IPv4 對容器連接埠 80 的存取,請使用未指定的 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_forward 和 net.ipv6.conf.all.forwarding 在啟動時未啟用,它會啟用它們。當它這樣做時,它還會將 iptables FORWARD 鏈的策略設定為 DROP。
如果 Docker 將 FORWARD 鏈的策略設定為 DROP。這將阻止您的 Docker 主機充當路由器,這是啟用 IP 轉送時的建議設定。
若要阻止 Docker 將 FORWARD 鏈的策略設定為 DROP,請在 /etc/docker/daemon.json 中包含 "ip-forward-no-drop": true,或將選項 --ip-forward-no-drop 新增到 dockerd 命令列。
或者,您可以將 ACCEPT 規則新增到 DOCKER-USER 鏈中,以用於您要轉送的封包。例如
$ 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
在 守護進程設定 中,可以將 iptables 或 ip6tables 鍵設定為 false,但此選項不適用於大多數使用者。它可能會破壞 Docker Engine 的容器網路功能。
所有容器的所有連接埠都將可從網路存取,並且不會從 Docker 主機 IP 位址映射任何連接埠。
無法完全阻止 Docker 建立 iptables 規則,並且事後建立規則極其複雜,超出本說明的範圍。
與 firewalld 整合
如果您以 iptables 選項設定為 true 執行 Docker,並且您的系統上已啟用 firewalld,Docker 會自動建立一個名為 docker 的 firewalld 區域,目標為 ACCEPT。
Docker 建立的所有網路介面 (例如 docker0) 都會插入到 docker 區域中。
Docker 還會建立一個名為 docker-forwarding 的轉送策略,允許從任何區域轉送到 docker 區域。
Docker 與 ufw
Uncomplicated Firewall (ufw) 是 Debian 和 Ubuntu 隨附的前端,它讓您管理防火牆規則。Docker 和 ufw 使用 iptables 的方式使其彼此不相容。
當您使用 Docker 發佈容器的連接埠時,進出該容器的流量會在通過 ufw 防火牆設定之前被轉移。Docker 會在 nat 表中路由容器流量,這意味著封包在到達 ufw 使用的 INPUT 和 OUTPUT 鏈之前就被轉移。封包在防火牆規則應用之前就被路由,實際上會忽略您的防火牆配置。