iptables使用詳解(示例如何屏蔽docker 暴露的端口)

【場景】搭建了一臺CentOS虛擬機,並在上面搭了DOCKER,然後再DOCKER中安裝Mysql。但只要將網絡端口映射到宿主機上,那麼外部網絡就可以直接訪問該數據。爲此,我們需要使用防火牆(暫且不考慮mysql本身的賬號權限控制)。

Linux系統的防火牆

   說到防火牆,CentOS有FirewallD,Ubuntu有ufw 。它們的用法和語法不盡相同,但有一點卻是一致的,那就是他們底層都使用了iptables。 所以爲了在不同發行版的linux下都能安全管理我們的服務器,有必要學學這個iptables

   由於FirewallD和ufw本質都是基於iptables的,那麼它們都會在iptables中添加一些規則,甚至定義一些鏈,爲了不跟往後我們自己定義的規則相沖突,第一件事,便是停止並卸掉FirewallD和ufw對應的服務。

  • 停掉FirewallD
sudo systemctl stop firewalld //停止FirewallD
sudo systemctl disable firewalld //讓FirewallD 不要隨系統啓動而啓動
  • 停掉ufw
sudo ufw disable //停止並在系統啓動時不啓動ufw

iptables是什麼?

   iptables是一個linux下的防火牆工具,它能幫助我們基於規則進行網絡流量控制。它可以做到,但不限於以下功能:

  • 允許/拒絕某種協議的鏈接建立,比如TCP,UDP
  • 允許/拒絕 來自某個ip的訪問
  • 允許/拒絕某個端口被訪問
  • ......

表、鏈、規則

規則(rule)

例如:來自192.168.2.31的訪問,就要將其拒絕。這即是一條規則。

鏈(chain)

   往往我們的安全策略不只一條規則,除了 來自192.168.2.31的訪問,就要將其拒絕 這條規則之外,我們還有其它規則,比如: 來自192.168.43.22的訪問,也要將其拒絕

   甚至,我們可能還有多個互斥的規則,這多個規則,哪個規則先執行? 這就涉及到鏈這個概念。簡單來講,鏈就是將多個規則從上大小串起來的一個集合單位。規則按從上倒下依次進行匹配。
鏈條

表(table)

鏈條可以有多個。將多個鏈條再規整在一起的集合,叫做表。
規則鏈條集合

總覽

在iptables中,有四張表:

  • filter:這裏面的鏈條,規則,可以決定一個數據包是否可以到達目標進程端口
  • mangle: 這裏面的鏈條,規則,可以修改數據包的內容,比如ttl
  • nat:這裏面的鏈條,規則,可以修改源和目標的ip地址,從而進行包路由。
  • raw:這裏面的鏈條,規則,能基於數據包的狀態進行規則設定

上述四張表中,會內置一些鏈,且每個鏈,都有默認包處理策略,默認策略一般在鏈中的所有規則都沒匹配時生效。 filter表中的鏈有:

  • INPUT:對路由策略分派過來的包到達目標進程端口之前進行匹配並處理,後續會講到細節
  • FORWARD:對路由策略分派過來的包進行路由轉發,後續會講到細節
  • OUTPUT:判斷,從本地的目標進程端口處理好的包如何返回/要不要返回給請求方

【mangle表】中的鏈有:

PREROUTING:包在到達網口時,進行規則匹配
INPUT:含義同filter
FORWARD: 含義同filter
OUTPUT: 含義同filter
POSTROUTING: 包離開網口的時候匹配

【nat表】中的鏈有:

PREROUTING:含義同mangle
OUTPUT:含義同filter
POSTROUTING:含義同mangle

【raw表】中的鏈有:

PREROUTING:含義同mangle 
OUTPUT:含義同filter

   注意,雖然不同的表中有同名的鏈,但他們並不是同一個鏈,並且一個鏈只能引用同一個表中的鏈,不能跨表引用。平時我們的防火牆策略配置,即是在上述各個表的各個鏈中配置具體的規則。

