資料包過濾和防火牆
在 Linux 上,Docker 會建立 iptables
和 ip6tables
規則來實現網路隔離、埠釋出和過濾。
由於這些規則對於 Docker bridge 網路的正常執行至關重要,您不應修改 Docker 建立的規則。
但是,如果您在暴露於網際網路的主機上執行 Docker,您可能希望新增 iptables 策略來阻止未經授權的訪問容器或主機上執行的其他服務。本頁面描述瞭如何實現這一點,以及您需要注意的注意事項。
注意
Docker 會為 bridge 網路建立
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
並新增規則以實現 IP 偽裝和埠對映。
在 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) 和 IP 偽裝。
例如,docker run -p 8080:80 [...]
會在 Docker 主機上任意地址的埠 8080 與容器的埠 80 之間建立對映。從容器發出的連線將進行 IP 偽裝,使用 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
開放。
iptables
很複雜。您可以在 Netfilter.org HOWTO。
直接路由
埠對映確保已釋出的埠可以在主機的網路地址上訪問,這些地址對於任何外部客戶端通常都是可路由的。通常不會在主機網路中為存在於主機內的容器地址設定路由。
但是,尤其是在使用 IPv6 時,您可能更願意避免使用 NAT,而是安排外部路由到容器地址(“直接路由”)。
要從 Docker 主機外部訪問 bridge 網路上的容器,您必須透過 Docker 主機上的一個地址設定到 bridge 網路的路由。這可以透過靜態路由、邊界閘道器協議 (BGP) 或適用於您網路的任何其他方式實現。
在本地的第 2 層網路中,遠端主機可以使用 Docker daemon 主機在本地網路上的地址設定到容器網路的靜態路由。這些主機可以直接訪問容器。對於本地網路之外的遠端主機,直接訪問容器需要路由器配置以啟用必要的路由。
閘道器模式
bridge 網路驅動程式有以下選項:
com.docker.network.bridge.gateway_mode_ipv6
com.docker.network.bridge.gateway_mode_ipv4
每個選項都可以設定為以下閘道器模式之一:
nat
nat-unprotected
routed
isolated
預設模式是 nat
,會為每個已釋出的容器埠設定 NAT 和 IP 偽裝規則。離開主機的資料包將使用主機地址。
在 routed
模式下,不會設定 NAT 或 IP 偽裝規則,但仍會設定 iptables
以便只有已釋出的容器埠可訪問。從容器發出的資料包將使用容器的地址,而不是主機地址。
在 nat
模式下,當埠釋出到特定主機地址時,該埠只能透過具有該地址的主機介面訪問。因此,例如,將埠釋出到迴環介面上的地址意味著遠端主機無法訪問它。
然而,使用直接路由,已釋出的容器埠總是可以從遠端主機訪問,除非 Docker 主機的防火牆有額外的限制。本地第 2 層網路上的主機可以在無需任何額外網路配置的情況下設定直接路由。本地網路之外的主機只有在網路路由器配置為啟用直接路由時才能使用直接路由訪問容器。
在 nat-unprotected
模式下,未釋出的容器埠也可以使用直接路由訪問,不會設定埠過濾規則。包含此模式是為了相容舊的預設行為。
閘道器模式也影響連線到同一主機上不同 Docker 網路的容器之間的通訊。
- 在
nat
和nat-unprotected
模式下,其他 bridge 網路中的容器只能透過已釋出到的主機地址訪問已釋出的埠。不允許從其他網路進行直接路由。 - 在
routed
模式下,其他網路中的容器可以使用直接路由訪問埠,而無需透過主機地址。
在 routed
模式下,-p
或 --publish
埠對映中的主機埠不使用,主機地址僅用於決定是將對映應用於 IPv4 還是 IPv6。因此,當對映僅適用於 routed
模式時,應只使用地址 0.0.0.0
或 ::
,並且不應指定主機埠。如果指定了特定地址或埠,它對已釋出的埠無效,並且會記錄警告訊息。
isolated
模式只能在建立網路時同時使用 CLI 標誌 --internal
或其等效選項時使用。通常會在 internal
網路中的 bridge 裝置上分配一個地址。因此,docker 主機上的程序可以訪問該網路,網路中的容器可以訪問在該 bridge 地址上監聽的主機服務(包括監聽“任意”主機地址 0.0.0.0
或 ::
的服務)。當以閘道器模式 isolated
建立網路時,不會為 bridge 分配地址。
示例
建立適用於 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
模式,容器的 IP 地址上會開啟埠 80。主機的 IP 地址上不會開啟埠 8080,發出的資料包將使用容器的 IP 地址。 - 對於 IPv4,使用預設的
nat
模式,可以透過主機 IP 地址上的埠 8080 以及直接訪問容器的埠 80。源自容器的連線將進行 IP 偽裝,使用主機的 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 daemon 會將已釋出的容器埠繫結到所有主機地址(0.0.0.0
和 [::]
)。
例如,以下命令會將埠 8080 釋出到主機上的所有網路介面,包括 IPv4 和 IPv6 地址,可能使其可供外部訪問。
docker run -p 8080:80 nginx
您可以更改已釋出容器埠的預設繫結地址,使其預設情況下只能由 Docker 主機訪問。為此,您可以配置 daemon 使用迴環地址(127.0.0.1
)代替。
警告
在早於 28.0.0 的版本中,同一 L2 網段內的主機(例如,連線到同一網路交換機的主機)可以訪問釋出到 localhost 的埠。更多資訊請參閱 moby/moby#45610
要為使用者定義的 bridge 網路配置此設定,請在建立網路時使用 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
網路介面上。
預設 bridge 網路
要為預設 bridge 網路設定預設繫結,請在 daemon.json
配置檔案中配置 "ip"
鍵:
{
"ip": "127.0.0.1"
}
這將把預設 bridge 網路上已釋出容器埠的預設繫結地址更改為 127.0.0.1
。重啟 daemon 以使此更改生效。或者,您可以在啟動 daemon 時使用 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 及更新版本中,僅當 Docker 自身啟用 IPv6 轉發時,才會設定該策略。IPv4 轉發的行為一直如此。如果在 Docker 啟動之前在主機上啟用了 IPv6 轉發,請檢查主機的配置以確保其仍然安全。
阻止 Docker 操作 iptables
可以將 守護程序配置 中的 iptables
或 ip6tables
鍵設定為 false
,但此選項不適合大多數使用者。這可能會破壞 Docker Engine 的容器網路。
所有容器的所有埠都將可從網路訪問,並且沒有埠會對映到 Docker 主機 IP 地址。
完全阻止 Docker 建立 iptables
規則是不可能的,事後建立規則極其複雜,並且超出了本說明的範圍。
與 firewalld 整合
如果您執行 Docker 時將 iptables
選項設定為 true
,並且系統上啟用了 firewalld,Docker 會自動建立一個名為 docker
的 firewalld 區域,其目標為 ACCEPT
。
Docker 建立的所有網路介面(例如 docker0
)都會插入到 docker
區域中。
Docker 還會建立一個名為 docker-forwarding
的轉發策略,該策略允許從 ANY
區域轉發到 docker
區域。
Docker 和 ufw
Uncomplicated Firewall (ufw) 是 Debian 和 Ubuntu 自帶的一個前端,可讓您管理防火牆規則。Docker 和 ufw 使用 iptables 的方式相互不相容。
當您使用 Docker 釋出容器埠時,進出該容器的流量會在經過 ufw 防火牆設定之前被轉移。Docker 在 nat
表中路由容器流量,這意味著資料包在到達 ufw 使用的 INPUT
和 OUTPUT
鏈之前就被轉移了。資料包在防火牆規則應用之前就被路由,從而有效地忽略了您的防火牆配置。