Quantcast
Channel: polygun2000的博客
Viewing all articles
Browse latest Browse all 55

haproxy的stick-table实际应用探索

$
0
0
    haproxy的stick-table,很有用,但很抽象,不好理解。之前生吞活剥翻译了一份haproxy官方blog的文档,自己都觉得非常不满意,因此删除了那篇blog文章,我又参考了几份老外的文档,仔细琢磨了一下stick-table,在此把我的理解写一下,希望能帮到有需要的人,转载请注明来自 http://blog.sina.com.cn/polygun2000 的博客.

第一部分: 理解haproxy的stick-table
  
1.1 如何理解stick-table

因为编程水平low,没有去阅读源代码,我理解的stick-table就是在内存中的一个key-value形式存储的表。

能作为key的类型有: 
ip,ipv6,integer,string,binary

而value类似一个set,可以同时存放多个值,值的类型有:
server_id
gpc0,gpc0_rate
conn_cnt,conn_cur,conn_rate
sess_cnt,sess_rate
http_req_cnt,http_req_rate
http_err_cnt,http_err_rate
bytes_in_cnt,bytes_in_rate
bytes_out_cnt,bytes_out_rate

针对每个frontend或backend目前只可以创建一个stick-table。

如果你的需求就是这么任性, 说我就需要2个stick-table怎么办?haproxy文档中提到了可以建立一个"dummy proxy",可以看文末参考文档中的permalink.gmane.org那一篇,其中haproxy的作者willy给举了例子如何这么使用,这可算是最正的根儿了。

1.2 如何定义一个sitck-table


type 定义了这个table的key的类型,可以是 ip,ipv6,integer,string,binary之一
"type ip" 用于存储IPv4类型的IP地址,每个entry约占用50 bytes内存空间,主要用途是存放客户端源IP。
"type ipv6" 用于存储IPv6类型的IP地址,每个entry约占用60 bytes内存空间,主要用途是存放客户端源IP。
"type integer" 用于存储32位整数
"type string [len ]" 用于存储字符串,如果设置了len,则匹配设置的长度,多的字符会被截断,如不指定len,则默认长度是32个字符。
"type binary [len ]" 用于存储二进制块,如果设置了len,则最大存储指定长度,如果数据少于len,则使用0填充。默认长度是32 bytes。

size 定义了这个table可以存储的entry最大数量.
[nopurge] 设置禁止清除旧的entry,默认动作是当stick-table满了以后,haproxy会自动清除最老的entry以便腾出空间.
[peers ] 用于多台haproxy同步stick-table的peers名字,在haproxy.cfg中定义的,这个同步会另文来说。
[expire ] 定义entry在stick-table中的过期时间,默认大概24天.如果不指定expire这个参数,千万不要指定nopurge!

[store ] 用于存储其他额外信息,这些信息可以用于ACL.用于控制各种与客户端活动相关的标准。多种data type可以用逗号分隔,写在store后边。除了"server_id"是系统自动启用和检测以外,其他data type必须明确声明。如果一个ACL引用了一个未声明的data type,则ACL会直接返回不匹配。

- server_id : 一个整数,request被分配到的server ID
- gpc0 : 通用计数器,一个正的32位整数
- conn_cnt : connection计数器,一个正的32位整数,记录了匹配当前entry的,从一个客户端处接收到的连接的绝对数量,这个数量并不意味着被accepted的连接数量,单纯就是收到的数量。
- conn_cur : 当前connection数,一个正32位整数,当一新connection匹配指定的entry的时候,这个数值增加,当connection结束的时候,这个数值减少。通过这个值可以了解一个entry上任意时间点的准确的连接数。
- conn_rate() : connection的连接频率 ,指定时间范围内(毫秒为单位)建立connection的频率。
- sess_cnt : session计数器,一个正32位整数。记录了匹配当前entry的,从一个客户端处接收到的session的绝对数量。一个session指的是一个已经被layer 4规则接受的connection。
- sess_rate() : session的连接频率,指定时间范围内(毫秒为单位)建立session的频率。
                          这个数值可以通过ACL匹配一些规则。
                          这个数值可以通过ACL匹配一些规则。
