openrestry指定ip黑名单过滤 (nginx+lua+redis),api接口限制地区访问

一. 安装openrestry
二. 安装GeoLite2以及类库

#
mkdir -p /home/lua
#
wget --no-cache --no-check-certificate -O /home/lua/GeoLite2-City.mmdb.gz https://cdn.jsdelivr.net/npm/[email protected]/GeoLite2-City.mmdb.gz
#
cd /home/lua && gunzip GeoLite2-City.mmdb.gz 
#
wget https://codeload.github.com/anjia0532/lua-resty-maxminddb/zip/refs/heads/master 
#
cd /usr/local/openresty/1.21.4.1/bin
#
wget --no-cache -O ./lua-resty-maxminddb.zip https://codeload.github.com/anjia0532/lua-resty-maxminddb/zip/refs/heads/master 
#
unzip -o -d anjia0532 lua-resty-maxminddb.zip && mv anjia0532/lua-resty-maxminddb-master anjia0532/lua-resty-maxminddb
#
./opm get anjia0532/lua-resty-maxminddb
 ```

三. 配置相关脚本 使用封装的redis库

 ```shell
 #
 cd /usr/local/openresty/1.21.4.1/lualib/resty
 #
 vim redis_iresty.lua
 ```
 lua代码如下
 ```lua
 local redis_c = require "resty.redis"

local ok, new_tab = pcall(require, "table.new")
if not ok or type(new_tab) ~= "function" then
    new_tab = function (narr, nrec) return {} end
end

local _M = new_tab(0, 155)
_M._VERSION = '0.01'

local commands = {
    "append",            "auth",              "bgrewriteaof",
    "bgsave",            "bitcount",          "bitop",
    "blpop",             "brpop",
    "brpoplpush",        "client",            "config",
    "dbsize",
    "debug",             "decr",              "decrby",
    "del",               "discard",           "dump",
    "echo",
    "eval",              "exec",              "exists",
    "expire",            "expireat",          "flushall",
    "flushdb",           "get",               "getbit",
    "getrange",          "getset",            "hdel",
    "hexists",           "hget",              "hgetall",
    "hincrby",           "hincrbyfloat",      "hkeys",
    "hlen",
    "hmget",              "hmset",      "hscan",
    "hset",
    "hsetnx",            "hvals",             "incr",
    "incrby",            "incrbyfloat",       "info",
    "keys",
    "lastsave",          "lindex",            "linsert",
    "llen",              "lpop",              "lpush",
    "lpushx",            "lrange",            "lrem",
    "lset",              "ltrim",             "mget",
    "migrate",
    "monitor",           "move",              "mset",
    "msetnx",            "multi",             "object",
    "persist",           "pexpire",           "pexpireat",
    "ping",              "psetex",            "psubscribe",
    "pttl",
    "publish",      --[[ "punsubscribe", ]]   "pubsub",
    "quit",
    "randomkey",         "rename",            "renamenx",
    "restore",
    "rpop",              "rpoplpush",         "rpush",
    "rpushx",            "sadd",              "save",
    "scan",              "scard",             "script",
    "sdiff",             "sdiffstore",
    "select",            "set",               "setbit",
    "setex",             "setnx",             "setrange",
    "shutdown",          "sinter",            "sinterstore",
    "sismember",         "slaveof",           "slowlog",
    "smembers",          "smove",             "sort",
    "spop",              "srandmember",       "srem",
    "sscan",
    "strlen",       --[[ "subscribe",  ]]     "sunion",
    "sunionstore",       "sync",              "time",
    "ttl",
    "type",         --[[ "unsubscribe", ]]    "unwatch",
    "watch",             "zadd",              "zcard",
    "zcount",            "zincrby",           "zinterstore",
    "zrange",            "zrangebyscore",     "zrank",
    "zrem",              "zremrangebyrank",   "zremrangebyscore",
    "zrevrange",         "zrevrangebyscore",  "zrevrank",
    "zscan",
    "zscore",            "zunionstore",       "evalsha"
}

local mt = { __index = _M }