規則生效順序

   雖然一個鏈中的規則是從上到下依次匹配,但多個表中的多個鏈,甚至同名鏈的之間的匹配優先順序是啥?這就要看下圖了:
規則生效順序

PREROUTING 鏈

   PREROUTING 鏈是最先生效的,當數據包到達網口時,即開始工作。同時由於其在raw, mangle, nat表中都存在,其執行的優先順序是:raw(PREROUTING) ----> mangle(PREROUTING)----> mangle(nat)

PREROUTING 一般用作對包進行目標地址修改。比如將該包的目標地址,修改爲非本機的另外的網絡ip,一般通過DNAT規則進行修改。

路由決策(Routing Decision)

   決定一個包該走哪個鏈。如果上述PREROUTING 鏈對包進行了目標網絡ip更改。那麼決策會覺得這個是一個需要轉發的數據包,於是會將該包轉發給 FORWARD 鏈。

否則, 該包會走INPUT鏈

FORWARD 鏈

   FORWARD在各表中生效的優先順序是:mangle(FORWARD) ----> filter(FORWARD) 處理路由決策派發發過來的包,到這裏的包一般目標網絡地址在PREROUTING鏈被修改過。

INPUT 鏈

   其生效順序是: mangle(INPUT) ----> filter(INPUT) 處理路由決策派發發過來的包,到這裏的包一般目標網絡地址在PREROUTING鏈沒有被修改過。

OUTPUT 鏈

   在目標進程端口接收到輸入數據包後,輸出的數據包,將在這裏進行規則應用。OUTPUT鏈在各表中生效的先後順序是: raw(OUTPUT) ----> mangle(OUTPUT) ----> nat(OUTPUT) ----> filter(OUTPUT)


規則詳解

   前面鋪墊了那麼多,主要講解了鏈的複雜生效時機,畢竟如果包最終都到不了這個鏈,那其中的規則配置也就沒有意義。接下來,我們需要講解,鏈中具體規則的設置和使用。

一個規則一般分爲兩大部分:

  1. 匹配: 即哪些數據包會命中這個規則,比如一個指定的ip,即是一個匹配規則
  2. 動作: 匹配到規則之後,需要做什麼動作,是放行,還是拒絕。
動作分爲以下幾種:
  • ACCEPT: 直接接受該數據包,不會再走其他鏈條和規則。比如filter中的input表中的某個規則命中後,動作是ACCEPT,那麼該數據包將被直接送達目標進程端口。
  • DROP: 直接拋棄該數據包,並且沒有任何返回。且不會再走其他鏈和規則
  • REJECT: 跟DROP類似,但好歹還是會跟請求方返回一些拒絕信息,比如我們拒絕掉ICMP協議後,ping該主機,會返回“destination host unreachable”
  • RETURN: 當前規則不做任何處理,返回。讓給下一個規則處理
  • LOG : 同RETURN類似,但只是會將請求信息記錄到系統日誌中,記錄路徑爲:/var/log/syslog or /var/log/messages

如何看某個表中有哪些鏈和規則?

iptables -t nat -nvL --line-numbers 

-t 表示想要查看哪個表,這裏查看的是nat表。iptables的所有命令,如果不指定-t,或者不寫默認是filter表。
-L 表示列出該表所有鏈和所有規則
-v 詳細顯示,會將規則匹配的進出網口也列出來
--line-numbers表示給規則進行編號處理。編號能方便我們後續對規則進行修改、刪除等操作。

如圖所示,表頭有以下信息:

  • num 表示當前規則編號,從1開始
  • in 表示該規則會匹配那些的輸入網口,如果包是由該網口輸入,則會被匹配
  • out 表示該規則會匹配的目標網口,如果包的目標網口是該網口,則會被匹配
  • source 表示該規則匹配的具體源ip範圍
  • destination 表示該規則匹配的具體目標ip範圍

總結來看,其實一個數據包本身就有源、目標的一些信息,而規則就是基於數據包本身屬性的特點進行規則設定。

在已知鏈末尾添加規則(舉例,拒絕某個ip的訪問)