- http_req_cnt : HTTP请求计数器,一个正32位整数。 记录了匹配当前entry的,从一个客户端接受到的HTTP请求的绝对数量。 无论这个请求是合法还是非法。当开启了客户端keep-alive的时候,这个数值与sess_cnt计数器的值就会不同。
- http_req_rate() : HTTP的请求频率,指定时间范围内(毫秒为单位)收到的HTTP请求的频率。 无论这个请求是合法还是非法。当开启了客户端keep-alive的时候,这个数值与sess_rate() 数器的值就会不同。
- http_err_cnt : HTTP错误计数器,一个正32位整数。 记录了匹配这个entry的HTTP错误的绝对数量,包含: 无效的、被截断的请求 被拒绝的或封堵的请求 认证失败 4xx错误 
- http_err_rate() : HTTP的请求错误频率,指定时间范围内(毫秒为单位)匹配的entry产生的HTTP错误的频率。
- bytes_in_cnt : 一个匹配entry的客户端发往服务器的字节数,形式为一个正64位整数。headers也包含在统计中,通常用于图片或者video服务器限制上传文件。
- bytes_in_rate() : 收到字节频率计数器,指定时间范围内(毫秒为单位)收到的字节数的频率,通常用于防止用户上传太快上传太多内容。这个计数器不建议使用,因为会有不公平情况,建议使用byte_in_cnt。
- bytes_out_cnt : 服务器发往客户端的字节数,一个正64位整数。headers也包含在统计中,通常用于防止机器人爬站。
- bytes_out_rate() : 发送字节频率计数器,指定时间范围内(毫秒为单位)服务器发送给客户端的字节数的频率。通常用于防止用户下载太快太多内容。这个计数器也不建议使用,因为会有不公平情况,建议使用byte_out_cnt。

下边举几个定义stick-table的实际例子:

stick-table type ip size 100k expire 30s store conn_cur
stick-table type ip size 200k expire 3m store conn_rate(100s),bytes_out_rate(60s)
stick-table type ip size 1m expire 10s store gpc0,http_req_rate(10s)

1.3 如何直观的查看stick-table里的内容

1.3.1 使用EPEL源,安装socat。
yum install socat
1.3.2 在haproxy.cfg中开启stat
stats socket /var/run/haproxy.stat mode 600 level operator

# socat readline /var/run/haproxy.stat
prompt
> set timeout cli 1d
> show table
table: http, type: 0, size:2048000, used:1
table: test1, type: 0, size:2048000, used:1
table: test2, type: 0, size:2048000, used:0

> show table http
table: http, type: 0, size:2048000, used:1
0x8370aac: key=127.0.0.1 use=0 exp=592347 gpc0=0

> show table test1
table: test1, type: 0, size:2048000, used:1
0x8370a68: key=127.0.0.1 use=0 exp=19020 conn_rate(100000)=2 bytes_out_rate(60000)=986

> help
Unknown command. Please enter one of the following commands only :
  clear counters : clear max statistics counters (add 'all' for all counters)
  clear table    : remove an entry from a table
  help           : this message
  prompt         : toggle interactive mode with prompt
  quit           : disconnect
  show info      : report information about the running process
  show stat      : report counters for each proxy and server
  show errors    : report last request and response errors for each proxy
  show sess [id] : report the list of current sessions or dump this session
  show table [id]: report table usage stats or dump this table's contents
  get weight     : report a server's current weight
  set weight     : change a server's weight
  set timeout    : change a timeout setting
  disable server : set a server in maintenance mode
  enable server  : re-enable a server that was previously in maintenance mode

1.4 应用stick-table的一般思路

    stick-table从名字上看,原本用途是用于将客户的request(或connection)与backend"粘"在一起,确保同一客户之后的request也会一直与同一backend通讯。而在1.5版以后,这个功能得到了扩展,可以用于存储更多类型的用户请求(或连接)数据的相关信息,利用这些信息,我们可以accept,block或route这些请求。这里想象力就非常重要了

    在haproxy中,要做"粘滞"操作有多种方法,例如cookie insert,source ip hash等。这些都基于简单的规则,但有一些协议或者应用需要比这几种更复杂的"粘滞"规则,不能简单依靠cookie或者source ip hash。

    haproxy 提供了以下几个语句,用于创建复杂的"粘滞"规则。



    1,2分别用于定义一个"pattern",从request和response中抽取数据,用这个数据在stick-table中作为key创建一个entry。
    3用于匹配之前定义的"pattern",用以创建"粘滞"关系。