local function is_redis_null( res )
    if type(res) == "table" then
        for k,v in pairs(res) do
            if v ~= ngx.null then
                return false
            end
        end
        return true
    elseif res == ngx.null then
        return true
    elseif res == nil then
        return true
    end

    return false
end

function _M.close_redis(self, redis)  
    if not redis then  
        return  
    end  
    --释放连接(连接池实现)
    local pool_max_idle_time = self.pool_max_idle_time --最大空闲时间 毫秒  
    local pool_size = self.pool_size --连接池大小  

    local ok, err = redis:set_keepalive(pool_max_idle_time, pool_size)  
    if not ok then  
        ngx.say("set keepalive error : ", err)  
    end  
end  

-- change connect address as you need
function _M.connect_mod( self, redis )
    redis:set_timeout(self.timeout)

    local ok, err = redis:connect(self.ip, self.port)
    if not ok then  
        ngx.say("connect to redis error : ", err)  
        return self:close_redis(redis)  
    end

    if self.password then ----密码认证
        local count, err = redis:get_reused_times()
        if 0 == count then ----新建连接,需要认证密码
            ok, err = redis:auth(self.password)
            if not ok then
                ngx.say("failed to auth: ", err)
                return
            end
        elseif err then  ----从连接池中获取连接,无需再次认证密码
            ngx.say("failed to get reused times: ", err)
            return
        end
    end

    return ok,err;
end

function _M.init_pipeline( self )
    self._reqs = {}
end

function _M.commit_pipeline( self )
    local reqs = self._reqs

    if nil == reqs or 0 == #reqs then
        return {}, "no pipeline"
    else
        self._reqs = nil
    end

    local redis, err = redis_c:new()
    if not redis then
        return nil, err
    end

    local ok, err = self:connect_mod(redis)
    if not ok then
        return {}, err
    end

    redis:init_pipeline()
    for _, vals in ipairs(reqs) do
        local fun = redis[vals[1]]
        table.remove(vals , 1)

        fun(redis, unpack(vals))
    end

    local results, err = redis:commit_pipeline()
    if not results or err then
        return {}, err
    end

    if is_redis_null(results) then
        results = {}
        ngx.log(ngx.WARN, "is null")
    end
    -- table.remove (results , 1)

    --self.set_keepalive_mod(redis)
    self:close_redis(redis)  

    for i,value in ipairs(results) do
        if is_redis_null(value) then
            results[i] = nil
        end
    end

    return results, err
end

local function do_command(self, cmd, ... )
    if self._reqs then
        table.insert(self._reqs, {cmd, ...})
        return
    end

    local redis, err = redis_c:new()
    if not redis then
        return nil, err
    end

    local ok, err = self:connect_mod(redis)
    if not ok or err then
        return nil, err
    end

    redis:select(self.db_index)

    local fun = redis[cmd]
    local result, err = fun(redis, ...)
    if not result or err then
        -- ngx.log(ngx.ERR, "pipeline result:", result, " err:", err)
        return nil, err
    end

    if is_redis_null(result) then
        result = nil
    end

    --self.set_keepalive_mod(redis)
    self:close_redis(redis)  

    return result, err
end

for i = 1, #commands do
    local cmd = commands[i]
    _M[cmd] =
            function (self, ...)
                return do_command(self, cmd, ...)
            end
end

function _M.new(self, opts)
    opts = opts or {}
    local timeout = (opts.timeout and opts.timeout * 1000) or 1000
    local db_index= opts.db_index or 0
    local ip = opts.ip or '127.0.0.1'
    local port = opts.port or 6379
    local password = opts.password
    local pool_max_idle_time = opts.pool_max_idle_time or 60000
    local pool_size = opts.pool_size or 100

    return setmetatable({
            timeout = timeout,
            db_index = db_index,
            ip = ip,
            port = port,
            password = password,
            pool_max_idle_time = pool_max_idle_time,
            pool_size = pool_size,
            _reqs = nil }, mt)
end

return _M
 ```
 限制IP脚本如下
 ```shell
 vim  /home/lua/black_ip.lua

lua核心逻辑

