Openresty之黑名单过滤

        如果是第一次看这个文章,可以先看下这篇openresty介绍性的文章:Openresty概述介绍

        有些时候在你配置nginx的时候可能需要做一些黑名单拦截的操作,设置黑名单,拦截存在这个黑名单中的url,使用openresty很方便,使用lua代码在access_by_lua*的过程中添加过滤代码即可实现,实现这个功能需要以下几步操作。

  • 编写lua脚本文件,实现lua拦截
  • 添加依赖库源文件到lualib目录
  • 拷贝lua代码文件到路径
  • 修改nginx.conf文件,引入黑名单拦截代码

1、编写lua脚本文件

lua脚本文件只要是获取每次请求的url地址,然后再根据黑名单的数据库中的黑名单进行匹配,存在则将请求拦截,并返回禁止访问的信息。

lua脚本文件如下:名字命为access.lua

local iputils = require("resty.iputils")
local cjson = require "cjson"
iputils.enable_lrucache()
local blacklist_ips = {
    "127.0.0.1",
    "10.10.10.0/24",
    "192.168.0.0/16",
}

local retdata = {
    code=ngx.HTTP_FORBIDDEN,
    message="黑名单禁止访问",
    data={}
}

local blacklist = iputils.parse_cidrs(blacklist_ips)

if iputils.ip_in_cidrs(ngx.var.remote_addr, blacklist) then
    ngx.status = ngx.HTTP_FORBIDDEN
    ngx.header["Content-type"] = 'application/json'
    local output = cjson.encode(retdata)
    ngx.say(output)
    return ngx.exit(0)
end

从上面的代码可以看到,我们用到了一个外部库,叫iputils,我们把它require进来,然后使用它来做黑名单筛选,其实在这个地方稍微改一下代码,也可以做白名单的限制,大家应该已经明白了;然后就是代码里面是写死的一个白名单,这个只是我目前的一个教程,你可以通过其他方式获取,比如说通过一个变量,然后这个变量可以通过定时任务通过http请求或者啥获取最新的黑名单来更新这个变量。定时任务一般通过下面的操作方法进行:

--延时5秒设置
local delay = 5
--每隔5秒定时执行handle_fun这个函数
ok,err=ngx.timer.every(delay, handle_fun)

2、添加依赖库源文件

如果使用上面的代码执行的时候报找不到模块iputils的话,将文件iputils.lua文件拷贝到路径/usr/local/openresty/lualib/resty下面,iputils.lua文件内容如下:

local ipairs, tonumber, tostring, type = ipairs, tonumber, tostring, type
local bit        = require("bit")
local lshift     = bit.lshift
local band       = bit.band
local bor        = bit.bor
local xor        = bit.bxor
local byte       = string.byte
local str_find   = string.find
local str_sub    = string.sub

local lrucache = nil

local _M = {
    _VERSION = '0.3.0',
}

local mt = { __index = _M }


-- Precompute binary subnet masks...
local bin_masks = {}
for i=0,32 do
    bin_masks[tostring(i)] = lshift((2^i)-1, 32-i)
end
-- ... and their inverted counterparts
local bin_inverted_masks = {}
for i=0,32 do
    local i = tostring(i)
    bin_inverted_masks[i] = xor(bin_masks[i], bin_masks["32"])
end

local log_err
if ngx then
    log_err = function(...)
        ngx.log(ngx.ERR, ...)
    end
else
    log_err = function(...)
        print(...)
    end
end


local function enable_lrucache(size)
    local size = size or 4000  -- Cache the last 4000 IPs (~1MB memory) by default
    local lrucache_obj, err = require("resty.lrucache").new(size)
    if not lrucache_obj then
        return nil, "failed to create the cache: " .. (err or "unknown")
    end
    lrucache = lrucache_obj
    return true
end
_M.enable_lrucache = enable_lrucache


local function split_octets(input)
    local pos = 0
    local prev = 0
    local octs = {}

    for i=1, 4 do
        pos = str_find(input, ".", prev, true)
        if pos then
            if i == 4 then
                -- Should not have a match after 4 octets
                return nil, "Invalid IP"
            end
            octs[i] = str_sub(input, prev, pos-1)
        elseif i == 4 then
            -- Last octet, get everything to the end
            octs[i] = str_sub(input, prev, -1)
            break
        else
            return nil, "Invalid IP"
        end
        prev = pos +1
    end

    return octs
end


local function unsign(bin)
    if bin < 0 then
        return 4294967296 + bin
    end
    return bin
end


