Openresty初探:使用Nginx Lua设计自己的WAF
本文介绍基于Openresty设计一个根据IP+URL做访问频率限制的web应用防火墙(WAF),其中涉及到Nginx、Redis等相关内容会做简单介绍。
一、为什么选择Openresty
通过 Lua 扩展 NGINX 实现的可伸缩的 Web 平台 。
根据官网( http://openresty.org/cn/ )介绍,Openresty是通过Lua拓展Nginx的高性能Web平台。在保证基础Nginx功能的情况下,Openresty通过其内部集成的大量Lua库,可以方便地使用Lua语言拓展自己的Web服务。
所以,Openresty使用Lua作为业务功能的开发,在保留Nginx高并发的特性的同时,又更加方便拓展。
二、准备
在写一个简易的WAF之前,先要对Openresty的组件和Nginx知识有基本的了解。
2.1 Openresty安装
http:// openresty.org/cn/instal lation.html
使用官网提供的源码包进行编译安装,安装方法和Nginx相同。安装目录结构:
[root@SC openresty]# ll
total 248
drwxr-xr-x 1 root root 512 Nov 16 19:06 bin
-rw-r--r-- 1 root root 22924 Nov 16 19:06 COPYRIGHT
drwxr-xr-x 1 root root 512 Nov 16 19:06 luajit
drwxr-xr-x 1 root root 512 Nov 16 19:06 lualib
drwxr-xr-x 1 root root 512 Nov 16 19:08 nginx
drwxr-xr-x 1 root root 512 Nov 16 19:06 pod
-rw-r--r-- 1 root root 226755 Nov 16 19:06 resty.index
drwxr-xr-x 1 root root 512 Nov 16 19:06 site
这里需要关注两个目录,bin/ 和 nginx/ 。bin目录是openresty编译后的一些基本工具,nginx目录类似Nginx源码编译之后的安装目录,使用方法也相同。
Openresty相关命令和Nginx类似:install_path/nginx/sbin/nginx ; install_path/nginx/sbin/nginx -t/-s 等。
2.2 Hello World
可不必关注工作目录,直接修改install_path/nginx/sbin/nginx/conf/nginx.conf文件编写官网上的示例demo。
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello, world</p>")
}
}
}
}
启动
[root@SC conf]# /opt/openresty/nginx/sbin/nginx
[root@SC conf]# curl 'http://localhost:8080'
<p>hello, world</p>
[root@SC conf]#
2.3 Demo:获取HTTP客户端访问IP地址
客户端IP常用来做HTTP访问白名单,在Nginx日志中,$remote_addr或者$http_x_forwarded_for字段记录了实际HTTP请求时客户端IP地址。$http_x_forwarded_for主要是在有反向代理的情况下,客户端实际IP会写在这个字段里,否则$remote_addr记录的是代理服务器的IP地址。
另外,在使用NAT的网络里,可以根据Openresty的返回获取到实际的出口IP。和curl http://ifconfig.me的功能类似。
-- 获取客户端IP地址
nginx.conf中类似如下配置
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
local uri = ngx.var.uri
local headers=ngx.req.get_headers()
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
ngx.say(ip)
}
}
}
}
重启Openresty
[root@SC conf]# /opt/openresty/nginx/sbin/nginx -s reload
[root@SC conf]# curl 'http://localhost:8080'
127.0.0.1
[root@SC conf]#
PS:在服务器有外网IP的情况下,curl后返回的就是客户端实际出口IP地址。
三、如何写一个WAF
上面介绍了Openresty的基本使用,接下来谈下如何写一个基于IP+URL的WEB应用防火墙。
3.1 设计
Openresty代理,最基本的需求是希望能够做到对于不同的IP有频率访问控制,阻挡恶意请求。写一个简易的CC防护WAF主要有如下几点:
1、客户端IP+URL拼接作为Redis的key
2、Redis的key设置过期时间
3、每次访问相同时Redis的key做自增
4、配置访问频率,如10/900(15分钟只能访问10次)。结合2,3两点做频控。
5、黑白名单配置
3.2 流程图
3.3 代码组织
3.3.1 nginx.conf
# 主要部分
http {
charset utf-8;
# lua文件查找路径
lua_package_path "$prefix/lua/?.lua;$prefix/lua/vendor/?.lua;$prefix/lua/waf/?.lua;;";
server {
listen 80;
location / {
default_type text/html;
# 入口
access_by_lua_block {
require("gw_waf").go()
}
}
}
}
3.3.2 Lua脚本
3.3.2.1 代码结构
[root@pc-nginx-az3 lua]# pwd
/opt/openresty/nginx/lua
[root@pc-nginx-az3 lua]# tree .
.
├── gw_waf.lua
├── vendor
│ ├── ipmatcher.lua
│ └── redis.lua
└── waf
├── config.lua
├── controller.lua
├── init.lua
└── lib.lua
2 directories, 7 files
gw_waf.lua:类似main函数,是access_by_lua的入口
vendor:存放三方库源码,Openresty官方的lua-resty-redis,lua-resty-mysql之类的都可以存放在这里。Nginx Lua标准包可以在Lua中直接引用,其他优秀的三方库可以直接放在自定义目录下
waf:具体的实现逻辑
config.lua:程序配置文件
controller.lua:控制逻辑
init.lua:具体实现
lib.lua:其他通用函数
3.3.2.2 控制-controller.lua
local
3.3.2.3 逻辑实现-init.lua
截取部分代码段说明
--allow white ip
3.4 建议
3.4.1 Redis服务端配置
timeout 60 ,设置服务端超时时间,否则高并发下TCP连接未及时释放导致Redis压力过大;
设置Redis密码
3.4.2 Lua 客户端
Lua客户端设置超时时间;
Lua客户端主动释放Redis连接,否则客户端请求出现超时导致拒绝连接
四、附录
4.1 验证
使用jemter做压测,验证功能和并发。访问的优先级的黑名单-白名单-频控,功能上测试完成可以对Redis中存储的数据进行验证。这里贴出的是部分Redis存储内容:
db0:keys=236200,expires=236200,avg_ttl=463353
TTL指令查看key的过期时间,其他Redis命令请参考 http://redis.cn/commands.html
4.2 更进一步
以上实现的只是WAF中比较小的一个方面。Lua中配置文件缺乏一个可视化的管理后台,攻击记录也没具体留存分析。后续的文章中将完善这两方面。
五、参考
5.1 Openresty官网
http://openresty.org
5.2 Nginx Lua库
Lua-resty-redis:https://github.com/openresty/lua-resty-redis
Lua-resty-ipmatcher: https://github.com/iresty/lua-resty-ipmatcher
5.3 Nginx WAF 实现
Ngx_lua_waf: https://github.com/loveshell/ngx_lua_waf