因为新浪blog不让写"尖括号",所以改用@代替

@pattern@ 是fetch sample的表达式规则,在官方配置文档的7.3节有详细说明。
对于一个请求或连接来说,一旦确定了由哪个backend server来负责处理
这个表达式规则将会决定request或connection中哪些元素需要被分析,抽取然后被存储到stick-table中。

@table@   stick-table表的名字,可选。
如不指定,则默认是使用stick store-request语句所在的backend的stick-table
如指定,可以使用其他backend中定义的stick-table的名字,如果使用了其他backend的stick-table,则server_id是用那个backend里边的。默认情况下,对于每个backend来说,其server_ID都是从1开始,server的顺序是应该是定好的。但为了避免不确定性,强烈建议使用id关键字来自己设置server_ID。

关于stick store-request定义的数量没有明确限制,但对于每个request或response同时只能有8个stick store-request定义。"store-request"规则会在server连接established以后才开始计算,所以stick-table中会包含处理这个request的real server。

@condition@ 可选的存储条件,如果设置了过滤条件,那么可以只储存那些符合(或不符合)条件的内容。

使用stick-table的一般思路如下:
  1. 首先声明一个stick-table,用于存储特定类型的请求(或连接)的相关数据;
  2. 使用stick store-requset,stick store-response,tcp-request content track-sc0 ,tcp-request connection track-sc0等语句将数据送入之前定义的stick-table中; 
  3. 在需要取出数据的地方,用之前存储的key(例如源ip,cookie串等),去stick-table中去取出key对应的数据,利用这些数据做acl判断来决定如何处理后续的用户请求。

需要keep in mind的是: 

默认情况下,stick-table的声明本身只是创建一个sitck-table,并不做任何其他事情。向stick-table中存储数据,需要其他语句。

与之类似的是acl,acl声明语句并不会做任何真正的"action/check",声明语句的作用仅仅是将"acl名字"与"action/check"动作关联起来而已。"action/check"真正被执行是在该acl在其他部分被真正调用的时候。
haproxy中的acl通常是作为某种判断条件,但却不只限于此,有时声明一个acl只是为了完成某个动作,例如GPC计数器的自增操作等,就完全脱离了acl原始概念,这种我们可以认为它是"伪acl"。

[2016.1.19] 增补

有网友提到sc0,sc1,sc2这3个计数器的引入目的,可以先看下官方文档关于这3个计数器的说明。
官方文档提到了这个目的,就是针对一个connection可以在多处同时进行追踪,官方建议sc0计数器用于frontend部分,而sc1用于backend部分。这样可以针对同一个连接同时监控两个指标。

- { track-sc0 | track-sc1 | track-sc2 } [table] :
      enables tracking of sticky counters from current connection. These
      rules do not stop evaluation and do not change default action. Two sets
      of counters may be simultaneously tracked by the same connection. The
      first "track-sc0" rule executed enables tracking of the counters of the
      specified table as the first set. The first "track-sc1" rule executed
      enables tracking of the counters of the specified table as the second
      set. The first "track-sc2" rule executed enables tracking of the
      counters of the specified table as the third set. It is a recommended
      practice to use the first set of counters for the per-frontend counters
      and the second set for the per-backend ones. But this is just a
      guideline, all may be used everywhere.

文末的参考文档haproxy作者willy给了这样一个案例:

backend per-ip
      stick-table type ip size 50k expire 120m store gpc0,http_req_rate(120s)

   frontend
      tcp-request connection track-sc1 src table per-ip
      tcp-request connection reject if { sc1_get_gpc0 gt 0 }
      ...
      use_backend foo...

   backend foo
      tcp-request content track-sc2 src table per-ip if METH_POST
      acl bruteforce_detection  sc2_http_req_rate gt 5
      acl block sc1_inc_gpc0 gt 0
      http-request deny if bruteforce_detection block