local function ip2bin(ip)
    if lrucache then
        local get = lrucache:get(ip)
        if get then
            return get[1], get[2]
        end
    end

    if type(ip) ~= "string" then
        return nil, "IP must be a string"
    end

    local octets = split_octets(ip)
    if not octets or #octets ~= 4 then
        return nil, "Invalid IP"
    end

    -- Return the binary representation of an IP and a table of binary octets
    local bin_octets = {}
    local bin_ip = 0

    for i,octet in ipairs(octets) do
        local bin_octet = tonumber(octet)
        if not bin_octet or bin_octet < 0 or bin_octet > 255 then
            return nil, "Invalid octet: "..tostring(octet)
        end
        bin_octets[i] = bin_octet
        bin_ip = bor(lshift(bin_octet, 8*(4-i) ), bin_ip)
    end

    bin_ip = unsign(bin_ip)
    if lrucache then
        lrucache:set(ip, {bin_ip, bin_octets})
    end
    return bin_ip, bin_octets
end
_M.ip2bin = ip2bin


local function split_cidr(input)
    local pos = str_find(input, "/", 0, true)
    if not pos then
        return {input}
    end
    return {str_sub(input, 1, pos-1), str_sub(input, pos+1, -1)}
end


local function parse_cidr(cidr)
    local mask_split = split_cidr(cidr, '/')
    local net        = mask_split[1]
    local mask       = mask_split[2] or "32"
    local mask_num   = tonumber(mask)

    if not mask_num or (mask_num > 32 or mask_num < 0) then
        return nil, "Invalid prefix: /"..tostring(mask)
    end

    local bin_net, err = ip2bin(net) -- Convert IP to binary
    if not bin_net then
        return nil, err
    end
    local bin_mask     = bin_masks[mask] -- Get masks
    local bin_inv_mask = bin_inverted_masks[mask]

    local lower = band(bin_net, bin_mask) -- Network address
    local upper = bor(lower, bin_inv_mask) -- Broadcast address
    return unsign(lower), unsign(upper)
end
_M.parse_cidr = parse_cidr


local function parse_cidrs(cidrs)
    local out = {}
    local i = 1
    for _,cidr in ipairs(cidrs) do
        local lower, upper = parse_cidr(cidr)
        if not lower then
            log_err("Error parsing '", cidr, "': ", upper)
        else
            out[i] = {lower, upper}
            i = i+1
        end
    end
    return out
end
_M.parse_cidrs = parse_cidrs


local function ip_in_cidrs(ip, cidrs)
    local bin_ip, bin_octets = ip2bin(ip)
    if not bin_ip then
        return nil, bin_octets
    end

    for _,cidr in ipairs(cidrs) do
        if bin_ip >= cidr[1] and bin_ip <= cidr[2] then
            return true
        end
    end
    return false
end
_M.ip_in_cidrs = ip_in_cidrs


local function binip_in_cidrs(bin_ip_ngx, cidrs)
    if 4 ~= #bin_ip_ngx then
        return false, "invalid IP address"
    end

    local bin_ip = 0
    for i=1,4 do
        bin_ip = bor(lshift(bin_ip, 8), byte(bin_ip_ngx, i))
    end
    bin_ip = unsign(bin_ip)

    for _,cidr in ipairs(cidrs) do
        if bin_ip >= cidr[1] and bin_ip <= cidr[2] then
            return true
        end
    end
    return false
end
_M.binip_in_cidrs = binip_in_cidrs

return _M

3、拷贝lua代码文件到路径

拷贝access.lua文件到路径/usr/local/openresty/nginx/lua/

4、修改nginx.conf文件

修改nginx.conf文件,引入黑名单拦截代码,修改nginx.conf文件内容如下:

#user  nobody;
worker_processes  1;
 
error_log  logs/error.log  error;
error_log  logs/error.log  notice;
error_log  logs/error.log  info;
pid        logs/nginx.pid;
 
events {
    worker_connections  1024;
}
 
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';
    # access_log  logs/access.log  main;
    # error_log   logs/error.log error; 
 
    lua_package_path "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/nginx/lua/?.lua;";
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
 
 
    sendfile        on;
    #tcp_nopush     on;
 
    #keepalive_timeout  0;
    keepalive_timeout  65;
    proxy_connect_timeout 3s;
 
    #gzip  on;
 
   
    # HTTPS server
    server {
       listen       80;
       server_name  localhost;
 
       location / {
            access_by_lua_file 	lua/access.lua;
            #设置代理目的url变量
            proxy_pass https://127.0.0.1;
       }
 
    }
 
}

lua_package_path "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/nginx/lua/?.lua;";

access_by_lua_file     lua/access.lua;

第一句是添加lua代码的路径,第二句代码是指定所有请求都会经过access.lua文件,在里面做黑名单操作,过滤到有一样的ip地址,黑名单拦截功能就会生效,返回给请求者错误码和禁止访问的错误信息。

猜你喜欢

转载自blog.csdn.net/u013896064/article/details/128706446