iptables -t filter -A INPUT -s 59.45.175.62 -j REJECT 

-A 表示Append,其後緊跟的是鏈的名稱,表示該條規則要被添加到哪個鏈中。
-s 表示包的來源ip即source。除了指定固定的ip外,我們還可以指定ip範圍,比如59.45.175.0/24
-j 表示jump 也即是我們最終的動作,這裏的動作是拒絕。

在已知鏈鏈首插入規則

   鏈尾的規則匹配優先級最低,如果前面有規則被匹配後,並將數據包進行了終態處理(比如:ACCEPT, DROP, REJECT),那麼鏈尾的規則將永遠不會被使用。

如果我們想要該規則優先匹配,可以選擇將其放入鏈首,使用-I參數,表示insert。舉例:

iptables -t filter -I INPUT -s 59.45.175.62 -j REJECT 

刪除規則

想要刪除已配置的規則,可以使用-D參數。舉例:

iptables -t filter -D INPUT -s 59.45.175.62 -j REJECT 

   這種刪法,要我們明確知道當初添加進去的規則是怎麼寫的。如果忘了,我們可以通過規則編號進行刪除。在查看規則時使用參數 --line-numbers(例如:iptables -nvL --line-numbers),可以對規則進行編號,然後基於編號進行刪除。

iptables -t filter -D FOWARD 1 //表示刪除filter表中的FORWARD鏈的第一條規則 

清空某個鏈中的所有規則(慎用)

iptables -t filter -F INPUT

拒絕掉對某個ip的迴應

iptables -A OUTPUT -d 31.13.78.35 -j DROP
-d 表示destination,即所有返回給ip 31.13.78.35的數據包都直接丟掉,不迴應。

所有TCP協議的數據包,都丟棄

iptables -A INPUT -p tcp -j DROP
-p表示protocol

丟棄掉某個ip對端口22的訪問

iptables -A INPUT -p tcp -m tcp --dport 22 -s 59.45.175.0/24 -j DROP
由於要對端口進行精準匹配,所以先-m tcp 進行tcp module加載。

如何對多個端口進行匹配

iptables -A INPUT -p tcp -m multiport --dports 22,5901 -s 59.45.175.0/24 -j DROP
--dport 是某個端口
--dports 是逗號分隔多個端口

匹配指定鏈接狀態的數據包

鏈接狀態有以下幾種:

  • NEW:新創建的連接
  • ESTABLISHED 已經建立的連接
  • RELATED:跟已經創建的連接相關的連接
  • INVALID:非正常狀態
  • DNAT:如果一個連接其目標地址被nat表PREROUTING鏈中的規則修改了,即是這個狀態
  • SNAT:如果一個連接其源地址被nat表中的規則修改了,即是這個狀態

示例:iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 即只對已經建立的連接和由此產生的相關連接進行放行。

   有些版本的linux,對應的module不是conntrack,而是state。 對應指定狀態的參數不是ctstate 而是--state。所以,上述寫法在有些linux版本中需要替換成:
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
   一般來講,這些規則不可能單獨出現,如果都不允許任何NEW狀態連接建立,那哪來的已建立連接和相關連接?所以正確的做法一般是:

iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT //這條規則允許已經建立的連接和相關連接
iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT //新建鏈接如果是訪問22號端口,則允許訪問

改變某個鏈的默認規則

   一般每個鏈都有默認規則,即該鏈沒有任何規則或者沒有任何一條規則被匹配的情況下,對數據的放行策略是怎麼樣的。

Chain INPUT (policy ACCEPT)
...
Chain FORWARD (policy ACCEPT)
...
Chain OUTPUT (policy ACCEPT)
...

以filter表的三個鏈爲例,默認是ACCEPT。 但是我們可以改變這個模型規則,比如默認規則就是DROP:

iptables -t filter -P INPUT DROP 

迴環地址的訪問始終允許

iptables -t filter -A INPUT -i lo -j ACCEPT //在本地網絡通信的所有包,都放行