这个案例解释了"dummy proxy"如何定义,同时也侧面说明了sc0,sc1,sc2的作用。
a. 首先定义了一个dummy proxy 名字叫per-ip
b. frontend追踪用户源ip(sc1)
c. backend同时追踪这个源ip的所有方法是POST的请求(sc2),并且根据判断情况标记用户是否是攻击用户(acl block sc1_inc_gpc0 gt 0)
d. fontend部分做检查,如果sc1的gpc0技术器大于0,则拒绝这个ip之后的请求。

我们另外再来看几个实际的例子。

例子1:

# 定义一个ip类型作为key的stick-table,用于存储这个ip相关的计数器
stick-table type ip size 500k expire 30s store  conn_cur,conn_rate(10s),http_req_rate(10s),http_err_rate(10s)

# 打开源ip追踪,用以向stick-table写入数据
tcp-request content track-sc0 src


# echo "show table fxa-https" | socat unix:./haproxy/stats -
# table: fxa-https, type: ip, size:512000, used:1
0x1aa3358: key=1.10.2.10 use=1 exp=29957 conn_rate(10000)=43 conn_cur=1 http_req_rate(10000)=42 http_err_rate(10000)=42

例子2:

# 从request或response中抽取SSL session ID,用这个ID将requset与backend"粘住" 
# SSL session ID可能由client提供,也可能由server提供

backend https
    mode tcp
    balance roundrobin
 
# 最大SSL session ID长度是32 bytes.
    stick-table type binary len 32 size 30k expire 30m
 
# 定义两个acl,分别匹配ssl client hello和ssl server hello ,在这个位置,ACL并没有被执行,仅仅是将ACL名字与判断条件关联起来而已。
    acl clienthello req.ssl_hello_type 1
    acl serverhello res.ssl_hello_type 2
 
# 使用tcp content accept来检测ssl client hello和ssl server hello.
# 因为client ssl hello包含在request中,因此必须等待5秒,确保tcp连接的数据都进入了buffer
    tcp-request inspect-delay 5s
    tcp-request content accept if clienthello      # 这里,acl的check操作才真正执行
 
# ssl server hello 由服务器端发出,所以不需要延迟,直接判断
    tcp-response content accept if serverhello
 
# SSL session ID 的长度为1 byte,位置在tcp payload的 offset 43之后。
# client hello在请求中,server hello在response中
# 取出payload_lv(43,1),作为key存入stick-table
    stick store-request payload_lv(43,1) if clienthello
    stick store-response payload_lv(43,1) if serverhello

# 用payload_lv(43,1)做key去stick-table中查找
    stick match payload_lv(43,1)
    server s1 192.168.1.1:443
    server s2 192.168.1.2:443






第二部分: 实际应用-用haproxy防范攻击

2.1 慢查询攻击

slowloris类型的攻击,客户端用非常非常慢的速度发送请求到服务器上。
通常是一个包头接一个包头或者更夸张的一个字符接一个字符,而每个包之间等待非常长的时间。
这样服务器端就不得不等待所有请求全部接收完毕才能返回响应。
这个攻击的目的是阻止正常用户访问我们提供的服务,所有的服务器资源都被用来等待处理慢查询了。

应对这种攻击,方法是在HAProxy中加入选项: "timeout http-request"
可以将这个值设置成5秒钟,应该已经足够长了。
这个参数告诉HAProxy最多等待5秒钟让客户端发送完整的HTTP请求,如果超过5秒,则HAProxy会切断连接并返回错误。

01    # On Aloha, the global section is already setup for you
02    # and the haproxy stats socket is available at /var/run/haproxy.stats
03    global
04      stats socket ./haproxy.stats level admin
05    
06    defaults
07      option http-server-close
08      mode http
09      timeout http-request 5s
10      timeout connect 5s
11      timeout server 10s
12      timeout client 30s
13    
14    listen stats
15      bind 0.0.0.0:8880
16      stats enable
17      stats hide-version
18      stats uri     /
19      stats realm   HAProxy\ Statistics
20      stats auth    admin:admin
21    
22    frontend ft_web
23      bind 0.0.0.0:8080
24    
25      # Spalreadylit static and dynamic traffic since these requests have different impacts on the servers
26      use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
27    
28      default_backend bk_web
29    
30    # Dynamic part of the application
31    backend bk_web
32      balance roundrobin
33      cookie MYSRV insert indirect nocache
34      server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
35      server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
36    
37    # Static objects
38    backend bk_web_static
39      balance roundrobin
40      server srv1 192.168.1.2:80 check maxconn 1000
41      server srv2 192.168.1.3:80 check maxconn 1000

