@(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是一個linux下的防火牆工具,它能幫助我們基於規則進行網路流量控制。它可以做到,但不限於以下功能:
來自192.168.2.31的存取,就要將其拒絕
,這即是一條規則
往往我們的安全策略不只一條規則,除了
來自192.168.2.31的存取,就要將其拒絕
這條規則之外,我們還有其它規則,比如:
來自192.168.43.22的存取,也要將其拒絕
甚至,我們可能還有多個互斥的規則,這多個規則,哪個規則先執行? 這就涉及到鏈這個概念。簡單來講,鏈就是將多個規則從上大小串起來的一個集合單位。規則按從上倒下依次進行匹配。
鏈條可以有多個。將多個鏈條再規整在一起的集合,叫做表。
在iptables中,有四張表:
上述四張表中,會內建一些鏈,且每個鏈,都有預設包處理策略,預設策略一般在鏈中的所有規則都沒匹配時生效。
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 鏈是最先生效的,當封包到達網口時,即開始工作。同時由於其在raw, mangle, nat表中都存在,其執行的優先順序是:raw(PREROUTING) ----> mangle(PREROUTING)----> mangle(nat)
PREROUTING 一般用作對包進行目標地址修改。比如將該包的目標地址,修改為非本機的另外的網路ip,一般通過DNAT
規則進行修改。
決定一個包該走哪個鏈。如果上述PREROUTING 鏈對包進行了目標網路ip更改。那麼決策會覺得這個是一個需要轉發的封包,於是會將該包轉發給 FORWARD 鏈。
否則, 該包會走INPUT鏈
FORWARD在各表中生效的優先順序是:mangle(FORWARD) ----> filter(FORWARD)
處理路由決策派發發過來的包,到這裡的包一般目標網路地址在PREROUTING鏈被修改過
其生效順序是: mangle(INPUT) ----> filter(INPUT)
處理路由決策派發發過來的包,到這裡的包一般目標網路地址在PREROUTING鏈沒有被修改過。
在目標程序埠接收到輸入封包後,輸出的封包,將在這裡進行規則應用。OUTPUT鏈在各表中生效的先後順序是:
raw(OUTPUT) ----> mangle(OUTPUT) ----> nat(OUTPUT) ----> filter(OUTPUT)
前面鋪墊了那麼多,主要講解了鏈的複雜生效時機,畢竟如果包最終都到不了這個鏈,那其中的規則設定也就沒有意義。接下來,我們需要講解,鏈中具體規則的設定和使用。
一個規則一般分為兩大部分:
動作分為以下幾種:
/var/log/syslog
or /var/log/messages
iptables -t nat -nvL --line-numbers
-t
表示想要檢視那個表,這裡檢視的是nat表。iptables的所有命令,如果不指定-t
,如果不寫預設是filter表。
-L
表示列出該表所有鏈和所有規則
-v
詳細顯示,會將規則匹配的進出網口也列出來
--line-numbers
表示給規則進行編號處理。編號能方便我們後續對規則進行修改、刪除等操作
如圖所示,表頭有以下資訊:
總結來看,其實一個封包本身就有源、目標的一些資訊,而規則就是基於封包本身屬性的特點進行規則設定。
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 -A OUTPUT -d 31.13.78.35 -j DROP
-d
表示destination,即所有返回給ip 31.13.78.35的封包都直接丟掉,不迴應。
iptables -t filter -F INPUT
iptables -A INPUT -p tcp -j DROP
-p
表示protocol
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
連結狀態有以下幾種:
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的操作,並不是永久生效的,機器重啟後,對應設定會丟失。如果需要持久化,則需要以下命令進行儲存
iptables-save > /etc/iptables.conf
這一步,每次修改後都要做/etc/rc.local
中新增命令iptables-restore < /etc/iptables.conf
。從此之後,每次重啟,系統會自動從/etc/iptables.conf
恢復對應的iptables設定。這一步只需要做一次安裝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規則,執行這步
除了在現有的鏈中新增規則,我們也可以自定義鏈,自定義鏈可以幫助我們將一組規則收納在一起,方便我們管理。比如:
ssh-rules
的鏈來管理ssh登入的一些規則iptables -t filter -N ssh-rules
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
iptables -A INPUT -p tcp -m tcp --dport 22 -j ssh-rules
以上含義就是在Input鏈中新增一個規則,所有22號埠的存取,都會導向ssh-rules
再次強調: 只要不指定具體使用的表,預設都是filter表
當我們想要刪除自定義鏈時,使用命令:iptables -X ssh-rules
一般我們會在filter中的input鏈中,設定對某個埠的限制。但是在裝有docker的linux伺服器上,docker暴露的任何埠,我們卻無法通過在filter表中的input鏈的規則進行限制,這是為什麼呢? 我們通過上文的對整個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表的DOCKER 鏈
!docker0
,表示非docker網路,該規則能被匹配。由於mysql 實際安裝在docker內,如果最終要實現存取,就要對請求包的目標地址進行修改,於是該條規則在匹配後的動作是:tcp dpt:3306 to:172.17.0.2:3306
,即將目標網路埠改成docker網段下的172.17.0.2:3306
由於prerouting對包進行了目標地址的修改,於是路由決策會將該包路由到foward鏈。所有表中的input 鏈將直接忽略。
forward鏈中的第一條規則,會應用於DOCKER-USER鏈。該鏈的規則是直接返回包。
相當於第一條規則沒啟作用。直接會進入第二條規則進行匹配計算
第二條鏈會包匯入DOCKER-ISOLATION-STAGE-1鏈進行規則計算
一路鏈下去,最終只有圖中的規則③
能命中,而該規則對包的處理方式,是RETURN,也即交給下個規則處理
第三條規則
是對目標網口是docker0的包進行匹配,按理說我們的包會匹配這條規則, 但是這條規則被匹配還有一個條件,就是包連結的狀態要是已建立的連線才行,我們第一次從外部對資料庫進行存取顯然不符合這個要求,於是該規則不會命中。進入第四條規則匹配
第四條規則命中後,進入DOCKER 鏈
從截圖可以看到,包到了這裡,被完美匹配。該包首先是一個非docker網路到docker網路的存取,其次,其目標ip是172.17.0.2 的3306埠,匹配後,處理動作是ACCEPT。也即最終該存取被響應,我們從外部網路存取到資料庫了。
說白了,由於封包被更改了目標地址,於是路由策略將該包導向了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",記錄程式碼人生,行業思考,科技評論