iptables使用詳解

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,因為在本例中,它是對外通信的網口。
當然我們也可以在此,插入只允許某個網絡訪問,或某個網絡不能訪問的規則,不再贅述。

參考資料

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

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