ACL(access control list)翻譯過來就是訪問控制列表;相信ACL這個詞對大家都不是太陌生;Linux里的許可權里有ACL,httpd、nginx、varnish里都有ACL的概念;訪問控制列表(ACL)的使用提供了一個靈活的解決方案來執行內容切換,並通常根據從請求、響應或任何環境... ...
前文我們聊到了haproxy的錯誤頁的配置,自定義日誌的配置,回顧請參考https://www.cnblogs.com/qiuhom-1874/p/12797913.html;今天我們主要來看看haproxy的訪問控制的實現;
ACL(access control list)翻譯過來就是訪問控制列表;相信ACL這個詞對大家都不是太陌生;Linux里的許可權里有ACL,httpd、nginx、varnish里都有ACL的概念;訪問控制列表(ACL)的使用提供了一個靈活的解決方案來執行內容切換,並通常根據從請求、響應或任何環境狀態提取的內容做出決策。haproxy中訪問控制實現和httpd、nginx、varnish中的訪問控制類似,都是先撲捉用戶的請求報文或響應報文,或者其他環境狀態的信息來把客戶端分類;然後把該ACL作為條件判斷,把不同類別或者說符合我們定義ACL的客戶端做其他操作;比如我們可以去撲捉用戶的請求報文中的referer首部的信息,來判斷本次請求的客戶端是不是合法的客戶端,合法就允許訪問,不合法就拒絕訪問或重定向到別的url上;至於是不是合法的客戶端,這個需要我們明確的去定義acl實現;
在haproxy中ACL的定義語法如下:
acl <aclname> <criterion> [flags] [operator] [<value>] ...
acl是關鍵字,aclname是區別不同acl的標識;不同名稱的acl是通過名稱來區分的,相同名稱的acl表示同一個ACL;criterion表示判斷的基準,什麼意思呢,就是以criterion指定的信息來分類不同客戶端;flages表示標記,常用的標記有 -i表示去區分字元大小寫;-m表示使用特定模式匹配方法;-n表示禁用DNS解析;-u表示強制ACL的唯一id;operator表示操作符,常用的操作符有;對於整數值的操作符有 eq(等於)、ge(大於等於)、gt(大於)、le(小於等於)、lt(小於);對於字元串的操作符有,精準匹配 -m str;子串匹配 -m sub;首碼匹配 -m beg;尾碼匹配 -m end; 路徑匹配 -m dir ;功能變數名稱匹配 -m dom;value就表示criterion的值,值得類型有布爾型,整數型,IP ,string,正則表達式,十六進位數;
acl作為條件時的邏輯關係:
AND 表示與關係;表示滿足第一個acl的同時要滿足第二acl 此條件才為真;通常用空白字元分隔兩個ACL
OR表示與關係;表示滿足acl1或acl2中的任意一個此條件就為真;通常用“||”分隔兩個ACL
!表示非關係;表示對該ACL取相反的操作,意思就是非該acl此條件為真;
常用的criterion有:
dst:表示目標ip
src:表示源ip
dst_port:表示目標埠
src_port:表示源埠
path:表示提取請求的URL路徑,該路徑從第一個斜杠開始,在問號之前結束(不包括主機部分)。
url:表示提取請求的整個URL路徑;
req.hdr:表示提取用戶請求報文中特定首部;
status:表示提取響應的狀態碼;
更多haproxyACL中criterion的說明可以參考官方文檔http://cbonte.github.io/haproxy-dconv/1.5/configuration.html#7;
示例:拒絕源ip為192.168.0.21的請求訪問
提示:紅框中的部分就表示拒絕源IP為192.168.0.21的訪問;acl block_ip src 192.168.0.21表示定義一個acl,名稱為block_ip ,提取客戶端的源ip作為分類基準,其源ip的值為192.168.0.21;這意味著只要是源IP為192.168.0.21就會被該acl匹配,或者說源IP為192.168.0.21的請求滿足block_ip這個ACL;block if block_ip表示拒絕滿足block_ip這條ACL規則的請求;
測試:用源地址為192.168.0.21的客戶端訪問,看看是否能夠實現拒絕訪問?
提示:可以看到用192.168.0.21為源ip的客戶端去訪問,給我們返回的是自定義錯誤頁面;這意味著我們本次訪問是被拒絕的;
block { if | unless } <condition>:表示7層拒絕,滿足或不滿足指定條件就拒絕;
示例:拒絕用戶請求首部User-Agent首部中的包含curl的訪問
提示:紅框中的內容表示定義一個acl其名稱為block_curl 提取用戶請求報文中的User-Agent首部的值做字串匹配,匹配不區分字元大小寫,如果用戶請求報文中包含curl子串,就表示滿足我們定義的ACL,如果滿足我們定義的block_curl規則,就拒絕;
測試:用curl訪問192.168.0.22看看是否能夠訪問?
提示:可以看到我們用curl去訪問是被拒絕的,但是用wget去訪問就完全正常,說明我們定義的acl用curl去訪問是滿足該acl,所以被拒絕了;
示例:拒絕源地址為192.168.0.21並且User-Agent首部中包含curl字串的客戶端訪問
提示:以上配置表示拒絕源地址為192.168.0.21並且用curl訪問的客戶端請求;
測試:源地址非192.168.0.21的客戶端,用curl訪問看看是否能夠訪問?
提示:源地址不是192.168.0.21,用curl訪問時可以正常訪問的;
測試:源地址為192.168.0.21,用wget訪問看看是否可以訪問?
提示:可以看到在源地址為192.168.0.21上用wget可以訪問訪問,用curl就不能訪問,這是因為在源地址為192.168.0.21上用curl訪問滿足我們定義的兩台acl規則,所以就會被拒絕;事實上block_ip 和block_curl這兩條ACL是邏輯與的關係,表示兩個ACL都要被滿足才能拒絕;滿足其中一個就不能被拒絕的;
示例:拒絕源地址為192.168.0.21的訪問或者請求報文User-Agent首部的值包含curl的訪問
提示:以上配置就表示滿足block_ip 或者block_curl中的任意一條ACL就會被拒絕訪問
測試:在源地址為192.168.0.21上訪問192.168.0.22,看看是否都會被拒絕?
提示:可以看到在源地址為192.168.0.21上不管是用curl還是wget都是被拒絕的,這是因為為被block_ip這條ACL匹配;
測試:在源地址非192.168.0.21上用curl和wget訪問,看看會是什麼情況?
提示:提示可以看到在非192.168.0.21為源地址的主機上用curl訪問時,就會被拒絕,用wget訪問就完全正常,這是因為用curl訪問會被block_curl這條ACL匹配,所以會被拒絕訪問;而用wget不會匹配到任何一條ACL,所以訪問就不會受限制;ACL作為條件或關係只需要滿足其中一條ACL即可;
示例:拒絕referer首部包含test的功能變數名稱字串的訪問
提示:以上配置表示拒絕referer首部包含test的功能變數名稱字串的訪問;其中hdr_dom(referer)同hdr(referer) -m dom是一樣的;
測試:用curl命令模擬referer信息為“www.test.com” 看看是否能夠訪問?
提示:可以看到在不加referer信息的訪問時會被拒絕,這是因為請求報文中referer首部的值為空,不滿足vaild_referer這條ACL,所以!valid_referer就為真,所以會被拒絕;加上referer訪問就會被valid_referer匹配,!valid_referer就為假,所以訪問就不會被拒絕;
示例:利用ACL實現動靜分離
提示:以上配置表示不區分字元大小寫匹配用戶請求的URI路徑中以/static /images /javascript /stylesheets開頭或者以.jpg .gif .png .css .js .html .txt .htm結尾的請求都歸類於url_static這個ACL中(以上是兩條同名的ACL,它倆會被haproxy識別成一條ACL,即兩條ACL之間是或關係,表示滿足其中一條即可);如果用戶請求的URI路徑滿足定義的ACL,就把請求調度到static_servs這個後端組響應;不滿足就預設調度到appservs這個後端組上進行響應;
實驗環境說明,本人以三台容器模擬static伺服器和app伺服器;分別在web1和web2上新建static目錄併在對應目錄下新建一主頁,內容是寫明是static伺服器,ip地址是多少,這樣的方式區分;在web3上修改主頁內容為app server ip 是172.17.0.4;
[root@docker_node1 ~]# docker exec -it web1 /bin/sh /usr/local/apache2 # cd htdocs/ /usr/local/apache2/htdocs # mkdir static /usr/local/apache2/htdocs # cd static/ /usr/local/apache2/htdocs/static # echo "<h1> this static server ip is 172.17.0.2 </h1>" > index.html /usr/local/apache2/htdocs/static # exit [root@docker_node1 ~]# docker exec -it web2 /bin/sh /usr/local/apache2 # cd htdocs/ /usr/local/apache2/htdocs # mkdir static /usr/local/apache2/htdocs # cd static /usr/local/apache2/htdocs/static # echo "<h1> this static server ip is 172.17.0.3 </h1>" > index.html /usr/local/apache2/htdocs/static # exit [root@docker_node1 ~]# docker exec -it web3 /bin/sh /usr/local/apache2 # echo "<h1> this is app server ip is 172.17.0.4 </h1>" >htdocs/index.html /usr/local/apache2 # exit [root@docker_node1 ~]#
測試:重啟haproxy,然後訪問192.168.0.22/static/ 和訪問192.168.0.22看看有什麼區別
提示:可以看到我們訪問192.168.0.22就只會把請求調度到172.17.0.4這台容器響應,這是因為請求URI路徑不被url_static這條ACL匹配,所以就預設走default_backend 指定的server組;訪問/static時被url_static這條ACL匹配,所以會把訪問/static的請求發送給use_backend指定的server組;這樣一來我們就通過不同的URI路徑把用戶請求調度到不同的後端server組上去,實現了動靜分離;