Iptables

Docker 破壞 libvirt 橋接網路

  • June 18, 2019

這個問題快把我逼瘋了。我執行全新安裝的 Ubuntu 18.04,其中:

  • ufw 管理防火牆
  • br0 橋
  • lxd 和 libvirt (KVM)

我嘗試了股票 docker.io 包和包形成 docker 自己的 deb 儲存庫。

我希望能夠部署 docker 容器,選擇 ip 來綁定其埠(例如 -p 10.58.26.6:98800:98800),然後使用 UFW 打開埠。

但 docker 似乎創建了擾亂 br0 橋的 iptables 規則(例如,主機無法 ping libvirt 來賓)

我環顧四周,找不到好的安全意識解決方案。

手動iptables -I FORWARD -i br0 -o br0 -j ACCEPT操作似乎使一切正常。

docker daemon 的設置也"iptables": false允許網橋正常執行,但會破壞 docker 的容器出口網路。

通過編輯單個 UFW 的文件https://stackoverflow.com/a/51741599/1091772 ,我發現這個解決方案看起來很簡單,但它根本不起作用。

永久解決這個問題的最佳實踐和安全方法是什麼,在重新啟動後倖存下來?

編輯: 我最終-A ufw-before-forward -i br0 -o br0 -j ACCEPT/etc/ufw/before.rules送出之前添加。我可以將其視為解決方案還是不會引發一些問題?

問題,其實是一個特性:br_netfilter

從描述中,我相信唯一合乎邏輯的解釋是啟用了網橋 netfilter 程式碼:用於有狀態網橋防火牆或利用iptables的匹配和來自網橋路徑的目標,而不必(或能夠)複製它們在ebtables中。完全不考慮網路分層,網路第 2 層的乙太網橋程式碼現在呼叫在 IP 級別(即網路第 3 層)工作的iptables。它只能在全域範圍內啟用:對於主機和每個容器,或者沒有。一旦了解了正在發生的事情並知道要尋找什麼,就可以做出適當的選擇。

netfilter 項目描述了啟用br_netfilter時的各種ebtables/iptables互動。尤其令人感興趣的是第 7 節解釋了為什麼有時需要一些沒有明顯效果的規則來避免橋接路徑的意外影響,例如使用:

iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -d 172.16.1.0/24 -j ACCEPT
iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -j MASQUERADE

避免同一 LAN 上的兩個系統被網橋 NAT(參見下面的範例)。

你有幾個選擇來避免你的問題,但是如果你不想知道所有的細節,也不想驗證某些 iptables 規則(有時隱藏在其他命名空間中)是否會被破壞,那麼你所做的選擇可能是最好的:

  • 永久阻止載入br_netfilter模組。通常blacklist是不夠的,install必須使用。對於依賴br_netfilter的應用程序來說,這是一個容易出現問題的選擇:顯然是 Docker、Kubernetes、…
echo install br_netfilter /bin/true > /etc/modprobe.d/disable-br-netfilter.conf
  • 載入模組,但禁用其效果。對於iptables的效果是:
sysctl -w net.bridge.bridge-nf-call-iptables=0

如果將其放在啟動時,則應首先載入模組,否則此切換將不存在。

這兩個先前的選擇肯定會破壞iptables匹配-m physdevxt_physdev模組在載入時會自動載入br_netfilter模組(即使從容器添加的規則觸發了載入也會發生這種情況)。現在br_netfilter不會被載入,-m physdev可能永遠不會匹配。

  • 在需要時解決 br_netfilter 的效果,例如 OP:如第 7 節所述,在各種鏈(PREROUTING、FORWARD、POSTROUTING)中添加那些明顯的無操作規則。例如:
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -d 172.18.0.0/16 -j ACCEPT

iptables -A FORWARD -i br0 -o br0 -j ACCEPT

這些規則永遠不應該匹配,因為同一 IP LAN 中的流量不會被路由,除了一些罕見的 DNAT 設置。但是多虧了br_netfilter,它們確實匹配,因為它們首先被呼叫用於穿越橋的**交換幀(“升級”為 IP 數據包) 。然後再次呼叫它們以獲取通過路由器到達不相關介面的**路由數據包(但不會匹配)。

  • 不要將 IP 放在網橋上:將該 IP 放在veth介面的一端,另一端放在網橋上:這應該確保網橋不會與路由互動,但這不是大多數容器/VM 所做的常見的產品。
  • 您甚至可以將網橋隱藏在其自己的隔離網路名稱空間中(這只有在這次想要與其他ebtables規則隔離時才有用)。
  • 將所有內容都切換到nftables,其中規定的目標將避免這些橋樑互動問題。目前,橋接防火牆沒有可用的狀態支持,它仍然是WIP,但承諾在可用時會更乾淨,因為不會有任何“upcall”。

您應該搜尋觸發br_netfilter載入的原因(例如:) -m physdev,看看您是否可以避免它,以選擇如何繼續。


網路命名空間範例

讓我們使用網路命名空間重現一些效果。請注意,任何地方都不會使用任何*ebtables規則。*另請注意,此範例依賴於通常的 legacy iptables,而不是Debian buster 預設啟用的iptables over nftables 。

讓我們重現一個與許多容器使用類似的簡單案例:路由器 192.168.0.1/192.0.2.100 執行 NAT,後面有兩個主機:192.168.0.101 和 192.168.0.102,與路由器上的網橋相連。兩台主機可以通過網橋在同一個 LAN 上直接通信。

#!/bin/sh

for ns in host1 host2 router; do
   ip netns del $ns 2>/dev/null || :
   ip netns add $ns
   ip -n $ns link set lo up
