在上一篇“使用redis来调用iptables,封禁恶意IP” 一文中已经讲解RedisPushIptables的用法,在经过几个版本迭代后,该模块功能更强大了,语法有所改变,所以需要新开篇幅来讲解下。
RedisPushIptables-6.2.tar.gz为最新版本,已经支持iptables、ipset、pf、nftables防火墙,意味着它已经跨平台支持,Linux、BSD、MacOS。最重要的是添加了动态删除防火墙规则的功能,当然这个功能ipset也有。虽然fail2ban也有此功能,但是极其消耗资源因为它是轮询来获取任务的。
本篇主要介绍RedisPushIptables模块的实现原理、安装方法、API使用方法以及适用范围。并与Fail2ban作了对比以便读者了解二者的区别,RedisPushIptables不受编程语言的限制。意味着开发者都可以使用它来进行业务防护,接着讲解了怎样重新封装lib库从而支持API调用,最后给出了部分编程语言调用API的示例,供读者参阅。
简介
RedisPushIptables是Redis的一个模块, 也可以把它理解为防火墙API调用库,该模块可以通过 redis 来操作 iptables 的 filter表INPUT链规则ACCEPT和DROP,而相对于BSD系统该模块则是对应pf防火墙。RedisPushIptables更新防火墙规则以在指定的时间内拒绝IP地址或永远拒绝。比如用来防御攻击。自此普通开发者也可以使用iptables或者PF,而不必再理会复杂的防火墙语法。
与Fail2Ban比较
主要从两个方面,实现原理和实用性。Fail2Ban倾向于事后分析,需要监控日志,支持的应用也比较多,只需简单配置即可。而RedisPushIptables倾向于实效性,不需要监控日志,但是,需要程序编码时调用API,使用门槛较高,并不适用所有人。
Fail2Ban
Fail2Ban是一种入侵防御软件框架,可以保护计算机服务器免受暴力攻击。用Python编程语言编写,它能够在POSIX系统上运行,该系统具有本地安装的数据包控制系统或防火墙的接口,例如iptables或TCP Wrapper。
fail2ban通过监控操作日志文件(如/var/log/auth.log,/var/log/apache/access.log等)选中的条目并运行基于他们的脚本。最常用于阻止可能属于试图破坏系统安全性的主机的所选IP地址。它可以禁止在管理员定义的时间范围内进行过多登录尝试或执行任何其他不需要的操作的任何主机IP地址。包括对IPv4和IPv6的支持。可选择更长时间的禁令可以为不断回来的滥用者进行定制配置。Fail2Ban通常设置为在一定时间内取消阻止被阻止的主机,以便不“锁定”任何可能暂时错误配置的真正连接。但是,几分钟的unban时间通常足以阻止网络连接被恶意连接淹没,并降低字典攻击成功的可能性。
每当检测到滥用的IP地址时,Fail2Ban都可以执行多个操作:更新Netfilter / iptables或PF防火墙规则,TCP Wrapper的hosts.deny表,拒绝滥用者的IP地址; 邮件通知; 或者可以由Python脚本执行的任何用户定义的操作。
标准配置附带Apache,Lighttpd,sshd,vsftpd,qmail,Postfix和Courier Mail Server的过滤器。过滤器是被Python定义的正则表达式,其可以由熟悉正则表达式的管理员可以方便地定制。过滤器和操作的组合称为“jail”,是阻止恶意主机访问指定网络服务的原因。除了随软件一起分发的示例之外,还可以为任何创建访问日志文件的面向网络的进程创建“jail”。
Fail2Ban类似于DenyHosts [...],但与专注于SSH的DenyHosts不同,Fail2Ban可以配置为监视将登录尝试写入日志文件的任何服务,而不是仅使用/etc/hosts.deny来阻止IP地址/ hosts,Fail2Ban可以使用Netfilter / iptables和TCP Wrappers /etc/hosts.deny。
缺点
- Fail2Ban无法防范分布式暴力攻击。
- 没有与特定于应用程序的API的交互。
- 太过依赖正则表达式,不同的程序需要各自对应的正则。
- 效率低下,性能受日志数量影响
- IP列表很多时,内存消耗很高
RedisPushIptables
虽然与Fail2Ban比较起来,RedisPushIptables支持还不是很完善,但是,术业有专攻,它的优势在于高性能,用C语言实现,同样支持跨平台Linux、BSD、MacOS。可以通过API调用,意味着redis官方支持的编程语言都可以使用,应用范围不受限。Fail2Ban是被动防御的需要根据关键字实时获取应用程序日志,匹配字符串再计算阈值达到就封禁IP地址。而RedisPushIptables业务主动调用,不需要分析日志。同样支持动态删除iptables或者PF规则,比fail2ban更省资源。
缺点
- 需要开发者编码时调用API。
- 无法防范分布式暴力攻击。
- 目前IPv6在我国还没普及所以不支持
安装
在安装RedisPushIptables之前,需要先安装redis。下面为版本redis-5.0.3.tar.gz
root@debian:~/bookscode# git clone https://github.com/limithit/RedisPushIptables.git
root@debian:~/bookscode# wget http://download.redis.io/releases/redis-5.0.3.tar.gz
root@debian:~/bookscode# tar zxvf redis-5.0.3.tar.gz
root@debian:~/bookscode#cd redis-5.0.3&& make
root@debian:~/bookscode/redis-5.0.3# make test
root@debian:~/bookscode/redis-5.0.3/deps/hiredis#make&& make install
root@debian:~/bookscode/redis-5.0.3/src#cp redis-server redis-sentinel redis-cliredis-benchmark redis-check-rdb redis-check-aof /usr/local/bin/
root@debian:~/bookscode/redis-5.0.3/utils# ./install_server.sh
root@debian:~/bookscode/redis-5.0.3# cd deps/hiredis
root@debian:~/bookscode/redis-5.0.3/deps/hiredis# make && make install
root@debian:~/bookscode/redis-5.0.3# echo /usr/local/lib >> /etc/ld.so.conf
root@debian:~/bookscode/redis-5.0.3# ldconfig
root@debian:~/bookscode# cd RedisPushIptables
root@debian:~/bookscode/ RedisPushIptables # make && make install
注意
编译时有四个选项 make、make CFLAGS=-DWITH_IPSET、make CFLAGS=-DWITH_NFTABLES和make CFLGAS=-DBSD
Linux系统默认是make选项即使用iptables,make CFLAGS=-DWITH_IPSET则是使用ipset更快地管理规则,make CFLAGS=-DWITH_NFTABLES则是启用nftables防火墙
make CFLGAS=-DBSD则是在BSD和MacOS系统上默认编译使用
可以使用以下redis.conf配置指令加载模块:
loadmodule /path/to/iptablespush.so
也可以使用以下命令在运行时加载模块:
MODULE LOAD /path/to/iptablespush.so
可以使用以下命令卸载模块:
MODULE unload iptables-input-filter
动态删除配置
默认情况下,禁用键空间事件通知,虽然不太明智,但该功能会使用一些CPU。使用redis.conf的notify-keyspace-events或CONFIG SET启用通知。将参数设置为空字符串会禁用通知。为了启用该功能,使用了一个非空字符串,由多个字符组成,其中每个字符都具有特殊含义,如下所示:
K Keyspace events, published with __keyspace@<db>__ prefix.
E Keyevent events, published with __keyevent@<db>__ prefix.
g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$ String commands
l List commands
s Set commands
h Hash commands
z Sorted set commands
x Expired events (events generated every time a key expires)
e Evicted events (events generated when a key is evicted for maxmemory)
A Alias for g$lshzxe, so that the "AKE" string means all the events.
字符串中至少应存在K或E,否则无论字符串的其余部分如何都不会传递任何事件。例如,只为列表启用键空间事件,配置参数必须设置为Kl,依此类推。字符串KEA可用于启用每个可能的事件。
# redis-cli config set notify-keyspace-events Ex
也可以使用以下redis.conf配置指令加载模块:
notify-keyspace-events Ex
#notify-keyspace-events "" #
注释掉这行
使用root用户运行ttl_iptables守护程序
root@debian:~/RedisPushIptables# /etc/init.d/ttl_iptables start
日志在/var/log/ttl_iptables.log中查看
root@debian:~# redis-cli TTL_DROP_INSERT 192.168.18.5 60
(integer) 12
root@debian:~# date
Fri Mar 15 09:38:49 CST 2019
root@debian:~/RedisPushIptables# tail -f /var/log/ttl_iptables.log
2019/03/15 09:39:48 pid=5670 iptables -D INPUT -s 192.168.18.5 -j DROP
指令
RedisPushIptables目前有五个指令,管理filter表中的INPUT链。为了保证规则生效,采用插入规则而不是按序添加规则,这么做的原因是,因为iptables是按顺序执行的。此外加入了自动去重功能(ipset和pfctl自带去重)。使用者不必担心会出现重复的规则,只需要添加即可。
accept_insert
等同iptables -I INPUT -s x.x.x.x -j ACCEPT
- accept_delete
等同iptables -D INPUT -s x.x.x.x -j ACCEPT
- drop_insert
等同iptables -I INPUT -s x.x.x.x -j DROP
- drop_delete
等同iptables -D INPUT -s x.x.x.x -j DROP
- ttl_drop_insert
例ttl_drop_insert 192.168.18.5 60
等同iptables -I INPUT -s x.x.x.x -j DROP 60秒后ttl_iptables守护进程自动删除iptables -D INPUT -s x.x.x.x -j DROP
客户端API示例
理论上除了C语言原生支持API调用,其他语言API调用前对应的库都要重新封装,因为第三方模块并不被其他语言支持。这里只示范C、Python、Bash、Lua其他编程语言同理。
C编程
C只需要编译安装hiredis即可。步骤如下:
root@debian:~/bookscode/redis-5.0.3/deps/hiredis#make install
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis.h>
int main(int argc, char **argv) {
unsigned int j;
redisContext *c;
redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
int port = (argc > 2) ? atoi(argv[2]) : 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout);
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis context\n");
}
exit(1);
}
reply = redisCommand(c,"drop_insert 192.168.18.3");
printf("%d\n", reply->integer);
freeReplyObject(reply);
reply = redisCommand(c,"accept_insert 192.168.18.4");
printf("%d\n", reply->integer);
freeReplyObject(reply);
reply = redisCommand(c,"drop_delete 192.168.18.3");
printf("%d\n", reply->integer);
freeReplyObject(reply);
reply = redisCommand(c,"accept_delete 192.168.18.5");
printf("%d\n", reply->integer);
freeReplyObject(reply);
redisFree(c);
return 0;
}
gcc example.c -I/usr/local/include/hiredis -lhiredis
编译即可
Python编程
root@debian:~/bookscode# git clone https://github.com/andymccurdy/redis-py.git #下载Python lib库
下载好之后不要急着编译安装,先编辑redis-py/redis/client.py文件,添加代码如下:
# COMMAND EXECUTION AND PROTOCOL PARSING
def execute_command(self, *args, **options):
"Execute a command and return a parsed response"
.....
.....
def drop_insert(self, name):
"""
Return the value at key ``name``, or None if the key doesn't exist
"""
return self.execute_command('drop_insert', name)
def accept_insert(self, name):
"""
Return the value at key ``name``, or None if the key doesn't exist
"""
return self.execute_command('accept_insert', name)
def drop_delete(self, name):
"""
Return the value at key ``name``, or None if the key doesn't exist
"""
return self.execute_command('drop_delete', name)
def accept_delete(self, name):
"""
Return the value at key ``name``, or None if the key doesn't exist
"""
return self.execute_command('accept_delete', name)
def ttl_drop_insert(self, name, blocktime):
"""
Return the value at key ``name``, or None if the key doesn't exist
"""
return self.execute_command('ttl_drop_insert', name, blocktime)
为了不误导读者,上述代码不加注释了,只是在类里添加几个函数而已,不需要解释
root@debian:~/bookscode/redis-py# python setup.py build
root@debian:~/bookscode/redis-py# python setup.py install
root@debian:~/bookscode/8/redis-py# python
Python 2.7.3 (default, Nov 19 2017, 01:35:09)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import redis
>>> r = redis.Redis(host='localhost', port=6379, db=0)
>>> r.drop_insert('192.168.18.7')
12L
>>> r.accept_insert('192.168.18.7')
12L
>>> r.accept_delete('192.168.18.7')
0L
>>> r.drop_delete('192.168.18.7')
0L
>>> r.ttl_drop_insert('192.168.18.7', 600)
12L
>>>
Bash编程
examples.sh
#!/bin/bash
for ((i=1; i<=254; i++))
do
redis-cli TTL_DROP_INSERT 192.168.17.$i 60
done
redis-cli DROP_INSERT 192.168.18.5
redis-cli DROP_DELETE 192.168.18.5
redis-cli ACCEPT_INSERT 192.168.18.5
redis-cli ACCEPT_DELETE 192.168.18.5
Lua编程
git clone https://github.com/nrk/redis-lua.git #下载Lua lib库
下载后编辑redis-lua/src/redis.lua 添加以下代码:
redis.commands = {
.....
ttl = command('TTL'),
drop_insert = command('drop_insert'),
drop_delete = command('drop_delete'),
accept_insert = command('accept_insert'),
accept_delete = command('accept_delete'),
ttl_drop_insert = command('ttl_drop_insert'),
pttl = command('PTTL'), -- >= 2.6
.....
示例代码examples.lua
package.path = "../src/?.lua;src/?.lua;" .. package.path
pcall(require, "luarocks.require") --不要忘记安装Luasocket库
local redis = require 'redis'
local params = {
host = '127.0.0.1',
port = 6379,
}
local client = redis.connect(params)
client:select(0) -- for testing purposes
client:drop_insert('192.168.1.1')
client:drop_delete('192.168.1.1')
client:ttl_drop_insert('192.168.1.2', '60') --加入规则后60秒后自动删除添加的规则
local value = client:get('192.168.1.2')
print(value)
最后,目前还缺少Java、php常用语言的驱动,由于我不太擅长太多语言,有兴趣的朋友可以提交PR来补充。