wafris 1.1.11 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/wafris/configuration.rb +121 -37
- data/lib/wafris/log_suppressor.rb +3 -2
- data/lib/wafris/middleware.rb +45 -23
- data/lib/wafris/version.rb +1 -1
- data/lib/wafris.rb +582 -38
- metadata +44 -19
- data/lib/lua/dist/wafris_core.lua +0 -305
metadata
CHANGED
@@ -1,58 +1,86 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wafris
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Michael Buckbee
|
8
8
|
- Ryan Castillo
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-
|
12
|
+
date: 2024-07-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: rack
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '2.
|
20
|
+
version: '2.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '2.
|
27
|
+
version: '2.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
29
|
+
name: sqlite3
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
34
|
+
version: '0'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
41
|
+
version: '0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: ipaddr
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - ">="
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version:
|
48
|
+
version: '0'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: httparty
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: awesome_print
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
56
84
|
- !ruby/object:Gem::Dependency
|
57
85
|
name: minitest
|
58
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,14 +135,14 @@ dependencies:
|
|
107
135
|
requirements:
|
108
136
|
- - ">="
|
109
137
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
138
|
+
version: '6.0'
|
111
139
|
type: :development
|
112
140
|
prerelease: false
|
113
141
|
version_requirements: !ruby/object:Gem::Requirement
|
114
142
|
requirements:
|
115
143
|
- - ">="
|
116
144
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
145
|
+
version: '6.0'
|
118
146
|
- !ruby/object:Gem::Dependency
|
119
147
|
name: railties
|
120
148
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,7 +177,6 @@ executables: []
|
|
149
177
|
extensions: []
|
150
178
|
extra_rdoc_files: []
|
151
179
|
files:
|
152
|
-
- lib/lua/dist/wafris_core.lua
|
153
180
|
- lib/wafris.rb
|
154
181
|
- lib/wafris/configuration.rb
|
155
182
|
- lib/wafris/log_suppressor.rb
|
@@ -161,11 +188,9 @@ licenses:
|
|
161
188
|
- Elastic-2.0
|
162
189
|
metadata: {}
|
163
190
|
post_install_message: |2+
|
164
|
-
Thank you for installing the
|
165
|
-
|
166
|
-
If you haven't already, please sign up for Wafris Hub at:
|
191
|
+
Thank you for installing the Wafris gem.
|
167
192
|
|
168
|
-
https://
|
193
|
+
Get your API key and set firewall rules at https://hub.wafris.org
|
169
194
|
|
170
195
|
rdoc_options: []
|
171
196
|
require_paths:
|
@@ -1,305 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
local USE_TIMESTAMPS_AS_REQUEST_IDS = false
|
4
|
-
local EXPIRATION_IN_SECONDS = tonumber(redis.call("HGET", "waf-settings", "expiration-time")) or 86400
|
5
|
-
local EXPIRATION_OFFSET_IN_SECONDS = 3600
|
6
|
-
|
7
|
-
|
8
|
-
local function get_timebucket(timestamp_in_seconds)
|
9
|
-
local startOfHourTimestamp = math.floor(timestamp_in_seconds / 3600) * 3600
|
10
|
-
return tostring(startOfHourTimestamp)
|
11
|
-
end
|
12
|
-
|
13
|
-
local function set_property_value_id_lookups(property_abbreviation, property_value)
|
14
|
-
|
15
|
-
local value_key = property_abbreviation .. "V" .. property_value
|
16
|
-
local property_id = redis.call("GET", value_key)
|
17
|
-
|
18
|
-
if property_id == false then
|
19
|
-
property_id = redis.call("INCR", property_abbreviation .. "-id-counter")
|
20
|
-
redis.call("SET", value_key, property_id)
|
21
|
-
redis.call("SET", property_abbreviation .. "I" .. property_id, property_value)
|
22
|
-
end
|
23
|
-
|
24
|
-
redis.call("EXPIRE", value_key, EXPIRATION_IN_SECONDS + EXPIRATION_OFFSET_IN_SECONDS)
|
25
|
-
redis.call("EXPIRE", property_abbreviation .. "I" .. property_id, EXPIRATION_IN_SECONDS + EXPIRATION_OFFSET_IN_SECONDS)
|
26
|
-
|
27
|
-
return property_id
|
28
|
-
end
|
29
|
-
|
30
|
-
local function increment_leaderboard_for(property_abbreviation, property_id, timebucket)
|
31
|
-
|
32
|
-
local key = property_abbreviation .. "L" .. timebucket
|
33
|
-
redis.call("ZINCRBY", key, 1, property_id)
|
34
|
-
redis.call("EXPIRE", key, EXPIRATION_IN_SECONDS)
|
35
|
-
end
|
36
|
-
|
37
|
-
local function set_property_to_requests_list(property_abbreviation, property_id, request_id, timebucket)
|
38
|
-
|
39
|
-
local key = property_abbreviation .. "R" .. property_id .. "-" .. timebucket
|
40
|
-
redis.call("LPUSH", key, request_id)
|
41
|
-
|
42
|
-
redis.call("EXPIRE", key, EXPIRATION_IN_SECONDS + EXPIRATION_OFFSET_IN_SECONDS)
|
43
|
-
end
|
44
|
-
|
45
|
-
|
46
|
-
local function ip_in_hash(hash_name, ip_address)
|
47
|
-
local found_ip = redis.call('HEXISTS', hash_name, ip_address)
|
48
|
-
|
49
|
-
if found_ip == 1 then
|
50
|
-
return ip_address
|
51
|
-
else
|
52
|
-
return false
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
local function ip_in_cidr_range(cidr_set, ip_decimal_lexical)
|
57
|
-
|
58
|
-
local higher_value = redis.call('ZRANGEBYLEX', cidr_set, '['..ip_decimal_lexical, '+', 'LIMIT', 0, 1)[1]
|
59
|
-
|
60
|
-
local lower_value = redis.call('ZREVRANGEBYLEX', cidr_set, '['..ip_decimal_lexical, '-', 'LIMIT', 0, 1)[1]
|
61
|
-
|
62
|
-
if not (higher_value and lower_value) then
|
63
|
-
return false
|
64
|
-
end
|
65
|
-
|
66
|
-
local higher_compare = higher_value:match('([^%-]+)$')
|
67
|
-
local lower_compare = lower_value:match('([^%-]+)$')
|
68
|
-
|
69
|
-
if higher_compare == lower_compare then
|
70
|
-
return lower_compare
|
71
|
-
else
|
72
|
-
return false
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
local function escapePattern(s)
|
77
|
-
local patternSpecials = "[%^%$%(%)%%%.%[%]%*%+%-%?]"
|
78
|
-
return s:gsub(patternSpecials, "%%%1")
|
79
|
-
end
|
80
|
-
|
81
|
-
local function match_by_pattern(property_abbreviation, property_value)
|
82
|
-
local hash_name = "rules-blocked-" .. property_abbreviation
|
83
|
-
|
84
|
-
local patterns = redis.call('HKEYS', hash_name)
|
85
|
-
|
86
|
-
for _, pattern in ipairs(patterns) do
|
87
|
-
if string.find(string.lower(property_value), string.lower(escapePattern(pattern))) then
|
88
|
-
return pattern
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
return false
|
93
|
-
end
|
94
|
-
|
95
|
-
local function blocked_by_rate_limit(request_properties)
|
96
|
-
|
97
|
-
local rate_limiting_rules_values = redis.call('HKEYS', 'rules-blocked-rate-limits')
|
98
|
-
|
99
|
-
for i, rule_name in ipairs(rate_limiting_rules_values) do
|
100
|
-
|
101
|
-
local conditions_hash = redis.call('HGETALL', rule_name .. "-conditions")
|
102
|
-
|
103
|
-
local all_conditions_match = true
|
104
|
-
|
105
|
-
for j = 1, #conditions_hash, 2 do
|
106
|
-
local condition_key = conditions_hash[j]
|
107
|
-
local condition_value = conditions_hash[j + 1]
|
108
|
-
|
109
|
-
if request_properties[condition_key] ~= condition_value then
|
110
|
-
all_conditions_match = false
|
111
|
-
break
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
if all_conditions_match then
|
116
|
-
|
117
|
-
local rule_settings_key = rule_name .. "-settings"
|
118
|
-
|
119
|
-
local limit, time_period, limited_by, rule_id = unpack(redis.call('HMGET', rule_settings_key, 'limit', 'time-period', 'limited-by', 'rule-id'))
|
120
|
-
|
121
|
-
local throttle_key = rule_name .. ":" .. limit .. "V" .. request_properties.ip
|
122
|
-
|
123
|
-
local new_value = redis.call('INCR', throttle_key)
|
124
|
-
|
125
|
-
if new_value == 1 then
|
126
|
-
redis.call('EXPIRE', throttle_key, tonumber(time_period))
|
127
|
-
end
|
128
|
-
|
129
|
-
if tonumber(new_value) >= tonumber(limit) then
|
130
|
-
return rule_id
|
131
|
-
else
|
132
|
-
return false
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
local function check_rules(functions_to_check)
|
139
|
-
for _, check in ipairs(functions_to_check) do
|
140
|
-
|
141
|
-
local rule = check.func(unpack(check.args))
|
142
|
-
local category = check.category
|
143
|
-
|
144
|
-
if type(rule) == "string" then
|
145
|
-
return rule, category
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
return false, false
|
150
|
-
end
|
151
|
-
|
152
|
-
local function check_blocks(request)
|
153
|
-
local rule_categories = {
|
154
|
-
{ category = "bi", func = ip_in_hash, args = { "rules-blocked-i", request.ip } },
|
155
|
-
{ category = "bc", func = ip_in_cidr_range, args = { "rules-blocked-cidrs-set", request.ip_decimal_lexical } },
|
156
|
-
{ category = "bs", func = ip_in_cidr_range, args = { "rules-blocked-cidrs-subscriptions-set", request.ip_decimal_lexical } },
|
157
|
-
{ category = "bu", func = match_by_pattern, args = { "u", request.user_agent } },
|
158
|
-
{ category = "bp", func = match_by_pattern, args = { "p", request.path } },
|
159
|
-
{ category = "ba", func = match_by_pattern, args = { "a", request.parameters } },
|
160
|
-
{ category = "bh", func = match_by_pattern, args = { "h", request.host } },
|
161
|
-
{ category = "bm", func = match_by_pattern, args = { "m", request.method } },
|
162
|
-
{ category = "bd", func = match_by_pattern, args = { "rh", request.headers } },
|
163
|
-
{ category = "bpb", func = match_by_pattern, args = { "pb", request.post_body } },
|
164
|
-
{ category = "brl", func = blocked_by_rate_limit, args = { request } }
|
165
|
-
}
|
166
|
-
|
167
|
-
return check_rules(rule_categories)
|
168
|
-
end
|
169
|
-
|
170
|
-
local function check_allowed(request)
|
171
|
-
local rule_categories = {
|
172
|
-
{ category = "ai", func = ip_in_hash, args = { "rules-allowed-i", request.ip } },
|
173
|
-
{ category = "ac", func = ip_in_cidr_range, args = { "rules-allowed-cidrs-set", request.ip_decimal_lexical } }
|
174
|
-
}
|
175
|
-
|
176
|
-
return check_rules(rule_categories)
|
177
|
-
end
|
178
|
-
|
179
|
-
local request = {
|
180
|
-
["ip"] = ARGV[1],
|
181
|
-
["ip_decimal_lexical"] = string.rep("0", 39 - #ARGV[2]) .. ARGV[2],
|
182
|
-
["ts_in_milliseconds"] = ARGV[3],
|
183
|
-
["ts_in_seconds"] = ARGV[3] / 1000,
|
184
|
-
["user_agent"] = ARGV[4],
|
185
|
-
["path"] = ARGV[5],
|
186
|
-
["parameters"] = ARGV[6],
|
187
|
-
["host"] = ARGV[7],
|
188
|
-
["method"] = ARGV[8],
|
189
|
-
["headers"] = ARGV[9],
|
190
|
-
["post_body"] = ARGV[10],
|
191
|
-
["ip_id"] = set_property_value_id_lookups("i", ARGV[1]),
|
192
|
-
["user_agent_id"] = set_property_value_id_lookups("u", ARGV[4]),
|
193
|
-
["path_id"] = set_property_value_id_lookups("p", ARGV[5]),
|
194
|
-
["parameters_id"] = set_property_value_id_lookups("a", ARGV[6]),
|
195
|
-
["host_id"] = set_property_value_id_lookups("h", ARGV[7]),
|
196
|
-
["method_id"] = set_property_value_id_lookups("m", ARGV[8])
|
197
|
-
}
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
local current_timebucket = get_timebucket(request.ts_in_seconds)
|
202
|
-
|
203
|
-
local blocked_rule = false
|
204
|
-
local blocked_category = nil
|
205
|
-
local treatment = "p"
|
206
|
-
|
207
|
-
local stream_id
|
208
|
-
|
209
|
-
if USE_TIMESTAMPS_AS_REQUEST_IDS == true then
|
210
|
-
stream_id = request.ts_in_milliseconds
|
211
|
-
else
|
212
|
-
stream_id = "*"
|
213
|
-
end
|
214
|
-
|
215
|
-
local stream_args = {
|
216
|
-
"XADD",
|
217
|
-
"rStream",
|
218
|
-
"MINID",
|
219
|
-
tostring((current_timebucket - EXPIRATION_IN_SECONDS) * 1000 ),
|
220
|
-
stream_id,
|
221
|
-
"i", request.ip_id,
|
222
|
-
"u", request.user_agent_id,
|
223
|
-
"p", request.path_id,
|
224
|
-
"h", request.host_id,
|
225
|
-
"m", request.method_id,
|
226
|
-
"a", request.parameters_id,
|
227
|
-
}
|
228
|
-
|
229
|
-
local allowed_rule, allowed_category = check_allowed(request)
|
230
|
-
|
231
|
-
if allowed_rule then
|
232
|
-
table.insert(stream_args, "t")
|
233
|
-
table.insert(stream_args, "a")
|
234
|
-
|
235
|
-
treatment = "a"
|
236
|
-
|
237
|
-
table.insert(stream_args, "ac")
|
238
|
-
table.insert(stream_args, allowed_category)
|
239
|
-
|
240
|
-
table.insert(stream_args, "ar")
|
241
|
-
table.insert(stream_args, allowed_rule)
|
242
|
-
|
243
|
-
else
|
244
|
-
blocked_rule, blocked_category = check_blocks(request)
|
245
|
-
end
|
246
|
-
|
247
|
-
if blocked_rule then
|
248
|
-
table.insert(stream_args, "t")
|
249
|
-
table.insert(stream_args, "b")
|
250
|
-
|
251
|
-
treatment = "b"
|
252
|
-
|
253
|
-
table.insert(stream_args, "bc")
|
254
|
-
table.insert(stream_args, blocked_category)
|
255
|
-
|
256
|
-
table.insert(stream_args, "br")
|
257
|
-
table.insert(stream_args, blocked_rule)
|
258
|
-
end
|
259
|
-
|
260
|
-
if blocked_rule == false and allowed_rule == false then
|
261
|
-
table.insert(stream_args, "t")
|
262
|
-
table.insert(stream_args, "p")
|
263
|
-
end
|
264
|
-
|
265
|
-
local request_id = redis.call(unpack(stream_args))
|
266
|
-
|
267
|
-
increment_leaderboard_for("i", request.ip_id, current_timebucket)
|
268
|
-
increment_leaderboard_for("u", request.user_agent_id, current_timebucket)
|
269
|
-
increment_leaderboard_for("p", request.path_id, current_timebucket)
|
270
|
-
increment_leaderboard_for("a", request.parameters_id, current_timebucket)
|
271
|
-
increment_leaderboard_for("h", request.host_id, current_timebucket)
|
272
|
-
increment_leaderboard_for("m", request.method_id, current_timebucket)
|
273
|
-
increment_leaderboard_for("t", treatment, current_timebucket)
|
274
|
-
|
275
|
-
set_property_to_requests_list("i", request.ip_id, request_id, current_timebucket)
|
276
|
-
set_property_to_requests_list("u", request.user_agent_id, request_id, current_timebucket)
|
277
|
-
set_property_to_requests_list("p", request.path_id, request_id, current_timebucket)
|
278
|
-
set_property_to_requests_list("a", request.parameters_id, request_id, current_timebucket)
|
279
|
-
set_property_to_requests_list("h", request.host_id, request_id, current_timebucket)
|
280
|
-
set_property_to_requests_list("m", request.method_id, request_id, current_timebucket)
|
281
|
-
set_property_to_requests_list("t", treatment, request_id, current_timebucket)
|
282
|
-
|
283
|
-
if blocked_rule ~= false then
|
284
|
-
increment_leaderboard_for("bc", blocked_category, current_timebucket)
|
285
|
-
set_property_to_requests_list("bc", blocked_category, request_id, current_timebucket)
|
286
|
-
|
287
|
-
increment_leaderboard_for("br", blocked_rule, current_timebucket)
|
288
|
-
set_property_to_requests_list("br", blocked_rule, request_id, current_timebucket)
|
289
|
-
end
|
290
|
-
|
291
|
-
if allowed_rule ~= false then
|
292
|
-
increment_leaderboard_for("ac", allowed_category, current_timebucket)
|
293
|
-
set_property_to_requests_list("ac", allowed_category, request_id, current_timebucket)
|
294
|
-
|
295
|
-
increment_leaderboard_for("ar", allowed_rule, current_timebucket)
|
296
|
-
set_property_to_requests_list("ar", allowed_rule, request_id, current_timebucket)
|
297
|
-
end
|
298
|
-
|
299
|
-
if blocked_rule ~= false then
|
300
|
-
return "Blocked"
|
301
|
-
elseif allowed_rule ~= false then
|
302
|
-
return "Allowed"
|
303
|
-
else
|
304
|
-
return "Passed"
|
305
|
-
end
|