1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
|
addListener("0.0.0.0:8084", true, "/etc/tls/certificate.pem", "/etc/tls/private_key.pem", {minimum_protocol="TLSv1.3"})
setWebserverPassword("aVerySecurePassword")
setKey("Ay9KXgU3g4ygK+qWT0Ut4gH8PPz02gbtPeXWPdjD0HE=")
controlSocket("0.0.0.0:4004")
addSibling("192.168.1.79")
addSibling("192.168.1.30")
addSibling("192.168.1.54")
siblingListener("0.0.0.0:4001")
addACL("127.0.0.0/8")
addACL("192.168.0.0/16")
-- Number of lua states used for allow/report queries.
-- Increasing this number will reduce lock contention if that is a problem
-- A lot of states will increase memory, but not by too much
setNumLuaStates(6)
-- Number of worker threads used for allow/report queries
-- Increasing this number increases concurrency but too many could cause thrashing
-- Should be >= number of lua states, and ideally == number of cores
setNumWorkerThreads(4)
-- Number of worker threads used to process siblings reports
-- Increasing this number increases concurrency but too many could cause thrashing
-- Should be around numWorkerThreads/2
setNumSiblingThreads(2)
-- Number of threads to use for sending webhook events
setNumWebHookThreads(2)
-- Register a webhook for "report" events
local config_keys={}
config_keys["url"] = "http://localhost:8080/webhook/regression"
config_keys["secret"] = "verysecretcode"
local events = { "report" }
addWebHook(events, config_keys)
-- Register a custom webhook for custom events
config_keys={}
config_keys["url"] = "http://localhost:8080/webhook/regression"
config_keys["secret"] = "verysecretcustomcode"
-- Change Content-Type of the HTTP post from the default application/json
-- config_keys["content-type"] = "text/plain"
addCustomWebHook("mycustomhook", config_keys)
local bulkRetrievers = newNetmaskGroup()
bulkRetrievers:addMask("130.161.0.0/16")
bulkRetrievers:addMask("145.132.0.0/16")
-- Field map contains as many different fields as you like
-- Supported types are:
-- "int" - simple counter
-- "hll" - cardinality (count how many different things you put in the bucket)
-- "countmin" - like a multi-valued bloom filter. Counts how many of each type there are.
local field_map = {}
field_map["countLogins"] = "int"
field_map["diffPasswords"] = "hll"
field_map["diffIPs"] = "hll"
field_map["countryCount"] = "countmin"
field_map["osCount"] = "countmin"
-- create a db for storing stats.
-- You can create multiple dbs with different window lengths for storing stats over different time windows
-- This one is 6 windows of 10 minutes each, so an hour in total. Every 10 minutes the oldest windows data will expire
newStringStatsDB("OneHourDB",600,6,field_map)
-- Persist the blacklist entries in the specified redis DB
-- blacklistPersistDB("127.0.0.1", 6379)
-- Only set this option if every wforce server is using a separate redis DB
-- Do not set when there is a central redis DB used by all wforce servers
-- blacklistPersistReplicated()
-- This one is 24 windows of 1 hour each, so 24 hours in total. Useful for tracking long-term stats.
-- You can reuse field_map or create a different one
newStringStatsDB("24HourDB",3600, 24, field_map)
-- Only initialize the GeoIPDB if you need it - this initializes the "country"
-- GeoIP DBs (ipv4 and ipv6)
initGeoIPDB()
-- You can also initialize the "city" and "ISP" databases (ensure you have
-- downloaded them, again you need both ipv4 and ipv6 DBs)
-- initGeoIPCityDB()
-- initGeoIPISPDB()
-- The report function is used to store custom stats
-- The allow and report functions cannot access any lua variables defined outside, hence the get... functions
function report(lt)
local sdb = getStringStatsDB("OneHourDB")
local sdb_day = getStringStatsDB("24HourDB")
local cur_ct = lookupCountry(lt.remote)
-- A note on keys: You can use strings or ComboAddresses.
-- You can specify a separate ipv4 and v6 netmasks for the DB, which will be used for all ComboAddress keys, thus allowing aggregation of IP stats
-- Example for IPv4 addresses: sdb:twSetv4Prefix(24)
-- Example for IPv6 addresses: sdb:twSetv6Prefix(64)
-- twAdd() is the generic way to add things to the stats bucket. For integers it does addition
sdb:twAdd(lt.login, "countLogins", 1)
sdb:twAdd(lt.remote, "countLogins", 1)
sdb:twAdd(lt.login, "diffPasswords", lt.pwhash)
sdb:twAdd(lt.login, "diffIPs", lt.remote)
sdb_day:twAdd(lt.login, "countryCount", cur_ct)
-- look for things in device_attrs
if (not ((lt.device_attrs["os.family"] == "") or
(lt.device_attrs["os.family"] == nil)))
then
-- store the os family
sdb:twAdd(lt.login, "osCount", lt.device_attrs["os.family"])
end
-- twSub() can be used for the integer type. It does what you expect
end
-- initialise some DNS lookup objects
newDNSResolver("Resolv")
local resolv = getDNSResolver("Resolv")
newDNSResolver("RBLResolv")
local rblresolv = getDNSResolver("RBLResolv")
-- Configure some resolvers for it to use
resolv:addResolver("8.8.8.8", 53)
rblresolv:addResolver("127.0.0.1", 5353)
-- The allow and report functions cannot access any lua variables defined outside, hence the get... functions
function allow(lt)
local sdb = getStringStatsDB("OneHourDB")
local sdb_day = getStringStatsDB("24HourDB")
local resolv = getDNSResolver("Resolv")
local rblresolv = getDNSResolver("RBLResolv")
-- look for things in device_attrs
if (lt.device_attrs["os.family"] == "iOS")
then
-- do something special for iOS
end
if (bulkRetrievers:match(lt.remote))
then
-- Must return 4 args - <return value>, <return msg>, <log msg>, <log key values>
return 0, "bulkRetrievers", "bulkRetrievers", { bulkRetrievers=1 }
end
-- check optional attrs for things that indicate badness
for k, v in pairs(lt.attrs) do
if ((k == "accountStatus") and (v == "blocked"))
then
return -1, "account blocked", "account blocked", { accountStatus="blocked" }
end
end
-- attrs can be multi-valued, in which case they will appear in attrs_mv automagically
for k, v in pairs(lt.attrs_mv) do
for i, vi in ipairs(v) do
if ((k == "countryList") and (vi == "Blockmestan"))
then
return -1, "blocked country list", "blocked country list", { countryList="Blockmestan" }
end
end
end
-- Example GeoIP lookup
if (lookupCountry(lt.remote) == "XX")
then
return -1, "country blocked", "country blocked", { remoteCountry="XX" }
end
-- You can also lookup ISP names:
-- lookupISP(lt.remote)
-- And more detailed information like city/lat/long:
-- gip_record = lookupCity(lt.remote)
-- local my_city = gip_record.city
-- Example DNS lookups
local dnames = resolv:lookupNameByAddr(lt.remote)
for i, dname in ipairs(dnames) do
if (string.match(dname, "XXX"))
then
return -1, "Reverse IP", "Reverse IP", { ptrContains="XXX" }
end
end
dnames = rblresolv:lookupRBL(lt.remote, "sbl.spamhaus.org")
for i, dname in ipairs(dnames) do
if (string.match(dname, "127.0.0.2"))
then
-- tarpit the connection
return 5, "RBL", "RBL", { spamhaus=1 }
end
end
-- twGet() returns the "sum" of all the values over all the time windows.
-- For integer and countmin types, this is just a sum. For HLL, it's a union.
if (sdb:twGet(lt.login, "diffPasswords") > 90)
then
-- blacklist the login for 300 seconds
blacklistLogin(lt.login, 300, "too many different incorrect password attempts")
return -1, "too many different password attempts for this account", "diffPasswords", { diffPasswords=90 }
end
-- If user is logging in from multiple IPs and we haven't seen this country in the last 24 hours then reject
local cur_ct = lookupCountry(lt.remote)
if ((sdb:twGet(lt.login, "diffIPs") > 5) and (sdb_day.twGet(lt.login, "countryCount", cur_ct) < 2))
then
return -1, "Too many IPs and countries", "diffIPs", { diffIPs=5, countryCount24Hrs=1, country=cur_ct }
end
-- We also have:
-- twGetCurrent() which returns the value for the current window
-- twGetWindows() which returns a table with all window values - the first item in the list is the "current" window. The last is the "oldest" window.
-- return must have these 4 arguments
return 0, "allowed", "allowed", {}
end
-- Use this function to reset stats if needed for particular IPs, logins or both
function reset(type, login, ip)
local sdb = getStringStatsDB("OneHourDB")
local sdb_day = getStringStatsDB("24HourDB")
if (string.find(type, "ip"))
then
sdb:twReset(ip)
-- if you set a non-default prefix for IP addresses, then reset will not necessarily do what you expect
-- for example if v4Prefix==24 and you reset an IP address it will reset the stats for all IPs in that range
end
if (string.find(type, "login"))
then
-- we do not actually set any login-only keys
sdb:twReset(login)
sdb_day:twReset(login)
end
if (string.find(type, "ip") and string.find(type, "login"))
then
-- we do not set any compound keys in this policy
end
return true
end
setReport(report)
setAllow(allow)
setReset(reset)
function custom(args)
for k,v in pairs(args.attrs) do
infoLog("custom func argument attrs", { key=k, value=v });
end
runCustomWebHook("mycustomhook", "{ \"foo\":\"bar\" }")
-- return consists of a boolean, followed by { key-value pairs }
return true, { key=value }
end
-- Register a custom endpoint
-- Parameters: name, send arguments to report sink?, function)
setCustomEndpoint("custom", false, custom)
|