为了测试这个选项的效果,可以使用telnet连接到HAProxy的端口,然后等待5秒钟,输出类似下边:
telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
HTTP/1.0 408 Request Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html

408 Request Time-out
Your browser didn't send a complete request in time.

Connection closed by foreign host.

2.2.Unfair用户

何谓一个"Unfair"用户?就是指这个用户(或者脚本)有不同于正常用户的行为:

打开太多连接
建立新连接的频率太快
http请求的频率太快
使用太多的带宽
客户端不遵守RFC协议(IE for SMTP)

正常的浏览器行为?

在保护我们站点不受怪异的行为伤害之前,我们需要了解正常的浏览器行为是什么样子的。
首先,用户会打开Chrome, Firefox, Internet Explorer, Opera之间的一种,然后输入URL。
浏览器首先去请求DNS解析IP地址,然后它会建立一个和服务器的连接,下载首页,分析其中的内容。
然后根据页面的HTML代码的连接去下载不同的对象:javascript, css, 图片等等。
为了下载对象,对于每个域名,浏览器会打开6或7个TCP连接。
等到所有对象下载完毕,浏览器会将这些对象叠加渲染出整个页面。

2.2.1 限制每个用户的连接数

根据之前的说明,每个用户浏览器会与webserver之间非常快的打开6或7个TCP连接,那么我们可以认为如果
一个用户打开了超过10个TCP连接就是不正常的行为。

以下示例作了每用户连接数的限制,重点在于25-32行。

01    # On Aloha, the global section is already setup for you
02    # and the haproxy stats socket is available at /var/run/haproxy.stats
03    global
04      stats socket ./haproxy.stats level admin
05    
06    defaults
07      option http-server-close
08      mode http
09      timeout http-request 5s
10      timeout connect 5s
11      timeout server 10s
12      timeout client 30s
13    
14    listen stats
15      bind 0.0.0.0:8880
16      stats enable
17      stats hide-version
18      stats uri     /
19      stats realm   HAProxy\ Statistics
20      stats auth    admin:admin
21    
22    frontend ft_web
23      bind 0.0.0.0:8080
24    
25      # Table definition  
26      stick-table type ip size 100k expire 30s store conn_cur
27    
28      # Allow clean known IPs to bypass the filter
29      tcp-request connection accept if { src -f /etc/haproxy/whitelist.lst }
30      # Shut the new connection as long as the client has already 10 opened
31      tcp-request connection reject if { src_conn_cur ge 10 }
32      tcp-request connection track-sc1 src
33    
34      # Split static and dynamic traffic since these requests have different impacts on the servers
35      use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
36    
37      default_backend bk_web
38    
39    # Dynamic part of the application
40    backend bk_web
41      balance roundrobin
42      cookie MYSRV insert indirect nocache
43      server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
44      server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
45    
46    # Static objects
47    backend bk_web_static
48      balance roundrobin
49      server srv1 192.168.1.2:80 check maxconn 1000
50      server srv2 192.168.1.3:80 check maxconn 1000

配置文件解说:

25      # 定义一个以ip类型为key,大小100K,过期时间30秒的stick-table,用于存储该IP对应的当前connection数量
26      stick-table type ip size 100k expire 30s store conn_cur
27    
28      # 允许白名单用户的IP绕过限制
29      tcp-request connection accept if { src -f /etc/haproxy/whitelist.lst }

30      # 如果当前客户端ip的connection数量已经超过10个,则拒绝其发起的新连接
31      tcp-request connection reject if { src_conn_cur ge 10 }
32      tcp-request connection track-sc1 src

src_conn_cur  是ACL layer 4的fetch method,作用是用当前发起连接的客户端源ip地址做key,去stick-table中查找对应的当前connection数量,如果查到则返回实际数值,查不到则返回0。

注意:
1.如果HAProxy用于代理多个域名,则需要增加conn_cur的数量,因为每个域名会有5-7个连接。
2.如果多个用户在同一NAT设备后边,则这个限制会对他们产生不利影响,原因显而易见。

