95 lines
2.1 KiB
Lua
95 lines
2.1 KiB
Lua
local rules = require "kong.plugins.bot-detection.rules"
|
|
local strip = require("kong.tools.string").strip
|
|
local lrucache = require "resty.lrucache"
|
|
local kong_meta = require "kong.meta"
|
|
|
|
local ipairs = ipairs
|
|
local re_find = ngx.re.find
|
|
|
|
local BotDetectionHandler = {}
|
|
|
|
BotDetectionHandler.PRIORITY = 2500
|
|
BotDetectionHandler.VERSION = kong_meta.version
|
|
|
|
local BAD_REQUEST = 400
|
|
local FORBIDDEN = 403
|
|
|
|
local MATCH_EMPTY = 0
|
|
local MATCH_ALLOW = 1
|
|
local MATCH_DENY = 2
|
|
local MATCH_BOT = 3
|
|
|
|
|
|
-- per-worker cache of matched UAs
|
|
-- we use a weak table, index by the `conf` parameter, so once the plugin config
|
|
-- is GC'ed, the cache follows automatically
|
|
local ua_caches = setmetatable({}, { __mode = "k" })
|
|
local UA_CACHE_SIZE = 10 ^ 4
|
|
|
|
local function get_user_agent()
|
|
local user_agent = kong.request.get_headers()["user-agent"]
|
|
if type(user_agent) == "table" then
|
|
return nil, "Only one User-Agent header allowed"
|
|
end
|
|
return user_agent
|
|
end
|
|
|
|
local function examine_agent(user_agent, conf)
|
|
user_agent = strip(user_agent)
|
|
|
|
if conf.allow then
|
|
for _, rule in ipairs(conf.allow) do
|
|
if re_find(user_agent, rule, "jo") then
|
|
return MATCH_ALLOW
|
|
end
|
|
end
|
|
end
|
|
|
|
if conf.deny then
|
|
for _, rule in ipairs(conf.deny) do
|
|
if re_find(user_agent, rule, "jo") then
|
|
return MATCH_DENY
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, rule in ipairs(rules.bots) do
|
|
if re_find(user_agent, rule, "jo") then
|
|
return MATCH_BOT
|
|
end
|
|
end
|
|
|
|
return MATCH_EMPTY
|
|
end
|
|
|
|
function BotDetectionHandler:access(conf)
|
|
local user_agent, err = get_user_agent()
|
|
if err then
|
|
return kong.response.error(BAD_REQUEST, err)
|
|
end
|
|
|
|
if not user_agent then
|
|
return
|
|
end
|
|
|
|
local cache = ua_caches[conf]
|
|
if not cache then
|
|
cache = lrucache.new(UA_CACHE_SIZE)
|
|
ua_caches[conf] = cache
|
|
end
|
|
|
|
local match = cache:get(user_agent)
|
|
if not match then
|
|
match = examine_agent(user_agent, conf)
|
|
cache:set(user_agent, match)
|
|
end
|
|
|
|
-- if we saw a denied UA or bot, return forbidden. otherwise,
|
|
-- fall out of our handler
|
|
if match > 1 then
|
|
return kong.response.error(FORBIDDEN)
|
|
end
|
|
end
|
|
|
|
return BotDetectionHandler
|