点赞
评论
收藏
分享
举报
通过nginx plus 实现动态黑白名单
发表于2020-11-09 10:26

浏览 3.9k

前言:

随着现代应用架构的发展和演进,特别是容器云的盛行,越来越多的用户将7层流量的控制从前端网络的应用交付设备转向到了在每个应用,微服务前的软负载SLB上。典型的SLB,如nginx,部署位置可以在网络边界,在服务器的前端,在容器云中,以反代,微网关,sidecar的形式存在。 因为业务在不断的变化,特别是随着devops文化的流行,业务变化的频率和业务对基础架构的诉求也在不断上升。因此,我们也在nginx上交付了越来越多的应用服务,其中就包括了大家熟悉的黑白名单功能。

从现代的安全架构上来看,内网是不安全的,这件事情也得到了共识,从而出现了零信任架构,出现了无界的安全,出现了双模安全,基于每个应用的定制化精细化安全策略的诉求也就出现。

那么如何基于每个应用或微服务,进行频率的控制是保证应用的稳定性,健壮性,可用性的关键。限流是一个很好的方法,但是静态的限流很容易被绕过,应用层的ddos攻击往往会压着访问频率,那么如果能够准确定位出每个客户端,每个session的请求频率,自动的把超出阈值的客户端加黑封禁一段时间,比如说3分钟,这样就可以实现自动化的黑白名单,保护服务的健壮性。 当然,还有更有意思的玩法,比如设置每秒100rps,超出阈值,动态设置50rps的限流阈值,再超,拉黑封禁,这样就是自动化阶梯限流。

本次我将介绍动态黑白名单的具体实现。

需求介绍 :

设置动态黑名单,判断客户端的每秒的请求数,如果超过阈值,自动的将该客户端地址写入动态黑名单,封锁一段时间,之后再解封锁。

设置静态白名单,在白名单的IP优先级最高,直接放过。

相关技术:

Nginx plus , njs,limit_req,kv

实现方法 :

通过limit_req_zone 设置限流的阈值;

通过limit_req_dry_run 不进行真正限流,仅通过dry run功能输出如果采用限流技术后的结果的变量,也就是limit_req_status

通过在njs中匹配变量limit_req_status的值是否是REJECTED_DRY_RUN,判断是否达到限流阈值。

4. 通过kv设置动态黑名单,key为客户端地址,value为是否block封锁的变量,如1为封锁,0为不封锁。

5. njs中完成逻辑功能实现:

5.1 如果单次请求,源地址在白名单中,放过,通过subrequest转回nginx中的id2 locationproxy pass到后端

5.2 如果单次请求的源地址在kv中没有值,null,那么赋值给ifblock变量为初始值0

5.3 如果单次请求 limit_req_status匹配REJECTED_DRY_RUN,表示达到限流阈值,对应的ifblock变量置为1,加入动态黑名单,响应499

5.4 如果单次请求,ifblock变量为0,表示没达到动态加黑的阈值,放过,通过subrequest转回nginx中的id2 locationproxy pass到后端

5.5 如果单次请求limit_req_status没有匹配REJECTED_DRY_RUN,但是ifblock变量为1,表示虽然现在没达到动态加黑的阈值,但是源地址还在动态黑名单中,还没有达到解冻时间,因此响应499

具体Nginx plus相关配置:

http {

js_import conf.d/kvcount1.js;    #调用njs文件

keyval_zone zone=bl:10m state=/etc/nginx/state_files/bl.json sync timeout=1m;

keyval $remote_addr $ifblock zone=bl;    #设置kv zone bl,设置kv变量

limit_req_zone $remote_addr zone=mylimit:10m rate=5r/s sync;  #设置限流zone

geo $remote_addr $ip_whitelist{     #设置白名单

default 0;

include conf.d/white_ip.conf;    #外置白名单配置文件,便于管理

}

}

white_ip.conf文件:

cat white_ip.conf

172.16.142.1/32 1;

221.176.33.0/24  1;

10.0.0.0/8  1;

192.168.0.0/24  1;

114.114.114.114  1;

location /black1 {

limit_req zone=mylimit;

limit_req_dry_run on;

js_content kvcount1.black;

}

location /id2/ {    #接收njssubrequest 返回的location

proxy_pass http://nplus2.nplus.com/;

}

具体NJS脚本:

function black(r) {

var limit = r.variables.limit_req_status;  # 将限流dryrun结果变量传入JS

var n = r.variables.ifblock;  # 将KVvalue变量传入JS

var b = r.variables.ip_whitelist;  # 将geo中的ip对应value变量传入JS

if ( b == 1 ) {                   # 如果在白名单,通过subrequest转回id2 location

var url = "/id2"+r.uri;

r.subrequest(url, { method: r.method },

function(res) {

r.return(res.status, res.responseBody);

});

} else {

if ( n = n ) {                 # 判断kv中没有值,赋值给ifblock变量为初始值0

if ( limit == "REJECTED_DRY_RUN") {  # 判断当前request超速。

r.variables.ifblock = 1;       #设置黑名单变量为1

r.return(499, "you are blocked in 60s");

} else {

if ( n == 0 ) {        #判断黑名单变量为0,通过subrequest转回id2 location

var url = "/id2"+r.uri;

r.subrequest(url, { method: r.method },

function(res) {

r.return(res.status, res.responseBody);

});

}

if ( n == 1 ) {        #判断黑名单变量为0,禁止访问。

r.return(499, "you are blocked in 60s");

}

}  

} else {

r.variables.ifblock = 0;

var url = "/id2"+r.uri;

r.subrequest(url, { method: r.method },

function(res) {

r.return(res.status, res.responseBody);

});

}

}

}

