理解多區域配置中的 firewalld
現在的新聞里充斥著伺服器被攻擊和數據失竊事件。對於一個閱讀過安全公告博客的人來說,通過訪問錯誤配置的伺服器,利用最新暴露的安全漏洞或通過竊取的密碼來獲得系統控制權,並不是件多困難的事情。在一個典型的 Linux 伺服器上的任何互聯網服務都可能存在漏洞,允許未經授權的系統訪問。
因為在應用程序層面上強化系統以防範任何可能的威脅是不可能做到的事情,而防火牆可以通過限制對系統的訪問提供了安全保證。防火牆基於源 IP、目標埠和協議來過濾入站包。因為這種方式中,僅有幾個 IP/埠/協議的組合與系統交互,而其它的方式做不到過濾。
Linux 防火牆是通過 netfilter 來處理的,它是內核級別的框架。這十幾年來,iptables 被作為 netfilter 的用戶態抽象層(LCTT 譯註: userland,一個基本的 UNIX 系統是由 kernel 和 userland 兩部分構成,除 kernel 以外的稱為 userland)。iptables 將包通過一系列的規則進行檢查,如果包與特定的 IP/埠/協議的組合匹配,規則就會被應用到這個包上,以決定包是被通過、拒絕或丟棄。
Firewalld 是最新的 netfilter 用戶態抽象層。遺憾的是,由於缺乏描述多區域配置的文檔,它強大而靈活的功能被低估了。這篇文章提供了一個示例去改變這種情況。
Firewalld 的設計目標
firewalld 的設計者認識到大多數的 iptables 使用案例僅涉及到幾個單播源 IP,僅讓每個符合白名單的服務通過,而其它的會被拒絕。這種模式的好處是,firewalld 可以通過定義的源 IP 和/或網路介面將入站流量分類到不同 區域 。每個區域基於指定的準則按自己配置去通過或拒絕包。
另外的改進是基於 iptables 進行語法簡化。firewalld 通過使用服務名而不是它的埠和協議去指定服務,使它更易於使用,例如,是使用 samba 而不是使用 UDP 埠 137 和 138 和 TCP 埠 139 和 445。它進一步簡化語法,消除了 iptables 中對語句順序的依賴。
最後,firewalld 允許互動式修改 netfilter,允許防火牆獨立於存儲在 XML 中的永久配置而進行改變。因此,下面的的臨時修改將在下次重新載入時被覆蓋:
# firewall-cmd <some modification>
而,以下的改變在重載入後會永久保存:
# firewall-cmd --permanent <some modification>
# firewall-cmd --reload
區域
在 firewalld 中最上層的組織是區域。如果一個包匹配區域相關聯的網路介面或源 IP/掩碼 ,它就是區域的一部分。可用的幾個預定義區域:
# firewall-cmd --get-zones
block dmz drop external home internal public trusted work
任何配置了一個網路介面和/或一個源的區域就是一個 活動區域 。列出活動的區域:
# firewall-cmd --get-active-zones
public
interfaces: eno1 eno2
Interfaces (介面)是系統中的硬體和虛擬的網路適配器的名字,正如你在上面的示例中所看到的那樣。所有的活動的介面都將被分配到區域,要麼是默認的區域,要麼是用戶指定的一個區域。但是,一個介面不能被分配給多於一個的區域。
在預設配置中,firewalld 設置所有介面為 public 區域,並且不對任何區域設置源。其結果是,public
區域是唯一的活動區域。
Sources (源)是入站 IP 地址的範圍,它也可以被分配到區域。一個源(或重疊的源)不能被分配到多個區域。這樣做的結果是產生一個未定義的行為,因為不清楚應該將哪些規則應用於該源。
因為指定一個源不是必需的,任何包都可以通過介面匹配而歸屬於一個區域,而不需要通過源匹配來歸屬一個區域。這表示通過使用優先順序方式,優先到達多個指定的源區域,稍後將詳細說明這種情況。首先,我們來檢查 public
區域的配置:
# firewall-cmd --zone=public --list-all
public (default, active)
interfaces: eno1 eno2
sources:
services: dhcpv6-client ssh
ports:
masquerade: no
forward-ports:
icmp-blocks:
rich rules:
# firewall-cmd --permanent --zone=public --get-target
default
逐行說明如下:
public (default, active)
表示public
區域是默認區域(當介面啟動時會自動默認),並且它是活動的,因為,它至少有一個介面或源分配給它。interfaces: eno1 eno2
列出了這個區域上關聯的介面。sources:
列出了這個區域的源。現在這裡什麼都沒有,但是,如果這裡有內容,它們應該是這樣的格式 xxx.xxx.xxx.xxx/xx。services: dhcpv6-client ssh
列出了允許通過這個防火牆的服務。你可以通過運行firewall-cmd --get-services
得到一個防火牆預定義服務的詳細列表。ports:
列出了一個允許通過這個防火牆的目標埠。它是用於你需要去允許一個沒有在 firewalld 中定義的服務的情況下。masquerade: no
表示這個區域是否允許 IP 偽裝。如果允許,它將允許 IP 轉發,它可以讓你的計算機作為一個路由器。forward-ports:
列出轉發的埠。icmp-blocks:
阻塞的 icmp 流量的黑名單。rich rules:
在一個區域中優先處理的高級配置。default
是目標區域,它決定了與該區域匹配而沒有由上面設置中顯式處理的包的動作。
一個簡單的單區域配置示例
如果只是簡單地鎖定你的防火牆。簡單地在刪除公共區域上當前允許的服務,並重新載入:
# firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
# firewall-cmd --permanent --zone=public --remove-service=ssh
# firewall-cmd --reload
在下面的防火牆上這些命令的結果是:
# firewall-cmd --zone=public --list-all
public (default, active)
interfaces: eno1 eno2
sources:
services:
ports:
masquerade: no
forward-ports:
icmp-blocks:
rich rules:
# firewall-cmd --permanent --zone=public --get-target
default
本著儘可能嚴格地保證安全的精神,如果發生需要在你的防火牆上臨時開放一個服務的情況(假設是 ssh),你可以增加這個服務到當前會話中(省略 --permanent
),並且指示防火牆在一個指定的時間之後恢復修改:
# firewall-cmd --zone=public --add-service=ssh --timeout=5m
這個 timeout
選項是一個以秒(s
)、分(m
)或小時(h
)為單位的時間值。
目標
當一個區域處理它的源或介面上的一個包時,但是,沒有處理該包的顯式規則時,這時區域的 目標 決定了該行為:
ACCEPT
:通過這個包。%%REJECT%%
:拒絕這個包,並返回一個拒絕的回復。DROP
:丟棄這個包,不回復任何信息。default
:不做任何事情。該區域不再管它,把它踢到「樓上」。
在 firewalld 0.3.9 中有一個 bug (已經在 0.3.10 中修復),對於一個目標是除了「default」以外的源區域,不管允許的服務是什麼,這的目標都會被應用。例如,一個使用目標 DROP
的源區域,將丟棄所有的包,甚至是白名單中的包。遺憾的是,這個版本的 firewalld 被打包到 RHEL7 和它的衍生版中,使它成為一個相當常見的 bug。本文中的示例避免了可能出現這種行為的情況。
優先權
活動區域中扮演兩個不同的角色。關聯介面行為的區域作為介面區域,並且,關聯源行為的區域作為源區域(一個區域能夠扮演兩個角色)。firewalld 按下列順序處理一個包:
- 相應的源區域。可以存在零個或一個這樣的區域。如果這個包滿足一個 富規則 、服務是白名單中的、或者目標沒有定義,那麼源區域處理這個包,並且在這裡結束。否則,向上傳遞這個包。
- 相應的介面區域。肯定有一個這樣的區域。如果介面處理這個包,那麼到這裡結束。否則,向上傳遞這個包。
- firewalld 默認動作。接受 icmp 包並拒絕其它的一切。
這裡的關鍵信息是,源區域優先於介面區域。因此,對於多區域的 firewalld 配置的一般設計模式是,創建一個優先源區域來允許指定的 IP 對系統服務的提升訪問,並在一個限制性介面區域限制其它訪問。
一個簡單的多區域示例
為演示優先權,讓我們在 public
區域中將 http
替換成 ssh
,並且為我們喜歡的 IP 地址,如 1.1.1.1,設置一個默認的 internal
區域。以下的命令完成這個任務:
# firewall-cmd --permanent --zone=public --remove-service=ssh
# firewall-cmd --permanent --zone=public --add-service=http
# firewall-cmd --permanent --zone=internal --add-source=1.1.1.1
# firewall-cmd --reload
這些命令的結果是生成如下的配置:
# firewall-cmd --zone=public --list-all
public (default, active)
interfaces: eno1 eno2
sources:
services: dhcpv6-client http
ports:
masquerade: no
forward-ports:
icmp-blocks:
rich rules:
# firewall-cmd --permanent --zone=public --get-target
default
# firewall-cmd --zone=internal --list-all
internal (active)
interfaces:
sources: 1.1.1.1
services: dhcpv6-client mdns samba-client ssh
ports:
masquerade: no
forward-ports:
icmp-blocks:
rich rules:
# firewall-cmd --permanent --zone=internal --get-target
default
在上面的配置中,如果有人嘗試從 1.1.1.1 去 ssh
,這個請求將會成功,因為這個源區域(internal
)被首先應用,並且它允許 ssh
訪問。
如果有人嘗試從其它的地址,如 2.2.2.2,去訪問 ssh
,它不是這個源區域的,因為和這個源區域不匹配。因此,這個請求被直接轉到介面區域(public
),它沒有顯式處理 ssh
,因為,public 的目標是 default
,這個請求被傳遞到默認動作,它將被拒絕。
如果 1.1.1.1 嘗試進行 http
訪問會怎樣?源區域(internal
)不允許它,但是,目標是 default
,因此,請求將傳遞到介面區域(public
),它被允許訪問。
現在,讓我們假設有人從 3.3.3.3 拖你的網站。要限制從那個 IP 的訪問,簡單地增加它到預定義的 drop
區域,正如其名,它將丟棄所有的連接:
# firewall-cmd --permanent --zone=drop --add-source=3.3.3.3
# firewall-cmd --reload
下一次 3.3.3.3 嘗試去訪問你的網站,firewalld 將轉發請求到源區域(drop
)。因為目標是 DROP
,請求將被拒絕,並且它不會被轉發到介面區域(public
)。
一個實用的多區域示例
假設你為你的組織的一台伺服器配置防火牆。你希望允許全世界使用 http
和 https
的訪問,你的組織(1.1.0.0/16)和工作組(1.1.1.0/8)使用 ssh
訪問,並且你的工作組可以訪問 samba
服務。使用 firewalld 中的區域,你可以用一個很直觀的方式去實現這個配置。
public
這個命名,它的邏輯似乎是把全世界訪問指定為公共區域,而 internal
區域用於為本地使用。從在 public
區域內設置使用 http
和 https
替換 dhcpv6-client
和 ssh
服務來開始:
# firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
# firewall-cmd --permanent --zone=public --remove-service=ssh
# firewall-cmd --permanent --zone=public --add-service=http
# firewall-cmd --permanent --zone=public --add-service=https
然後,取消 internal
區域的 mdns
、samba-client
和 dhcpv6-client
服務(僅保留 ssh
),並增加你的組織為源:
# firewall-cmd --permanent --zone=internal --remove-service=mdns
# firewall-cmd --permanent --zone=internal --remove-service=samba-client
# firewall-cmd --permanent --zone=internal --remove-service=dhcpv6-client
# firewall-cmd --permanent --zone=internal --add-source=1.1.0.0/16
為容納你提升的 samba
的許可權,增加一個富規則:
# firewall-cmd --permanent --zone=internal --add-rich-rule='rule family=ipv4 source address="1.1.1.0/8" service name="samba" accept'
最後,重新載入,把這些變化拉取到會話中:
# firewall-cmd --reload
僅剩下少數的細節了。從一個 internal
區域以外的 IP 去嘗試通過 ssh
到你的伺服器,結果是回復一個拒絕的消息。它是 firewalld 默認的。更為安全的作法是去顯示不活躍的 IP 行為並丟棄該連接。改變 public
區域的目標為 DROP
,而不是 default
來實現它:
# firewall-cmd --permanent --zone=public --set-target=DROP
# firewall-cmd --reload
但是,等等,你不再可以 ping 了,甚至是從內部區域!並且 icmp (ping 使用的協議)並不在 firewalld 可以列入白名單的服務列表中。那是因為,icmp 是第 3 層的 IP 協議,它沒有埠的概念,不像那些捆綁了埠的服務。在設置公共區域為 DROP
之前,ping 能夠通過防火牆是因為你的 default
目標通過它到達防火牆的默認動作(default),即允許它通過。但現在它已經被刪除了。
為恢復內部網路的 ping,使用一個富規則:
# firewall-cmd --permanent --zone=internal --add-rich-rule='rule protocol value="icmp" accept'
# firewall-cmd --reload
結果如下,這裡是兩個活動區域的配置:
# firewall-cmd --zone=public --list-all
public (default, active)
interfaces: eno1 eno2
sources:
services: http https
ports:
masquerade: no
forward-ports:
icmp-blocks:
rich rules:
# firewall-cmd --permanent --zone=public --get-target
DROP
# firewall-cmd --zone=internal --list-all
internal (active)
interfaces:
sources: 1.1.0.0/16
services: ssh
ports:
masquerade: no
forward-ports:
icmp-blocks:
rich rules:
rule family=ipv4 source address="1.1.1.0/8" service name="samba" accept
rule protocol value="icmp" accept
# firewall-cmd --permanent --zone=internal --get-target
default
這個設置演示了一個三層嵌套的防火牆。最外層,public
,是一個介面區域,包含全世界的訪問。緊接著的一層,internal
,是一個源區域,包含你的組織,它是 public
的一個子集。最後,一個富規則增加到最內層,包含了你的工作組,它是 internal
的一個子集。
這裡的關鍵信息是,當在一個場景中可以突破到嵌套層,最外層將使用介面區域,接下來的將使用一個源區域,並且在源區域中額外使用富規則。
調試
firewalld 採用直觀範式來設計防火牆,但比它的前任 iptables 更容易產生歧義。如果產生無法預料的行為,或者為了更好地理解 firewalld 是怎麼工作的,則可以使用 iptables 描述 netfilter 是如何配置操作的。前一個示例的輸出如下,為了簡單起見,將輸出和日誌進行了修剪:
# iptables -S
-P INPUT ACCEPT
... (forward and output lines) ...
-N INPUT_ZONES
-N INPUT_ZONES_SOURCE
-N INPUT_direct
-N IN_internal
-N IN_internal_allow
-N IN_internal_deny
-N IN_public
-N IN_public_allow
-N IN_public_deny
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -j INPUT_ZONES_SOURCE
-A INPUT -j INPUT_ZONES
-A INPUT -p icmp -j ACCEPT
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -j REJECT --reject-with icmp-host-prohibited
... (forward and output lines) ...
-A INPUT_ZONES -i eno1 -j IN_public
-A INPUT_ZONES -i eno2 -j IN_public
-A INPUT_ZONES -j IN_public
-A INPUT_ZONES_SOURCE -s 1.1.0.0/16 -g IN_internal
-A IN_internal -j IN_internal_deny
-A IN_internal -j IN_internal_allow
-A IN_internal_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p udp -m udp --dport 137 -m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p udp -m udp --dport 138 -m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p tcp -m tcp --dport 139 -m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -s 1.1.1.0/8 -p tcp -m tcp --dport 445 -m conntrack --ctstate NEW -j ACCEPT
-A IN_internal_allow -p icmp -m conntrack --ctstate NEW -j ACCEPT
-A IN_public -j IN_public_deny
-A IN_public -j IN_public_allow
-A IN_public -j DROP
-A IN_public_allow -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
-A IN_public_allow -p tcp -m tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
在上面的 iptables 輸出中,新的鏈(以 -N
開始的行)是被首先聲明的。剩下的規則是附加到(以 -A
開始的行) iptables 中的。已建立的連接和本地流量是允許通過的,並且入站包被轉到 INPUT_ZONES_SOURCE
鏈,在那裡如果存在相應的區域,IP 將被發送到那個區域。從那之後,流量被轉到 INPUT_ZONES
鏈,從那裡它被路由到一個介面區域。如果在那裡它沒有被處理,icmp 是允許通過的,無效的被丟棄,並且其餘的都被拒絕。
結論
firewalld 是一個文檔不足的防火牆配置工具,它的功能遠比大多數人認識到的更為強大。以創新的區域範式,firewalld 允許系統管理員去分解流量到每個唯一處理它的分類中,簡化了配置過程。因為它直觀的設計和語法,它在實踐中不但被用於簡單的單一區域中也被用於複雜的多區域配置中。
via: https://www.linuxjournal.com/content/understanding-firewalld-multi-zone-configurations
作者:Nathan Vance 譯者:qhwdw 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive