Nginx TTTYGateway:开源的 HTTP 访问限制模块

xjz19901211 · 2017年05月16日 · 最后由 xjz19901211 回复于 2017年05月16日 · 1769 次阅读

TTTYGateway 是基于 OpenResty 的一套 HTTP 请求限制模块

可以做什么

  • 设置请求的 IP 黑名单及白名单
  • 针对 Path 设置请求的白名单
  • 根据 IP 针对 Path 设置请求频率限制
  • 根据 User 针对 Path 设置请求频率限制
  • 为 Prometheus 提供简单的 Nginx 状态信息,可以不用再去分析日志,直接得到请求数、返回状态、请求耗时等数据

Path: 可以对指定请求方法(GET POST PUT DELETE)及请求的路径进行匹配,路径支持简单的通配符

User: 代码中自由定义,可以未登陆时使用 IP,登陆后使用用户 ID 或 TOKEN 等

性能如何

OpenResty 主体就是 Nginx + JITLua,通常 JITLua 的性能和 C 在一个量级,使用 OpenResty 写的应用,运行起来通常会有 C 程序运行的快感。

下面一个简单的对比示例,只是简单表示 JITLua 是很快的,因为只使用了简单的逻辑,在整体上他们性能还是有些差别的

C Code

#include "stdio.h"

int main() {
  int sum=0;
  int n = 5000000;

  for(int i=1; i<=n; i++){
    if(i%2==0){
      sum = sum - i;
    } else {
      sum = sum + i;
    }
  }

  printf("%i", sum);
}

Lua Code

local sum = 0
local n = 5000000

for i = 1, n do
  if i % 2 == 0 then
    sum = sum - i
  else
    sum = sum + i
  end
end

print(sum)

C 编译后运行

$ time ./add
-2500000
real   0m0.025s
user   0m0.021s
sys    0m0.001s

Lua 编译后运行

$ time luajit ./add.lua
-2500000
real    0m0.025s
user    0m0.022s
sys     0m0.001s

TTTYGateway 怎么用

只需要把你的 nginx 配置复制到项目的 nginx/conf/nginx.conf 下面,然后做如下配置

http {
  # 指向 ttty-gateway 目录 下的 nginx/lualib
  lua_package_path "/path/to/ttty-gateway/nginx/lualib/?.lua;;";
  init_by_lua_file 'lualib/init.lua'; # load gateway config

  server {
    listen 80;

    -- 为了直接在 nginx 中取得用户实际的 IP
    set_real_ip_from 127.0.0.1;
    set_real_ip_from 192.168.0.0/16;
    set_real_ip_from 10.0.0.0/8;
    set_real_ip_from 172.0.0.0/12;

    real_ip_header 'X-Forwarded-For';
    real_ip_recursive on;

    # 关键点,运行 API 请求检查器
    # 如果放在 server block 中,所检查所有请求,像静态资源什么的
    access_by_lua_file 'lualib/access_checker.lua';

    location /api {
      # 通常我们都是在 location 下只限制 API 的请求
      # access_by_lua_file 'lualib/access_checker.lua';

      proxy_pass http://xxx;
    }
  }
}

然后跑起来

openresty -p /path/to/ttty-gateway

或者使用 docker

$ docker run -v /path/to/ttty-gateway:/openresty -e REDIS_HOST={IP} openresty/openresty -p /openresty

当然,你也可以先 build 好,再用 docker 运行

$ docker build ./ -t ttty_gateway:latest
$ docker run -v /path/to/your/nginx.conf:/openresty/conf/nginx.conf -e REDIS_HOST={IP} ttty_gateway

上面运行时会需要 Redis ,如果没有,所有请求都不会进行限制,也就是说,运行一半时,如果你的 Redis 服务挂了,就不会进行限制检查了。而 Mysql 服务是可选的,可以让你动态配置限制规则。

上面运行的是默认规则,详见 nginx/lualib/web_shield_config.lua,我们只是将内网IP设置为白名单:

whitelist: {'127.0.0.1', '192.168.0.1/16', '172.17.0.1/12', '10.0.0.0/8'}

并对所有的请求做了一个简单的分级限制

{
  -- level 1
  {matcher = {method = READ, path = '*'}, period = 20, limit = 15},
  {matcher = {method = WRITE, path = '*'}, period = 20, limit = 7},
  -- level 2
  {matcher = {method = READ, path = '*'}, period = 60, limit = 30},
  {matcher = {method = WRITE, path = '*'}, period = 60, limit = 14},
  -- level 3
  {matcher = {method = READ, path = '*'}, period = 120, limit = 45},
  {matcher = {method = WRITE, path = '*'}, period = 120, limit = 21},
}

更详细的可以参考文档。上面的这些配置都支持直接在 Mysql 中动态配置的,ttty-gateway 会自动缓存 Mysql 中的配置,并简单的检查是否正确,所以,如果 Mysql 挂了的话,他会使用缓存,如果配置错误的话,将会直接忽略 Mysql 中的配置,而是使用默认文件中的配置

Mysql 配置的话,我们写了一个简单的配置项目 TTTYManager

如何按用户来限制请求

nginx/lualib/access_checker.lua 文件中,默认使用了我们自己的逻辑取得用户标识,应该不会是你想要的,你需要做一些修改,告诉系统你的用户标识,比如下面的逻辑

local WebShield = require 'resty.web_shield'

local ip = ngx.var.remote_addr
local h = ngx.req.get_headers()
local uid = h['X-User-ID'] or 'nil'

-- 简单的取得 `nginx/lualib/web_shield_config' 中的配置,为了简单不再去取 mysql 配置
local config = require('web_shield_config')
local web_shield = WebShield.new(config.web_shield, config.shield)

-- 使用自定的用户标识进行检查,如果失败就返回 429
if not web_shield:check(ip, uid, ngx.var.request_method, ngx.var.uri) then
  ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
  ngx.say("Too many requests")
  ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end

结语

先就介绍到这,如果有问题的话,可以邮件(github)联系我,或是直接在项目中提 issue,此项目将会持续改进,欢迎关注我们 “天天投研” 团队。

共收到 2 条回复

结语没有句号。

tzwm 回复

这个 bug 已经修复,哈哈

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册