done

ip netns exec router sysctl -q -w net.ipv4.conf.default.forwarding=1

ip -n router link add bridge0 type bridge
ip -n router link set bridge0 up
ip -n router address add 192.168.0.1/24 dev bridge0

for i in 1 2; do
   ip -n host$i link add eth0 type veth peer netns router port$i
   ip -n host$i link set eth0 up
   ip -n host$i address add 192.168.0.10$i/24 dev eth0
   ip -n host$i route add default via 192.168.0.1
   ip -n router link set port$i up master bridge0
done

#to mimic a standard NAT router, iptables rule voluntarily made as it is to show the last "effect"
ip -n router link add name eth0 type dummy
ip -n router link set eth0 up
ip -n router address add 192.0.2.100/24 dev eth0
ip -n router route add default via 192.0.2.1
ip netns exec router iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE

讓我們載入核心模組br_netfilter(以確保它不會稍後)並使用(非每個命名空間)切換bridge-nf-call-iptables禁用它的效果,僅在初始命名空間中可用:

modprobe br_netfilter
sysctl -w net.bridge.bridge-nf-call-iptables=0

警告:同樣,這可能會破壞iptables規則,例如-m physdev主機上的任何位置或依賴於載入和啟用br_netfilter的容器中的任何位置。

讓我們添加一些 icmp ping 流量計數器。

ip netns exec router iptables -A FORWARD -p icmp --icmp-type echo-request
ip netns exec router iptables -A FORWARD -p icmp --icmp-type echo-reply

讓我們ping:

# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.
64 bytes from 192.168.0.102: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 192.168.0.102: icmp_seq=2 ttl=64 time=0.058 ms

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1017ms
rtt min/avg/max/mdev = 0.047/0.052/0.058/0.009 ms

計數器不匹配:

# ip netns exec router iptables -v -S FORWARD
-P FORWARD ACCEPT -c 0 0
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 0 0
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 0 0

讓我們啟用bridge-nf-call-iptables並再次 ping:

# sysctl -w net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-iptables = 1
# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.
64 bytes from 192.168.0.102: icmp_seq=1 ttl=64 time=0.094 ms
64 bytes from 192.168.0.102: icmp_seq=2 ttl=64 time=0.163 ms

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1006ms
rtt min/avg/max/mdev = 0.094/0.128/0.163/0.036 ms

這次交換的數據包在 iptables 的過濾器/轉發鏈中得到了匹配:

# ip netns exec router iptables -v -S FORWARD
-P FORWARD ACCEPT -c 4 336
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 2 168
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 2 168

讓我們設置一個 DROP 策略(將預設計數器歸零)並再試一次:

# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1008ms

# ip netns exec router iptables -v -S FORWARD
-P FORWARD DROP -c 2 168
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 4 336
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 2 168

橋接程式碼通過 iptables 過濾交換的幀/數據包。讓我們像在 OP 中一樣添加繞過規則(這將使預設計數器再次歸零),然後再試一次:

# ip netns exec router iptables -A FORWARD -i bridge0 -o bridge0 -j ACCEPT
# ip netns exec host1 ping -n -c2 192.168.0.102
PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data.
64 bytes from 192.168.0.102: icmp_seq=1 ttl=64 time=0.132 ms
64 bytes from 192.168.0.102: icmp_seq=2 ttl=64 time=0.123 ms

--- 192.168.0.102 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1024ms
rtt min/avg/max/mdev = 0.123/0.127/0.132/0.012 ms

# ip netns exec router iptables -v -S FORWARD
-P FORWARD DROP -c 0 0
-A FORWARD -p icmp -m icmp --icmp-type 8 -c 6 504
-A FORWARD -p icmp -m icmp --icmp-type 0 -c 4 336
-A FORWARD -i bridge0 -o bridge0 -c 4 336 -j ACCEPT

讓我們看看在來自 host1 的 ping 期間在 host2 上實際收到的內容:

# ip netns exec host2 tcpdump -l -n -s0 -i eth0 -p icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
02:16:11.068795 IP 192.168.0.1 > 192.168.0.102: ICMP echo request, id 9496, seq 1, length 64
02:16:11.068817 IP 192.168.0.102 > 192.168.0.1: ICMP echo reply, id 9496, seq 1, length 64
02:16:12.088002 IP 192.168.0.1 > 192.168.0.102: ICMP echo request, id 9496, seq 2, length 64
02:16:12.088063 IP 192.168.0.102 > 192.168.0.1: ICMP echo reply, id 9496, seq 2, length 64

…而不是源 192.168.0.101。MASQUERADE 規則也從橋接路徑中呼叫。為避免這種情況,請在之前添加(如第 7 節的範例中所述)異正常則,或聲明一個非網橋傳出介面,如果可能的話(現在它可用,-m physdev如果它必須是網橋,您甚至可以使用它.. .)。


隨機相關:

LKML/netfilter-dev: br_netfilter: 在非初始 netns中啟用:這將有助於在每個命名空間而不是全域啟用此功能,從而限制主機和容器之間的互動。

netfilter-dev: netfilter: physdev: 放鬆 br_netfilter 依賴:僅僅試圖刪除一個不存在的physdev規則可能會產生問題。

netfilter-dev:對網橋的連接跟踪支持:WIP 網橋 netfilter 程式碼使用 nftables 準備有狀態的網橋防火牆,這一次更優雅。我認為擺脫 iptables (的核心端 API)的最後步驟之一。

引用自:https://serverfault.com/questions/963759