local cjson = require "cjson"
local geo=require 'resty.maxminddb'
local arg_ip=ngx.var.arg_ip
local arg_node=ngx.var.arg_node

local enable_redis = true
local redis = require "resty.redis_iresty"
local redis_host = "10.7.0.11"
local redis_port = "6379"
local redis_password = nil
local redis_db_index = 0

local redis_key_fliter_countries = "ngx:filter:countries"
local redis_key_fliter_ips = "ngx:filter:ips"

local time = ngx.time()
local message = ""
local filter_enable = true
local filter_black_countries = {}
--{"上海","北京","南京","浙江省"}
local filter_black_ips = {"16.232.76.45"}

if not geo.initted() then
    geo.init("/home/lua/GeoLite2-City.mmdb")
end

function IsInTable(value, tbl)
    for k,v in ipairs(tbl) do
      if v == value then
      return true;
      end
    end
    return false;
    end

function getClientIp()
  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"
  return ip
end

function getCityName(testIp)
  testCityName = nil;
  res,err=geo.lookup(testIp or ngx.var.remote_addr)
  if not res then
      return testCityName;
  else
     sub = res["subdivisions"]
     if not sub then
       testCityName = nil;
     else
       testCityName =sub[1]["names"]["zh-CN"] or ""
     end
  end
  return testCityName
end

function isInFilter(testIp,filter_type,target_list)
    if not target_list then
      return false
    end
    if not filter_enable then
        return false;
    end
    if (filter_type == 1) then
        res,err=geo.lookup(testIp or ngx.var.remote_addr)
        if not res then
            return false;
        else
           testCityName = "";
            --ngx.say("info:",cjson.encode(res))
           sub = res["subdivisions"]
           if not sub then
             testCityName = "";
           else
             testCityName =sub[1]["names"]["zh-CN"] or ""
           end
            --ngx.say("name:",testCityName)
           return IsInTable(testCityName,target_list)
        end
    else
        return IsInTable(testIp,target_list);
    end
end

function get_filter_in_redis(key)
  filters = nil
  if enable_redis then
    local opts = {
      ip = redis_host,
      port = redis_port,
      password = redis_password,
      db_index = redis_db_index
    }
    local red = redis:new(opts)
    local data, err = red:lrange(key,0,-1);
    if data then
      filters = data
    end
  end
  return filters
end

local clientip = getClientIp()
local ip = arg_ip or clientip;

isHitted = false
if isInFilter(ip,0,filter_black_ips) then
  isHitted = true
else
  isHitted = isInFilter(ip,1,filter_black_countries);
end
if not isHitted then
  redis_filter_countries = get_filter_in_redis(redis_key_fliter_countries)
  redis_filter_ips = get_filter_in_redis(redis_key_fliter_ips)
  isHitted = isInFilter(ip,1,redis_filter_countries) or isInFilter(ip,2,redis_filter_ips)
end

if isHitted==true then
  ngx.exit(ngx.HTTP_FORBIDDEN)
else
  return
end

nginx配置如下

 server {
    listen *:80 default;
    server_name _;
    charset utf-8;
    location / {
        root  /home/www;
        index index.html index.htm;
    }
    location /lua {
                default_type "text/html";
                charset utf-8;
                access_by_lua_file /home/lua/black_ip.lua;
                content_by_lua "ngx.say('{\"msg\":\"hello world\",\"time\":2023}')";
        }

    location /stub_status {
            stub_status on;
            allow 10.8.0.0/8;
            allow 127.0.0.1;  #only allow requests from localhost
            deny all;   #deny all other hosts 
    }
    location = /favicon.ico {
          log_not_found     off;
          access_log     off;
    }

    location ~ /\.map {
        deny  all;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

    access_log off;
}
#配置redis
10.7.0.11:6379> LPUSH ngx:filter:ips "116.232.76.45"
(integer) 1
10.7.0.11:6379> LPUSH ngx:filter:countries "上海"
(integer) 1

测试使用

 #
 curl http://127.0.0.1/lua

完成!

正文完
 
linxiaokai
版权声明:本站原创文章,由 linxiaokai 2023-09-25发表,共计8083字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。