为了测试限制的效果,我们做如下测试:

使用Apache bench打开10个连接:
ab -n 50000000 -c 10 http://127.0.0.1:8080/

观察haproxy的stats输出:
echo "show table ft_web" | socat unix:./haproxy.stats -
# table: ft_web, type: ip, size:102400, used:1
0x7afa34: key=127.0.0.1 use=10 exp=29994 conn_cur=10

我们打开telnet尝试建立新连接:
telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Connection closed by foreign host.

发现已经被HAProxy拒绝了。

2.2.2 限制每用户建立连接的频率

我们可以认为3秒钟之内建立超过20个连接的用户就是非正常访问。
以下示例作了每用户建立连接的频率,重点在于25-32行。

01    # On Aloha, the global section is already setup for you
02    # and the haproxy stats socket is available at /var/run/haproxy.stats
03    global
04      stats socket ./haproxy.stats level admin
05    
06    defaults
07      option http-server-close
08      mode http
09      timeout http-request 5s
10      timeout connect 5s
11      timeout server 10s
12      timeout client 30s
13    
14    listen stats
15      bind 0.0.0.0:8880
16      stats enable
17      stats hide-version
18      stats uri     /
19      stats realm   HAProxy\ Statistics
20      stats auth    admin:admin
21    
22    frontend ft_web
23      bind 0.0.0.0:8080
24    
25      # Table definition  
26      stick-table type ip size 100k expire 30s store conn_rate(3s)
27    
28      # Allow clean known IPs to bypass the filter
29      tcp-request connection accept if { src -f /etc/haproxy/whitelist.lst }
30      # Shut the new connection as long as the client has already 10 opened
31      tcp-request connection reject if { src_conn_rate ge 10 }
32      tcp-request connection track-sc1 src
33    
34      # Split static and dynamic traffic since these requests have different impacts on the servers
35      use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
36    
37      default_backend bk_web
38    
39    # Dynamic part of the application
40    backend bk_web
41      balance roundrobin
42      cookie MYSRV insert indirect nocache
43      server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
44      server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
45    
46    # Static objects
47    backend bk_web_static
48      balance roundrobin
49      server srv1 192.168.1.2:80 check maxconn 1000
50      server srv2 192.168.1.3:80 check maxconn 1000

注意:如果多个用户在同一NAT设备后边,则这个限制也会对他们产生不利影响,原因同样显而易见。

为了测试限制的效果,我们做如下测试:

ab -n 10 -c 1 -r http://127.0.0.1:8080/

echo "show table ft_web" | socat unix:./haproxy.stats -
# table: ft_web, type: ip, size:102400, used:1
0x11faa3c: key=127.0.0.1 use=0 exp=28395 conn_rate(3000)=10

telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Connection closed by foreign host.

2.2.3 限制HTTP请求的频率

01    # On Aloha, the global section is already setup for you
02    # and the haproxy stats socket is available at /var/run/haproxy.stats
03    global
04      stats socket ./haproxy.stats level admin
05    
06    defaults
07      option http-server-close
08      mode http
09      timeout http-request 5s
10      timeout connect 5s
11      timeout server 10s
12      timeout client 30s
13    
14    listen stats
15      bind 0.0.0.0:8880
16      stats enable
17      stats hide-version
18      stats uri     /
19      stats realm   HAProxy\ Statistics
20      stats auth    admin:admin
21    
22    frontend ft_web
23      bind 0.0.0.0:8080
24    
25      # Use General Purpose Couter (gpc) 0 in SC1 as a global abuse counter
26      # Monitors the number of request sent by an IP over a period of 10 seconds
27      stick-table type ip size 1m expire 10s store gpc0,http_req_rate(10s)
28      tcp-request connection track-sc1 src
29      tcp-request connection reject if { src_get_gpc0 gt 0 }
30    
31      # Split static and dynamic traffic since these requests have different impacts on the servers
32      use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
33    
34      default_backend bk_web
35    
36    # Dynamic part of the application
37    backend bk_web
38      balance roundrobin
39      cookie MYSRV insert indirect nocache
40    
41      # If the source IP sent 10 or more http request over the defined period,
42      # flag the IP as abuser on the frontend
43      acl abuse src_http_req_rate(ft_web) ge 10
44      acl flag_abuser src_inc_gpc0(ft_web)
45      tcp-request content reject if abuse flag_abuser
46    
47      server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
48      server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
49    
50    # Static objects
51    backend bk_web_static
52      balance roundrobin
53      server srv1 192.168.1.2:80 check maxconn 1000
54      server srv2 192.168.1.3:80 check maxconn 1000

