基于Nginx与iptables的限速策略

原理

  1. Nginx自带的组件 limit_req_zone可以对单个IP发起的请求进行限速,采用的策略是漏桶(leaky bucket)算法,划定一块内存空间用来建立桶即可使用,开销比较小.初步确定10Requests/s,对于超出的请求均返回503.
  2. 对非浏览器端发起的API请求拒绝服务,初步的方法是拒绝无Referer或携带除*.pasteme.cnlocalhost(调试用)之外的Referer头的请求,均返回403.
  3. 通过Fail2Ban组件监视error.log,提取触发503限速的IP,通过写入iptables拒绝掉流量.从TCP层面过滤流量.

实现

设置Nginx

limit_req_zone $binary_remote_addr zone=saltedfish:10m rate=5r/s; 
# 开启limit_req_zone,以remote_address作为分类标准,分配10M内存空间saltedfish,标准速度为5次请求每秒.
server
{
    listen 8080;
    server_name _;
    index index.html;
    root /www/wwwroot;
    underscores_in_headers on;  
    gzip_http_version 1.0;

    location / {
        try_files $uri $uri/ /index.html;
        location ~ .*\.(js|css)?$ {
            gzip_static on;
        }
    }

    location /_api/backend/ { 
    #对/_api/backend/ 进行限流,使用上面划分的zone pasteme,允许有5次请求每秒的突发,因此
    #限流的速率要求为共10次请求每秒,nodelay表示超过限流就返回503拒绝请求.
        limit_req zone=pasteme burst=5 nodelay;
        valid_referers localhost pasteme.cn *.pasteme.cn;
        if ($invalid_referer) {
          return 403;
        }
        # 对referer 头部进行校验,只允许 localhost/pasteme.cn/*.pasteme.cn三种域名的referer,若无Referer或Referer中不以http(s)起始也算无效,返回403
       #Do Sth.....
    }

    location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
    {
        return 404;
    }

    access_log  /var/lib/logs/access.log;
    error_log  /var/lib/logs/access.error.log;
    #错误log位置
}

重载Nginx即可生效.

安装Fail2Ban

sudo apt-get install fail2ban

增加日志检测规则

vim /etc/fail2ban/filter.d/nginx-req-limit.conf

内容为

# Fail2Ban configuration file
#
# supports: ngx_http_limit_req_module module

[Definition]

failregex = limiting requests, excess:.* by zone.*client: <HOST>
#使用正则匹配,<HOST>部分即为提取出的封禁IP
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex =

可参见

Fail2Ban文档

增加封禁任务

vim /etc/fail2ban/jail.local

官方建议使用*.local文件来覆写同目录下jail.conf的默认设置.jail.local内容如下

[nginx-req-limit] 
#任务名称 nginx-req-limit
enabled = true
# 启用
filter = nginx-req-limit
#过滤器 即之前新建的filter.d下的nginx-req-limit.conf
action = iptables-multiport[name=ReqLimit, port="http,https,8080", protocol=tcp]
#封禁动作 使用iptables封禁 iptables规则名称为ReqLimit 端口为http,https和8080,协议为tcp,记得要更改为Nginx的监听端口.
logpath = /var/lib/logs/access.error.log
#要检测日志文件路径
findtime = 600
#检测时间为 600秒为一个时间区间
bantime = 7200
#封禁时间 7200s
maxretry = 10
#时间区间内允许的失败次数,这里设置为600秒内超过限流10次即拉黑.
systemctl restart fail2ban

查看日志规则是否启动

tail -f /var/log/fail2ban.log

2020-12-08 19:48:47,423 fail2ban.jail           [6163]: INFO    Jail 'nginx-req-limit' uses pyinotify {}
2020-12-08 19:48:47,432 fail2ban.jail           [6163]: INFO    Initiated 'pyinotify' backend
2020-12-08 19:48:47,438 fail2ban.filter         [6163]: INFO    Added logfile: '/var/lib/pasteme/pasteme.error.log' (pos = 2070, hash = 838cc5f2d47c4bd59219f24814adb669d56e62db)
2020-12-08 19:48:47,439 fail2ban.filter         [6163]: INFO      encoding: UTF-8
2020-12-08 19:48:47,440 fail2ban.filter         [6163]: INFO      maxRetry: 10
2020-12-08 19:48:47,441 fail2ban.filter         [6163]: INFO      findtime: 600
2020-12-08 19:48:47,441 fail2ban.actions        [6163]: INFO      banTime: 7200
2020-12-08 19:48:47,445 fail2ban.jail           [6163]: INFO    Jail 'sshd' started #默认会自动启动对ssh尝试失败的封禁
2020-12-08 19:48:47,456 fail2ban.jail           [6163]: INFO    Jail 'nginx-req-limit' started #已启动
2020-12-08 19:48:47,649 fail2ban.actions        [6163]: NOTICE  [nginx-req-limit] Restore Ban 192.168.124.238

image-20201208195801556

或者使用fail2ban-client

fail2ban-client status nginx-req-limit

Status for the jail: nginx-req-limit
|- Filter
|  |- Currently failed: 0 #fail指检测到日志中的超限记录条数
|  |- Total failed:     0 
|  `- File list:        /var/lib/logs/access.error.log
`- Actions
   |- Currently banned: 1 #ban掉的IP
   |- Total banned:     1
   `- Banned IP list:   192.168.124.238

如何解封

fail2ban-client set <规则名称> unbanip <IP>

fail2ban-client set nginx-req-limit unbanip 192.168.124.238

验证Fail2Ban效果

Fail2Ban的核心在于改写iptables规则.执行iptables -L你会看到filter表中的所有规则链,Fail2Ban默认会新建一条规则链,并在INPUT的最顶层引用它.而每一个封禁都是一条REJECT记录.

image-20201208212729532

已知问题

  1. Docker容器中开放端口的流量不受过滤.

    Docker默认的子网组成方式是使用Bridge模式,在iptables的nat表中就转发入Docker容器.而Fail2ban的规则链添加在filter的INPUT链中,通往容器的数据包在NAT的Forward链中已经送往Docker容器,无法执行规则.

    表的处理优先级:raw>mangle>nat>filter.在表中又有5个规则链:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING.下图表示了一个数据包进入路由表所经过的规则处理链.

    img

解决方案

设置为将规则添加在FORWARD链中即可.

vim /etc/fail2ban/action.d/iptables-common.conf
# Option:  chain
# Notes    specifies the iptables chain to which the Fail2Ban rules should be
#          added
# Values:  STRING  Default: INPUT
chain = FORWARD 
#将chain由INPUT改为 FORWARD
systemctl restart fail2ban
tag(s): none
show comments · back · home
Edit with markdown