2团
Published on 2024-08-30 / 35 Visits
0
0

Openresty访问指定路径设置动态黑名单

1. 前言

网站使用Openresty进行反代,发现经常有客户端访问指定路径,例如:

  • www.2tuan.work/.user

  • www.2tuan.work/.env

  • www.2tuan.work/.git/config

针对此类访问,前序配置基本上是直接deny,配置示例如下:

        location ~ /(\.user|\.git)$ {
            deny all;
        }

但是最近想着复习下以前的Openresty学习知识,实现如下目标:访问特定路径的客户端将拉入黑名单,拉黑有效期为1小时。

2. 实现

2.1 黑名单

http {
    # 客户端黑名单字典
    lua_shared_dict blacklist 10m;
    
    location = /403 {
          # 仅接收内部重定向请求,不允许外部客户端访问
          internal;
          default_type text/html;
          return 403 'Access Forbidden';
    }
}

在配置文件的http模块下:

  • 创建大小为10m的共享字典,命名为blacklist,后续将需要拉黑的客户端IP存储至此字典;

  • 设置403路径,若客户端被拉黑,请求将被重定向至此路径。

2.2 拉黑脚本

创建拉黑脚本,示例如下:

local blacklist = ngx.shared.blacklist
local client_ip = ngx.var.remote_addr

-- 检查客户端 IP 是否在黑名单中
if blacklist:get(client_ip) then
    ngx.log(ngx.INFO, "Client IP ", client_ip, " is in the blacklist.")
    -- 重定向请求至403路径
    ngx.exec("/403")
end

# 不建议使用nxg.var.request_uri
local uri = ngx.var.uri
-- 定义需要匹配的模式
local patterns_end = { "%.php$", "%.env$", "%.git$", "%.json$", "%.sql$", "%.yamnl", "%.yml$" }
local patterns_contain = { "webui", "geoserver", "wlwmanifest%.xml", "%.git" }

-- 检查请求 URI 是否以指定模式结尾
for _, pattern in ipairs(patterns_end) do
    if uri:match(pattern) then
        -- 将客户端 IP 加入黑名单,并设置1小时后过期
        blacklist:set(client_ip, true, 3600)
        ngx.log(ngx.INFO, "Client IP ", client_ip, " has been blacklisted for 1 hour.")

        -- 跳转到内部的/403接口
        return ngx.exec("/403")
    end
end

-- 检查请求 URI 是否包含指定模式
for _, pattern in ipairs(patterns_contain) do
    if uri:match(pattern) then
        -- 将客户端 IP 加入黑名单,并设置1小时后过期
        blacklist:set(client_ip, true, 3600)
        ngx.log(ngx.INFO, "Client IP ", client_ip, " has been blacklisted for 1 hour.")

        -- 跳转到内部的/403接口
        return ngx.exec("/403")
    end
end

文件命名为blacklist.lua,放置于/data/openresty文件夹下。

2.3 定期清理缓存过期IP

虽然lua_shared_dict支持设置缓存过期时间,但是手动创建定期清除任务,具有如下优点:

  1. 内存管理:

    • lua_shared_dict 的内存是预先分配的,大小是固定的。如果不定期清除过期的键,内存可能会被过期的键占用,导致新的键无法插入。

    • 定期清除过期的键可以确保内存的有效利用,避免内存泄漏。

  2. 性能优化:

    • 虽然 lua_shared_dict 会自动管理过期的键,但在高并发的情况下,自动清理可能无法及时进行,导致性能下降。

    • 定期清除过期的键可以减轻自动清理的负担,提高性能。

  3. 数据一致性:

    • 定期清除过期的键可以确保数据的一致性,避免过期的数据被误用。

在nginx配置文件的http模块,添加定时清除任务,示例代码如下所示:

http { 
   init_worker_by_lua_block {
        local function remove_expired_ips()
            local blacklist = ngx.shared.blacklist
            local keys = blacklist:get_keys(0)
            for _, key in ipairs(keys) do
                local ttl = blacklist:ttl(key)
                if ttl == -1 then
                    blacklist:delete(key)
                    ngx.log(ngx.INFO, "Unban ", key, " from blacklist.")
                end
            end
        end

        -- 每分钟检查一次黑名单,删除过期的 IP
        local ok, err = ngx.timer.every(60, remove_expired_ips)
        if not ok then
            ngx.log(ngx.ERR, "failed to create timer: ", err)
        end
    }
}

需要注意:

  • blacklist:get_keys(max_count):要返回的最大键数。如果设置为 0,则返回所有键;

  • ipairs: 是 Lua 标准库中的一个函数,用于迭代数组(即具有连续整数索引的表)。

3. 配置文件

通用路径访问配置中,添加黑名单检查脚本。

        location / {
            # 接入检查:若当前访问IP已被拉黑,直接返回限制访问响应
            access_by_lua_file /data/openresty/blacklist.lua;

            proxy_set_header HOST $host;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://2tuan-work;
        }

常规路径,添加访问检查,详见配置文件中的注释内容。

4. 尝试

尝试访问非法路径后,Openresty争取拉黑,日志如下所示:

黑名单拦截记录


Comment