测试方法同上边。

2.2.4 检测漏洞扫描

如果有人尝试对我们的站点进行漏洞扫描,那么通过HAProxy可以追踪到不同的错误。

HAProxy可以监控每个用户产生错误的频率,并且根据这个频率决定进一步的操作。

01    # On Aloha, the global section is already setup for you
02    # and the haproxy stats socket is available at /var/run/haproxy.stats
03    global
04      stats socket ./haproxy.stats level admin
05    
06    defaults
07      option http-server-close
08      mode http
09      timeout http-request 5s
10      timeout connect 5s
11      timeout server 10s
12      timeout client 30s
13    
14    listen stats
15      bind 0.0.0.0:8880
16      stats enable
17      stats hide-version
18      stats uri     /
19      stats realm   HAProxy\ Statistics
20      stats auth    admin:admin
21    
22    frontend ft_web
23      bind 0.0.0.0:8080
24    
25      # Use General Purpose Couter 0 in SC1 as a global abuse counter
26      # Monitors the number of errors generated by an IP over a period of 10 seconds
27      stick-table type ip size 1m expire 10s store gpc0,http_err_rate(10s)
28      tcp-request connection track-sc1 src
29      tcp-request connection reject if { src_get_gpc0 gt 0 }
30    
31      # Split static and dynamic traffic since these requests have different impacts on the servers
32      use_backend bk_web_static if { path_end .jpg .png .gif .css .js }
33    
34      default_backend bk_web
35    
36    # Dynamic part of the application
37    backend bk_web
38      balance roundrobin
39      cookie MYSRV insert indirect nocache
40    
41      # If the source IP generated 10 or more http request over the defined period,
42      # flag the IP as abuser on the frontend
43      acl abuse src_http_err_rate(ft_web) ge 10
44      acl flag_abuser src_inc_gpc0(ft_web)
45      tcp-request content reject if abuse flag_abuser
46    
47      server srv1 192.168.1.2:80 check cookie srv1 maxconn 100
48      server srv2 192.168.1.3:80 check cookie srv2 maxconn 100
49    
50    # Static objects
51    backend bk_web_static
52      balance roundrobin
53      server srv1 192.168.1.2:80 check maxconn 1000
54      server srv2 192.168.1.3:80 check maxconn 1000

我们通过如下方法测试:

ab -n 10 http://127.0.0.1:8080/dlskfjlkdsjlkfdsj

echo "show table ft_web" | socat unix:./haproxy.stats -
# table: ft_web, type: ip, size:1048576, used:1
0x8a9770: key=127.0.0.1 use=0 exp=5866 gpc0=1 http_err_rate(10000)=11.

再次执行上边的ab命令,会得到如下错误:
apr_socket_recv: Connection reset by peer (104)

说明HAProxy已经block这个IP。

参考文档:
http://blog.exceliance.fr/2012/02/27/use-a-load-balancer-as-a-first-row-of-defense-against-ddos/
http://brokenhaze.com/blog/2014/03/25/how-stack-exchange-gets-the-most-out-of-haproxy/
https://blog.codecentric.de/en/2014/12/haproxy-http-header-rate-limiting/
https://github.com/jvehent/haproxy-aws
http://cbonte.github.com/haproxy-dconv/configuration-1.5.html#stick-table
http://blog.serverfault.com/2010/08/26/1016491873/
http://blog.gnuers.org/?p=120
http://permalink.gmane.org/gmane.comp.web.haproxy/13285
http://serverfault.com/questions/678882/is-there-a-way-to-rate-limit-connections-with-haproxy-using-multiple-thresholds
http://blog.haproxy.com/2013/04/26/wordpress-cms-brute-force-protection-with-haproxy/

 

Viewing all articles
Browse latest Browse all 55

Trending Articles