-i 表示input 輸入網口。lo表示本地的網絡接口。
   這裏沒有指定 -s地址(來源) 或 -d地址(目標),表示在迴環網絡上通信的所有端口都放行。這樣我們本機的web service,訪問本機的mysql數據庫纔不會有問題。當然一般INPUT的默認規則是ACCEPT,你不用配置上述的規則,只要沒有其它規則去限制,那麼本機迴環地址之間的端口通信也是放行的,除非你對INPUT鏈默認開啓了拒絕策略。

在使用某個網絡時,不響應請求

iptables -A OUTPUT -o wlan0 -d 121.18.238.0/29 -j DROP 

上述配置含義:所有發給目標網口是wlan0 且 目標ip是121.18.238.0/29 地址的包,都會被丟棄。 -o 表示 數據包的目標網口。

   在linux命令行中,使用 ifconfig ,就能看見當前已連接的所有網絡接口。

規則的取反配置

   上述規則配置,一般都是滿足某某條件,做什麼動作。除此之外,我們還可以配置,如果不滿足某某條件,則做某個動作。

iptables -A INPUT -p tcp -m multiport ! --dports 22,80,443 -j DROP 

   這個不滿足則的取動作,是通過感嘆號來實現的。 上述命令的含義是:非22,80,443的端口,我們直接丟棄。
   當然這條命令之前,應該要配置一條規則:

iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 

因爲通過22或80建立的連接,可能會衍生出一些RELATED的連接,他們的端口可能不是22或80,那樣也就被拒掉了。會導致通信出問題。

如何將規則持久化

   上述命令對iptable的操作,並不是永久生效的,機器重啓後,對應配置會丟失。如果需要持久化,則需要以下命令進行保存。
【centos系統】

  1. 第一步,在修改iptables配置後,將其導出到某個文件,比如: /etc/iptables.conf 。命令爲:iptables-save > /etc/iptables.conf 這一步,每次修改後都要做
  2. 第二步,在 /etc/rc.local 中添加命令 iptables-restore < /etc/iptables.conf。從此之後,每次重啓,系統會自動從 /etc/iptables.conf 恢復對應的iptables配置。這一步只需要做一次

【ubuntu系統】

  • 安裝iptables-persistent,它會在系統啓動時,從/etc/iptables/rules.v4 和 /etc/iptables/rules.v6分別加載ipv4 和ipv6的iptables 規則
sudo apt install iptables-persistent 

所以,每次我們對iptables進行了任何改動,使用下面的命令,將當前生效的iptables配置,導出到/etc/iptables/rules.v4 和 /etc/iptables/rules.v6即可

sudo iptables-save > /etc/iptables/rules.v4 //如果添加了ipv4 規則,執行這步
sudo ip6tables-save > /etc/iptables/rules.v6 //如果添加了ipv6規則,執行這步 

自定義鏈

   除了在現有的鏈中添加規則,我們也可以自定義鏈,自定義鏈可以幫助我們將一組規則收納在一起,方便我們管理。比如:

  1. 我們可以定義一個名爲ssh-rules的鏈來管理ssh登錄的一些規則
iptables -t filter -N ssh-rules 
  1. 在這個鏈中添加具體的規則:
iptables -t filter -A ssh-rules -s 18.130.0.0/16 -j ACCEPT
iptables -t filter -A ssh-rules -s 18.11.0.0/16 -j ACCEPT
iptables -t filter -A ssh-rules -j DROP
  1. 然後將該鏈作爲一個規則出口,掛在到iptable內置的鏈上。
iptables -A INPUT -p tcp -m tcp --dport 22 -j ssh-rules

以上含義就是在Input鏈中添加一個規則,所有22號端口的訪問,都會導向ssh-rules 再次強調: 只要不指定具體使用的表,默認都是filter表

刪除自定義鏈

當我們想要刪除自定義鏈時,使用命令:iptables -X ssh-rules


如何屏蔽docker 暴露的端口

   一般我們會在filter中的input鏈中,配置對某個端口的限制。但是在裝有docker的linux服務器上,docker暴露的任何端口,我們卻無法通過在filter表中的input鏈的規則進行限制,這是爲什麼呢? 我們通過上文的對整個iptables的工作機制,來拆解下原因。

