前文我们聊到了 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 的说明可以参考官方文档;
示例: 拒绝源 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 组上去, 实现了动态分离;
来源: https://www.cnblogs.com/qiuhom-1874/p/12817773.html