iptables使用詳解

2022-10-16 12:02:59

iptables使用詳解

@(linux)[iptables]

前言

最近買了一個VPS,並在上面搭了DOCKER,然後再DOCKER中安裝Mysql。但只要將網路埠對映到宿主機上,那麼外部網路就可以直接存取該資料。屬實嚇人。為此,我們需要使用防火牆。

說到防火牆,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)

規則詳解

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

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

  • 匹配: 即哪些封包會命中這個規則,比如一個指定的ip,即是一個匹配規則
  • 動作: 匹配到規則之後,需要做什麼動作,是放行,還是拒絕。

動作分為以下幾種:

  • 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鏈的第一條規則

拒絕掉對某個ip的迴應

iptables -A OUTPUT -d 31.13.78.35 -j DROP

-d 表示destination,即所有返回給ip 31.13.78.35的封包都直接丟掉,不迴應。

清空某個鏈中的所有規則

iptables -t filter -F INPUT

所有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

匹配指定連結狀態的封包

連結狀態有以下幾種:

  • 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

該機器真正的網口是enp0s3。 docker 啟動的虛擬網口是docker0

docker服務本身會在iptables中插入很多規則,甚至定義許多自定義化的鏈

當我們我們在192.68.31.23 這臺機器上存取192.168.31.102的3306埠時。

首先封包被nat表中PREROUTING鏈命中

該鏈中的規則會被命中,同時將封包導向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鏈

由於prerouting對包進行了目標地址的修改,於是路由決策會將該包路由到foward鏈。所有表中的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,因為在本例中,它是對外通訊的網口。
當然我們也可以在此,插入只允許某個網路存取,或某個網路不能存取的規則,不再贅述。

參考資料

https://www.zsythink.net/archives/1199
https://www.booleanworld.com/depth-guide-iptables-linux-firewall/
https://linuxconfig.org/how-to-make-iptables-rules-persistent-after-reboot-on-linux
https://askubuntu.com/questions/579231/whats-the-difference-between-prerouting-and-forward-in-iptables
https://docs.docker.com/network/iptables/

歡迎關注我的個人公眾號"西北偏北UP",記錄程式碼人生,行業思考,科技評論