訪問docker服務時,iptables的工作機制

   比如,我們在docker 中啓動一個mysql,暴露端口是3306。 docker宿主機所在ip: 192.168.31.102。docker 服務啓動的虛擬網段:172.17.0.1/16 , 啓動的mysql在該虛擬網絡的ip是: 172.17.0.2
docker映射端口3306
服務器網口信息:
機器網口
該機器真正的網口是enp0s3。 docker 啓動的虛擬網口是docker0
docker服務本身會在iptables中插入很多規則,甚至定義許多自定義化的鏈。
當我們我們在192.68.31.23 這臺機器上訪問192.168.31.102的3306端口時:

首先數據包被nat表中PREROUTING鏈命中

首先數據包被nat表中PREROUTING鏈命中
該鏈中的規則會被命中,同時將數據包導向nat表的DOCKER 鏈。

nat中的DOCKER鏈修改目標地址

nat中的DOCKER鏈修改目標地址

  • 第一條規則的輸入網口是docker0,顯然從192.168.31.102外部訪問該機器不可能走這個網口進入,因爲docker建立了虛擬網絡,不可能被外部訪問,所以第一條規則不會命中
  • 第二條規則的輸入網口是!docker0,表示非docker網絡,該規則能被匹配。由於mysql 實際安裝在docker內,如果最終要實現訪問,就要對請求包的目標地址進行修改,於是該條規則在匹配後的動作是:tcp dpt:3306 to:172.17.0.2:3306,即將目標網絡端口改成docker網段下的172.17.0.2:3306
路由決策到filter中的forward鏈

路由決策到filter中的forward鏈
由於prerouting對包進行了目標地址的修改,於是路由決策會將該包路由到forward鏈。所有表中的input 鏈將直接忽略。

  • forward鏈中的第一條規則,會應用於DOCKER-USER鏈。該鏈的規則是直接返回包。相當於第一條規則沒啓作用。直接會進入第二條規則進行匹配計算
  • 第二條鏈會包導入DOCKER-ISOLATION-STAGE-1鏈進行規則計算 ······ 一路鏈下去,最終只有圖中的規則③能命中,而該規則對包的處理方式,是RETURN,也即交給下個規則處理
  • 第三條規則是對目標網口是docker0的包進行匹配,按理說我們的包會匹配這條規則, 但是這條規則被匹配還有一個條件,就是包鏈接的狀態要是已建立的連接纔行,我們第一次從外部對數據庫進行訪問顯然不符合這個要求,於是該規則不會命中。進入第四條規則匹配
  • 第四條規則命中後,進入DOCKER 鏈從截圖可以看到,包到了這裏,被完美匹配。該包首先是一個非docker網絡到docker網絡的訪問,其次,其目標ip是172.17.0.2 的3306端口,匹配後,處理動作是ACCEPT。也即最終該訪問被響應,我們從外部網絡訪問到數據庫了。

docker服務無法被iptables限制問題總結及解決辦法

   說白了,由於數據包被更改了目標地址,於是路由策略將該包導向了FORWARD鏈。所以我們在INPUT鏈中再怎麼定義規則,都無法限制外網對docker服務的訪問。

   那解決辦法很簡單,既然包導向了FORWARD鏈,那麼在FORWARD鏈中添加攔路虎,不就得了嘛。DOCKER官方給的建議便是如此,比如,針對本文中的例子,我們可以添加如下規則,即可實現所有外部網絡都無法訪問docker中的服務:

iptables -I DOCKER-USER -i enp0s3 -j DROP

   規則含義是:所有從外部網絡進入的數據包,直接被丟棄。 DOCKER-USER鏈是上述FORWARD鏈中第一個規則匹配的到的鏈。 外部訪問的數據包,其輸入網口,肯定是enp0s3,因爲在本例中,它是對外通信的網口。 當然我們也可以在此,插入只允許某個網絡訪問,或某個網絡不能訪問的規則,不再贅述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章