-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathwforce.conf.example
255 lines (217 loc) · 9.47 KB
/
wforce.conf.example
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)