Project

General

Profile

AbsoLUAtion » reject-bad-actors.lua

block client requests matching entries in mcdb reject list - gstrauss, 2021-03-18 10:52

 
-- reject-bad-actors.lua
--
-- Summary: block client requests matching any criteria in reject list
--
--
-- Copyright (c) 2021, Glenn Strauss (gstrauss () gluelogic.com)
-- All rights reserved.
--
-- License: 3-clause BSD
--
--
-- * check if reject.mcdb database contains remote IP
-- * check if reject.mcdb database contains Referer
-- * check if reject.mcdb database contains User-Agent
--
-- single mcdb combines reject list of remote IP, Referer, and User-Agent
-- (e.g. blocks if IP in reject list is in Referer)
-- remote IP, Referer, and User-Agent not expected to look like one another
-- chance of false positive is low
--
-- prep:
-- * replace all "/path/to" in instructions and script with actual path to files
-- * build mcdb
-- https://github.com/gstrauss/mcdb/blob/master/README
-- https://github.com/gstrauss/mcdb/blob/master/INSTALL
-- * install mcdb or use qualified path to mcdbctl when creating database
-- * build lua-mcdb and copy mcdb.so to /path/to/
-- https://github.com/gstrauss/mcdb/blob/master/contrib/lua-mcdb/README
-- * create reject database
-- perl -e 'while (<>) { chomp; next unless length($_); printf "+%d,0:%s->\n", length($_), $_; } print "\n";' /path/to/rejects/list-one-per-line | mcdbctl make /path/to/reject.mcdb -
-- * note: database performs exact matches. If case-insensitive is required,
-- then lowercase input when database is created (lc($_)) and modify code
-- below to lowercase request item before querying database
--
-- lighttpd.conf
-- server.modules += ("mod_magnet")
-- magnet.attract-raw-url-to = ( "/path/to/reject-bad-actors.lua" )

-- get database file handle (cached in global)
-- note: when database changes, 'touch' file containing this script in order to
-- trigger lighttpd mod_magnet_cache to reload this script and re-open the mcdb
-- (lighttpd etags must be enabled (default) for modified script to be noticed)
-- (alternative: skip caching open mcdb in _G to have mcdb opened each request)
local reject = _G.rejectdb
if (not reject) then
-- open database file
local db = "/path/to/reject.mcdb"
local mcdb = package.loaded["mcdb"]
if (not mcdb) then
-- update search path with path to mcdb.so
-- (todo: should skip if already present)
--package.cpath = '/path/to/?.so'
package.cpath = package.cpath .. ';/path/to/?.so'
mcdb = require("mcdb")
end
local rejectdb, errstr = mcdb.init(db)
if rejectdb == nil then
io.stderr:write('mcdb.init("' .. db .. '"): ' .. errstr .. '\n')
return 0 -- fail open (not closed); allow request to proceed
end
_G.rejectdb = rejectdb
reject = rejectdb
end

-- check remote IP
local ip = lighty.env["request.remote-ip"]
if (reject(ip) ~= nil) then -- exact match on IP string (normalized)
return 403 -- HTTP 403 Forbidden
end

-- check User-Agent
local ua = lighty.request["user-agent"]
if (ua ~= nil and reject(ua) ~= nil) then
return 410 -- HTTP 410 Gone
end

-- check Referer
local ref = lighty.request["referer"]
if (ref ~= nil) then
-- normalize Referer to some extent (optional)
--ref = ref:lower() -- lowercase
--ref = ref:gsub("%.+$", "") -- remove trailing dots
-- suffix match from longest to shortest name, splitting on dot
local dot = 1
while (dot) do
if (reject(ref) ~= nil) then
return 403 -- HTTP 403 Forbidden
end
dot = ref:find(".", 1, true)
if dot then ref = ref:sub(dot+1) end
end
end

-- allow request to proceed
return 0
(4-4/6)