export default {black};

注意事项:

设置kv变量的时候,$remote_addr可以用$binary_remote_addr代替,例如下面的配置。

keyval_zone zone=bl:10m state=/etc/nginx/state_files/bl.json sync timeout=1m;

keyval $remote_addr $ifblock zone=bl;

原因是The $binary_remote_addr variable’s size is always 4 bytes for IPv4 addresses or 16 bytes for IPv6 addresses. The stored state always occupies 64 bytes on 32-bit platforms and 128 bytes on 64-bit platforms. One megabyte zone can keep about 16 thousand 64-byte states or about 8 thousand 128-byte states.

2 KV zone的容量规划,64位的服务器,1M空间可以存储8k的黑名单条目。

keyval_zone zone=bl:10m state=/etc/nginx/state_files/bl.json sync timeout=1m;

keyval $remote_addr $ifblock zone=bl;

具体实现上,可以基于客户端的ip,或者基于cookie来实现

4 Subrequest功能非常强大,具体细节请参考https://nginx.org/en/docs/njs/reference.html#http

关于njs相关资料

Docs: 

https://nginx.org/en/docs/njs/

Blog: 

https://www.nginx.com/blog/introduction-nginscript/

Github:

https://github.com/f5devcentral/nginx-njs-usecases

https://github.com/xeioex/njs-examples

nginx-cn.net:

https://www.nginx-cn.net

总结

本文只针对功能需要的最小配置。可以看到NJSKV可以应用到需要较为复杂的应用场景中,给我们带来更多的想象空间。

已修改于2023-03-07 02:20
本作品系原创
创作不易,留下一份鼓励
林夏

暂无个人介绍

关注



写下您的评论
发表评论
全部评论(0)

按点赞数排序

按时间排序

关于作者
林夏
这家伙很懒还未留下介绍~
2
文章
0
问答
1
粉丝
相关文章
12分钟阅读 本文链接:https://blog.openacid.com/algo/slimarray/ slimarray 场景和问题在时序数据库,或列存储为基础的系统中,很常见的形式就是存储一个整数数组,例如slim这个项目按天统计的star数: 这类数据有有很明显的统一的变化趋势,对这类数据的存储,我们可以利用数据分布的特点,将整体数据的大小压缩到几分之一.这就是slimarray要做的事情. 使用slimarray,可以将数据容量减小到gzip差不多的大小,同时还能允许直接访问这些数据!测试中我们选择了2组随机数,以及现实中的2份数据,一个ipv4的数据库,一个slim的star变化数据,服用slimarray后效果如下: 在达到gzip同等压缩率的前提下,构建slimarray和访问的性能也非常高: 构建slimarray时,平均每秒可压缩6百万个数组元素;读取一个数组元素平均花费7ns/op. 本文手把手的介绍slimarray的原理,实现: 初步想法:
点赞 1
浏览 3.5k
最近在准备优化日志请求时遇到了一些令人疑惑的问题,比如为什么响应头里出现了两个cachecontrol、为什么明明设置了nocache却还是发请求,为什么多次访问时有时请求里带了etag,有时又没有带?等等。。。后来查了一些资料以及同事亲自验证,总算对这些问题有了个清晰的理解,现在整理出来以备忘。1、缓存的分类缓存分为服务端侧(serverside,比如Nginx、Apache)和客户端侧(clientside,比如webbrowser)。服务端缓存又分为代理服务器缓存和反向代理服务器缓存(也叫网关缓存,比如Nginx反向代理、Squid等),其实广泛使用的CDN也是一种服务端缓存,目的都是让用户的请求走”捷径“,并且都是缓存图片、文件等静态资源。客户端侧缓存一般指的是浏览器缓存,目的就是加速各种静态资源的访问,想想现在的大型网站,随便一个页面都是一两百个请求,每天pv都是亿级别,如果没有缓存,用户体验会急剧下降、同时服务器压力和网络带宽都面临严重的考验。2、浏览器缓存机制详解浏览器缓存控制机制有两种:HTMLMeta标签vs.HTTP头信息
点赞 0
浏览 3.2k
【产品更新】NGINX Ingress Controller v3.6.2 现已上线。新版本发布了 Helm Chart 3.6.2,在 helm schema 中将 Kubernetes 版本升级至 1.31.0,并修复少量 bugs。访问 NGINX 中文官方开源社区(nginx.org.cn)了解详情。
点赞 0
浏览 2.7k