wafris 0.8.5 → 0.9.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/lua/dist/wafris_core.lua +251 -113
- data/lib/wafris/configuration.rb +7 -8
- data/lib/wafris/middleware.rb +1 -1
- data/lib/wafris/version.rb +1 -1
- data/lib/wafris.rb +6 -12
- metadata +5 -12
- data/lib/lua/src/get_time_buckets.lua +0 -58
- data/lib/lua/src/queries.lua +0 -14
- data/lib/lua/src/seeds/data_load.lua +0 -104
- data/lib/lua/src/time_bucket.lua +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf428bf85deefd6d35c17de90e06f1090710577d0216f2db9ebe5460e1a8260f
|
4
|
+
data.tar.gz: ea64374d9b9fe3e629ae84966ab5a35024f139a44c40265e52a46f55c2c41e1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aae408ce2065bc030209eb9d5b68ac58917fed970b7af940022e1781659394d9ef32b254864e0ca97c5395f0c6e6d52b40cfc42ff26f5ac3b1302a6d9c76046e
|
7
|
+
data.tar.gz: 886557ee407acf070bd94aeec88e21fba8cbc40ab15f8af337be3507dc41d6e6aa7df4ed180c3d7d262c453f8567cd3c956502a3a793541b8c0b57d3c2f11cb5
|
@@ -1,136 +1,274 @@
|
|
1
|
-
local
|
2
|
-
local
|
1
|
+
local use_timestamps_as_request_ids = true
|
2
|
+
local EXPIRATION_IN_SECONDS = 86400
|
3
3
|
|
4
|
-
local function
|
5
|
-
local
|
6
|
-
|
4
|
+
local function get_timebucket(timestamp_in_seconds)
|
5
|
+
local startOfHourTimestamp = math.floor(timestamp_in_seconds / 3600) * 3600
|
6
|
+
return tostring(startOfHourTimestamp)
|
7
|
+
end
|
8
|
+
|
9
|
+
local function set_property_value_id_lookups(property_abbreviation, property_value)
|
10
|
+
local value_key = property_abbreviation .. "V" .. property_value
|
11
|
+
local property_id = redis.call("GET", value_key)
|
12
|
+
|
13
|
+
if property_id == false then
|
14
|
+
property_id = redis.call("INCR", property_abbreviation .. "-id-counter")
|
15
|
+
redis.call("SET", value_key, property_id)
|
16
|
+
redis.call("SET", property_abbreviation .. "I" .. property_id, property_value)
|
17
|
+
else
|
18
|
+
redis.call("EXPIRE", value_key, EXPIRATION_IN_SECONDS)
|
19
|
+
redis.call("EXPIRE", property_abbreviation .. "I" .. property_id, EXPIRATION_IN_SECONDS)
|
7
20
|
end
|
8
21
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
22
|
+
return property_id
|
23
|
+
end
|
24
|
+
|
25
|
+
local function increment_leaderboard_for(property_abbreviation, property_id, timebucket)
|
26
|
+
local key = property_abbreviation .. "L" .. timebucket
|
27
|
+
redis.call("ZINCRBY", key, 1, property_id)
|
28
|
+
redis.call("EXPIRE", key, EXPIRATION_IN_SECONDS)
|
29
|
+
end
|
30
|
+
|
31
|
+
local function set_property_to_requests_list(property_abbreviation, property_id, request_id, timebucket)
|
32
|
+
local key = property_abbreviation .. "R" .. property_id .. "-" .. timebucket
|
33
|
+
redis.call("LPUSH", key, request_id)
|
34
|
+
redis.call("EXPIRE", key, EXPIRATION_IN_SECONDS)
|
35
|
+
end
|
36
|
+
|
37
|
+
local function ip_in_hash(hash_name, ip_address)
|
38
|
+
local found_ip = redis.call('HEXISTS', hash_name, ip_address)
|
39
|
+
|
40
|
+
if found_ip == 1 then
|
41
|
+
return ip_address
|
42
|
+
else
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
local function ip_in_cidr_range(cidr_set, ip_decimal_lexical)
|
48
|
+
local higher_value = redis.call('ZRANGEBYLEX', cidr_set, '['..ip_decimal_lexical, '+', 'LIMIT', 0, 1)[1]
|
49
|
+
local lower_value = redis.call('ZREVRANGEBYLEX', cidr_set, '['..ip_decimal_lexical, '-', 'LIMIT', 0, 1)[1]
|
50
|
+
|
51
|
+
if not (higher_value and lower_value) then
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
|
55
|
+
local higher_compare = higher_value:match('([^%-]+)$')
|
56
|
+
local lower_compare = lower_value:match('([^%-]+)$')
|
57
|
+
|
58
|
+
if higher_compare == lower_compare then
|
59
|
+
return lower_compare
|
60
|
+
else
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
local function match_by_pattern(property_abbreviation, property_value)
|
66
|
+
local hash_name = "rules-blocked-" .. property_abbreviation
|
67
|
+
local patterns = redis.call('HKEYS', hash_name)
|
68
|
+
|
69
|
+
for _, pattern in ipairs(patterns) do
|
70
|
+
if string.find(property_value, pattern) then
|
71
|
+
return pattern
|
13
72
|
end
|
14
|
-
return year, days
|
15
73
|
end
|
16
74
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
if days - days_in_each_month[month] <= 0 then
|
35
|
-
return month, days
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
local function blocked_by_rate_limit(request_properties)
|
79
|
+
local rate_limiting_rules_values = redis.call('HKEYS', 'rules-blocked-rate-limits')
|
80
|
+
|
81
|
+
for i, rule_name in ipairs(rate_limiting_rules_values) do
|
82
|
+
local conditions_hash = redis.call('HGETALL', rule_name .. "-conditions")
|
83
|
+
local all_conditions_match = true
|
84
|
+
|
85
|
+
for j = 1, #conditions_hash, 2 do
|
86
|
+
local condition_key = conditions_hash[j]
|
87
|
+
local condition_value = conditions_hash[j + 1]
|
88
|
+
|
89
|
+
if request_properties[condition_key] ~= condition_value then
|
90
|
+
all_conditions_match = false
|
91
|
+
break
|
36
92
|
end
|
37
|
-
days = days - days_in_each_month[month]
|
38
93
|
end
|
39
|
-
end
|
40
94
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
95
|
+
if all_conditions_match then
|
96
|
+
local rule_settings_key = rule_name .. "-settings"
|
97
|
+
local limit, time_period, limited_by, rule_id = unpack(redis.call('HMGET', rule_settings_key, 'limit', 'time-period', 'limited-by', 'rule-id'))
|
98
|
+
local throttle_key = rule_name .. ":" .. limit .. "V" .. request_properties.ip
|
99
|
+
local new_value = redis.call('INCR', throttle_key)
|
100
|
+
|
101
|
+
if new_value == 1 then
|
102
|
+
redis.call('EXPIRE', throttle_key, tonumber(time_period))
|
103
|
+
end
|
104
|
+
|
105
|
+
if tonumber(new_value) >= tonumber(limit) then
|
106
|
+
return rule_id
|
107
|
+
else
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
end
|
56
111
|
end
|
57
112
|
end
|
58
113
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
114
|
+
local function check_rules(functions_to_check)
|
115
|
+
for _, check in ipairs(functions_to_check) do
|
116
|
+
local rule = check.func(unpack(check.args))
|
117
|
+
local category = check.category
|
118
|
+
|
119
|
+
if type(rule) == "string" then
|
120
|
+
return rule, category
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
return false, false
|
64
125
|
end
|
65
126
|
|
66
|
-
local function
|
67
|
-
local
|
68
|
-
|
69
|
-
|
70
|
-
|
127
|
+
local function check_blocks(request)
|
128
|
+
local rule_categories = {
|
129
|
+
{ category = "bi", func = ip_in_hash, args = { "rules-blocked-i", request.ip } },
|
130
|
+
{ category = "bc", func = ip_in_cidr_range, args = { "rules-blocked-cidrs-set", request.ip_decimal_lexical } },
|
131
|
+
{ category = "bu", func = match_by_pattern, args = { "u", request.user_agent } },
|
132
|
+
{ category = "bp", func = match_by_pattern, args = { "p", request.path } },
|
133
|
+
{ category = "ba", func = match_by_pattern, args = { "a", request.parameters } },
|
134
|
+
{ category = "bh", func = match_by_pattern, args = { "h", request.host } },
|
135
|
+
{ category = "bm", func = match_by_pattern, args = { "m", request.method } },
|
136
|
+
{ category = "bd", func = match_by_pattern, args = { "rh", request.headers } },
|
137
|
+
{ category = "bpb", func = match_by_pattern, args = { "pb", request.post_body } },
|
138
|
+
{ category = "brl", func = blocked_by_rate_limit, args = { request } }
|
139
|
+
}
|
140
|
+
|
141
|
+
return check_rules(rule_categories)
|
71
142
|
end
|
72
143
|
|
73
|
-
|
74
|
-
local
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
144
|
+
local function check_allowed(request)
|
145
|
+
local rule_categories = {
|
146
|
+
{ category = "ai", func = ip_in_hash, args = { "rules-allowed-i", request.ip } },
|
147
|
+
{ category = "ac", func = ip_in_cidr_range, args = { "rules-allowed-cidrs-set", request.ip_decimal_lexical } }
|
148
|
+
}
|
149
|
+
|
150
|
+
return check_rules(rule_categories)
|
79
151
|
end
|
80
152
|
|
81
|
-
local
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
153
|
+
local request = {
|
154
|
+
["ip"] = ARGV[1],
|
155
|
+
["ip_decimal_lexical"] = string.rep("0", 39 - #ARGV[2]) .. ARGV[2],
|
156
|
+
["ts_in_milliseconds"] = ARGV[3],
|
157
|
+
["ts_in_seconds"] = ARGV[3] / 1000,
|
158
|
+
["user_agent"] = ARGV[4],
|
159
|
+
["path"] = ARGV[5],
|
160
|
+
["parameters"] = ARGV[6],
|
161
|
+
["host"] = ARGV[7],
|
162
|
+
["method"] = ARGV[8],
|
163
|
+
["headers"] = ARGV[9],
|
164
|
+
["post_body"] = ARGV[10],
|
165
|
+
["ip_id"] = set_property_value_id_lookups("i", ARGV[1]),
|
166
|
+
["user_agent_id"] = set_property_value_id_lookups("u", ARGV[4]),
|
167
|
+
["path_id"] = set_property_value_id_lookups("p", ARGV[5]),
|
168
|
+
["parameters_id"] = set_property_value_id_lookups("a", ARGV[6]),
|
169
|
+
["host_id"] = set_property_value_id_lookups("h", ARGV[7]),
|
170
|
+
["method_id"] = set_property_value_id_lookups("m", ARGV[8])
|
171
|
+
}
|
172
|
+
|
173
|
+
local current_timebucket = get_timebucket(request.ts_in_seconds)
|
174
|
+
local blocked_rule = false
|
175
|
+
local blocked_category = nil
|
176
|
+
local treatment = "p"
|
177
|
+
local stream_id
|
178
|
+
|
179
|
+
if use_timestamps_as_request_ids == true then
|
180
|
+
stream_id = request.ts_in_milliseconds
|
181
|
+
else
|
182
|
+
stream_id = "*"
|
89
183
|
end
|
90
|
-
end
|
91
184
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
local
|
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
|
-
|
185
|
+
local stream_args = {
|
186
|
+
"XADD",
|
187
|
+
"rStream",
|
188
|
+
"MINID",
|
189
|
+
tostring((current_timebucket - EXPIRATION_IN_SECONDS) * 1000 ),
|
190
|
+
stream_id,
|
191
|
+
"i", request.ip_id,
|
192
|
+
"u", request.user_agent_id,
|
193
|
+
"p", request.path_id,
|
194
|
+
"h", request.host_id,
|
195
|
+
"m", request.method_id,
|
196
|
+
"a", request.parameters_id,
|
197
|
+
}
|
198
|
+
|
199
|
+
local allowed_rule, allowed_category = check_allowed(request)
|
200
|
+
|
201
|
+
if allowed_rule then
|
202
|
+
table.insert(stream_args, "t")
|
203
|
+
table.insert(stream_args, "a")
|
204
|
+
|
205
|
+
treatment = "a"
|
206
|
+
|
207
|
+
table.insert(stream_args, "ac")
|
208
|
+
table.insert(stream_args, allowed_category)
|
209
|
+
|
210
|
+
table.insert(stream_args, "ar")
|
211
|
+
table.insert(stream_args, allowed_rule)
|
212
|
+
else
|
213
|
+
blocked_rule, blocked_category = check_blocks(request)
|
214
|
+
end
|
215
|
+
|
216
|
+
if blocked_rule then
|
217
|
+
table.insert(stream_args, "t")
|
218
|
+
table.insert(stream_args, "b")
|
219
|
+
|
220
|
+
treatment = "b"
|
221
|
+
|
222
|
+
table.insert(stream_args, "bc")
|
223
|
+
table.insert(stream_args, blocked_category)
|
224
|
+
|
225
|
+
table.insert(stream_args, "br")
|
226
|
+
table.insert(stream_args, blocked_rule)
|
227
|
+
end
|
228
|
+
|
229
|
+
if blocked_rule == false and allowed_rule == false then
|
230
|
+
table.insert(stream_args, "t")
|
231
|
+
table.insert(stream_args, "p")
|
232
|
+
end
|
233
|
+
|
234
|
+
local request_id = redis.call(unpack(stream_args))
|
235
|
+
|
236
|
+
increment_leaderboard_for("i", request.ip_id, current_timebucket)
|
237
|
+
increment_leaderboard_for("u", request.user_agent_id, current_timebucket)
|
238
|
+
increment_leaderboard_for("p", request.path_id, current_timebucket)
|
239
|
+
increment_leaderboard_for("a", request.parameters_id, current_timebucket)
|
240
|
+
increment_leaderboard_for("h", request.host_id, current_timebucket)
|
241
|
+
increment_leaderboard_for("m", request.method_id, current_timebucket)
|
242
|
+
increment_leaderboard_for("t", treatment, current_timebucket)
|
243
|
+
|
244
|
+
set_property_to_requests_list("i", request.ip_id, request_id, current_timebucket)
|
245
|
+
set_property_to_requests_list("u", request.user_agent_id, request_id, current_timebucket)
|
246
|
+
set_property_to_requests_list("p", request.path_id, request_id, current_timebucket)
|
247
|
+
set_property_to_requests_list("a", request.parameters_id, request_id, current_timebucket)
|
248
|
+
set_property_to_requests_list("h", request.host_id, request_id, current_timebucket)
|
249
|
+
set_property_to_requests_list("m", request.method_id, request_id, current_timebucket)
|
250
|
+
set_property_to_requests_list("t", treatment, request_id, current_timebucket)
|
251
|
+
|
252
|
+
if blocked_rule ~= false then
|
253
|
+
increment_leaderboard_for("bc", blocked_category, current_timebucket)
|
254
|
+
set_property_to_requests_list("bc", blocked_category, request_id, current_timebucket)
|
255
|
+
|
256
|
+
increment_leaderboard_for("br", blocked_rule, current_timebucket)
|
257
|
+
set_property_to_requests_list("br", blocked_rule, request_id, current_timebucket)
|
258
|
+
end
|
259
|
+
|
260
|
+
if allowed_rule ~= false then
|
261
|
+
increment_leaderboard_for("ac", allowed_category, current_timebucket)
|
262
|
+
set_property_to_requests_list("ac", allowed_category, request_id, current_timebucket)
|
263
|
+
|
264
|
+
increment_leaderboard_for("ar", allowed_rule, current_timebucket)
|
265
|
+
set_property_to_requests_list("ar", allowed_rule, request_id, current_timebucket)
|
266
|
+
end
|
267
|
+
|
268
|
+
if blocked_rule ~= false then
|
132
269
|
return "Blocked"
|
133
|
-
|
134
|
-
else
|
270
|
+
elseif allowed_rule ~= false then
|
135
271
|
return "Allowed"
|
272
|
+
else
|
273
|
+
return "Passed"
|
136
274
|
end
|
data/lib/wafris/configuration.rb
CHANGED
@@ -12,7 +12,8 @@ module Wafris
|
|
12
12
|
)
|
13
13
|
@redis_pool_size = 20
|
14
14
|
|
15
|
-
|
15
|
+
# TODO: update HUB with the REDIS_URL on startup
|
16
|
+
create_settings if ENV['REDIS_URL']
|
16
17
|
end
|
17
18
|
|
18
19
|
def connection_pool
|
@@ -33,13 +34,11 @@ module Wafris
|
|
33
34
|
CONNECTION_ERROR
|
34
35
|
end
|
35
36
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
version = version_line.slice(/v\d.\d/)
|
42
|
-
redis.set('version', version)
|
37
|
+
def create_settings
|
38
|
+
redis.hset('waf-settings',
|
39
|
+
'version', Wafris::VERSION,
|
40
|
+
'client', 'ruby',
|
41
|
+
'redis-host', 'heroku')
|
43
42
|
end
|
44
43
|
|
45
44
|
def core_sha
|
data/lib/wafris/middleware.rb
CHANGED
@@ -7,7 +7,7 @@ module Wafris
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def call(env)
|
10
|
-
user_defined_proxies = ENV['
|
10
|
+
user_defined_proxies = ENV['TRUSTED_PROXY_RANGES'].split(',') if ENV['TRUSTED_PROXY_RANGES']
|
11
11
|
|
12
12
|
valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
|
13
13
|
|
data/lib/wafris/version.rb
CHANGED
data/lib/wafris.rb
CHANGED
@@ -13,7 +13,7 @@ module Wafris
|
|
13
13
|
class << self
|
14
14
|
def configure
|
15
15
|
yield configuration
|
16
|
-
configuration.
|
16
|
+
configuration.create_settings
|
17
17
|
end
|
18
18
|
|
19
19
|
def configuration
|
@@ -24,26 +24,20 @@ module Wafris
|
|
24
24
|
@configuration = Wafris::Configuration.new
|
25
25
|
end
|
26
26
|
|
27
|
-
# ip: the IP of the client making the request, may be from x-forwarded-for
|
28
|
-
# user_agent: full user agent making the request
|
29
|
-
# path: path including parameters of the request
|
30
|
-
# host: host (website/domain) making the request
|
31
|
-
# time: UTC time of the request (from the logs to match things up)
|
32
|
-
|
33
27
|
def allow_request?(request)
|
34
28
|
configuration.connection_pool.with do |conn|
|
35
|
-
time = Time.now.
|
36
|
-
puts "WAF LOG: headers with http-x-forwarded-for key #{request.get_header(Rack::Request::HTTP_X_FORWARDED_FOR)}"
|
37
|
-
puts "WAF LOG: Client IP #{request.ip}"
|
29
|
+
time = Time.now.utc.to_i * 1000
|
38
30
|
status = conn.evalsha(
|
39
31
|
configuration.core_sha,
|
40
32
|
argv: [
|
41
33
|
request.ip,
|
42
34
|
IPAddr.new(request.ip).to_i,
|
43
|
-
time
|
35
|
+
time,
|
44
36
|
request.user_agent,
|
45
37
|
request.path,
|
46
|
-
request.
|
38
|
+
request.params,
|
39
|
+
request.host,
|
40
|
+
request.request_method
|
47
41
|
]
|
48
42
|
)
|
49
43
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wafris
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Micahel Buckbee
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-08-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: connection_pool
|
@@ -186,10 +186,6 @@ extensions: []
|
|
186
186
|
extra_rdoc_files: []
|
187
187
|
files:
|
188
188
|
- lib/lua/dist/wafris_core.lua
|
189
|
-
- lib/lua/src/get_time_buckets.lua
|
190
|
-
- lib/lua/src/queries.lua
|
191
|
-
- lib/lua/src/seeds/data_load.lua
|
192
|
-
- lib/lua/src/time_bucket.lua
|
193
189
|
- lib/wafris.rb
|
194
190
|
- lib/wafris/configuration.rb
|
195
191
|
- lib/wafris/middleware.rb
|
@@ -199,11 +195,8 @@ homepage:
|
|
199
195
|
licenses:
|
200
196
|
- Elastic-2.0
|
201
197
|
metadata: {}
|
202
|
-
post_install_message:
|
203
|
-
|
204
|
-
BETA software. We may ask that you clear your redis instance but
|
205
|
-
will do our best to help migrate any block or allow rules that
|
206
|
-
you have created.
|
198
|
+
post_install_message: " Thank you for installing the wafris gem. \n \n If
|
199
|
+
you haven't already, please sign up for Wafris Hub at:\n\n https://wafris.org\n\n"
|
207
200
|
rdoc_options: []
|
208
201
|
require_paths:
|
209
202
|
- lib
|
@@ -221,5 +214,5 @@ requirements: []
|
|
221
214
|
rubygems_version: 3.3.26
|
222
215
|
signing_key:
|
223
216
|
specification_version: 4
|
224
|
-
summary: Web
|
217
|
+
summary: Web Application Firewall for Rack apps
|
225
218
|
test_files: []
|
@@ -1,58 +0,0 @@
|
|
1
|
-
function get_time_bucket_from_timestamp(unix_time_milliseconds)
|
2
|
-
local function calculate_years_number_of_days(yr)
|
3
|
-
return (yr % 4 == 0 and (yr % 100 ~= 0 or yr % 400 == 0)) and 366 or 365
|
4
|
-
end
|
5
|
-
|
6
|
-
local function get_year_and_day_number(year, days)
|
7
|
-
while days >= calculate_years_number_of_days(year) do
|
8
|
-
days = days - calculate_years_number_of_days(year)
|
9
|
-
year = year + 1
|
10
|
-
end
|
11
|
-
return year, days
|
12
|
-
end
|
13
|
-
|
14
|
-
local function get_month_and_month_day(days, year)
|
15
|
-
local days_in_each_month = {
|
16
|
-
31,
|
17
|
-
(calculate_years_number_of_days(year) == 366 and 29 or 28),
|
18
|
-
31,
|
19
|
-
30,
|
20
|
-
31,
|
21
|
-
30,
|
22
|
-
31,
|
23
|
-
31,
|
24
|
-
30,
|
25
|
-
31,
|
26
|
-
30,
|
27
|
-
31,
|
28
|
-
}
|
29
|
-
|
30
|
-
for month = 1, #days_in_each_month do
|
31
|
-
if days - days_in_each_month[month] <= 0 then
|
32
|
-
return month, days
|
33
|
-
end
|
34
|
-
days = days - days_in_each_month[month]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
local unix_time = unix_time_milliseconds / 1000
|
39
|
-
local year = 1970
|
40
|
-
local days = math.ceil(unix_time / 86400)
|
41
|
-
local month = nil
|
42
|
-
|
43
|
-
year, days = get_year_and_day_number(year, days)
|
44
|
-
month, days = get_month_and_month_day(days, year)
|
45
|
-
local hours = math.floor(unix_time / 3600 % 24)
|
46
|
-
-- local minutes, seconds = math.floor(unix_time / 60 % 60), math.floor(unix_time % 60)
|
47
|
-
-- hours = hours > 12 and hours - 12 or hours == 0 and 12 or hours
|
48
|
-
return string.format("%04d-%02d-%02d-%02d", year, month, days, hours)
|
49
|
-
end
|
50
|
-
|
51
|
-
function get_time_buckets(unix_time_milliseconds)
|
52
|
-
local time_buckets = {}
|
53
|
-
|
54
|
-
for i = 23, 0, -1 do
|
55
|
-
table.insert(time_buckets, get_time_bucket_from_timestamp(unix_time_milliseconds - (1000 * 60 * 60 * i)))
|
56
|
-
end
|
57
|
-
return time_buckets
|
58
|
-
end
|
data/lib/lua/src/queries.lua
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
local function num_requests(start_time, end_time)
|
2
|
-
local request_keys = redis.call('KEYS', 'unique-requests:*')
|
3
|
-
redis.call('PFMERGE', 'merged_unique-requests', unpack(request_keys))
|
4
|
-
return redis.call('PFCOUNT', 'merged_unique-requests')
|
5
|
-
end
|
6
|
-
|
7
|
-
local function unique_ips(start_time, end_time)
|
8
|
-
local ip_keys = redis.call('KEYS', 'unique-ips:*')
|
9
|
-
redis.call('PFMERGE', 'merged_unique-ips', unpack(ip_keys))
|
10
|
-
return redis.call('PFCOUNT', 'merged_unique-ips')
|
11
|
-
end
|
12
|
-
|
13
|
-
redis.debug("Request count: ", num_requests(0, 10000000))
|
14
|
-
redis.debug("IP request count: ", unique_ips(0, 10000000))
|
@@ -1,104 +0,0 @@
|
|
1
|
-
-- Template strings below are replaced with generated
|
2
|
-
-- data from the ip_data_generator.rb script
|
3
|
-
-- local ipArray = { }
|
4
|
-
-- local timestampArray = { }
|
5
|
-
-- redis.debug("Timestamp count: ", #timestampArray)
|
6
|
-
|
7
|
-
local function get_time_bucket_from_timestamp(unix_time_milliseconds)
|
8
|
-
local function calculate_years_number_of_days(yr)
|
9
|
-
return (yr % 4 == 0 and (yr % 100 ~= 0 or yr % 400 == 0)) and 366 or 365
|
10
|
-
end
|
11
|
-
|
12
|
-
local function get_year_and_day_number(year, days)
|
13
|
-
while days >= calculate_years_number_of_days(year) do
|
14
|
-
days = days - calculate_years_number_of_days(year)
|
15
|
-
year = year + 1
|
16
|
-
end
|
17
|
-
return year, days
|
18
|
-
end
|
19
|
-
|
20
|
-
local function get_month_and_month_day(days, year)
|
21
|
-
local days_in_each_month = {
|
22
|
-
31,
|
23
|
-
(calculate_years_number_of_days(year) == 366 and 29 or 28),
|
24
|
-
31,
|
25
|
-
30,
|
26
|
-
31,
|
27
|
-
30,
|
28
|
-
31,
|
29
|
-
31,
|
30
|
-
30,
|
31
|
-
31,
|
32
|
-
30,
|
33
|
-
31,
|
34
|
-
}
|
35
|
-
|
36
|
-
for month = 1, #days_in_each_month do
|
37
|
-
if days - days_in_each_month[month] <= 0 then
|
38
|
-
return month, days
|
39
|
-
end
|
40
|
-
days = days - days_in_each_month[month]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
local unix_time = unix_time_milliseconds / 1000
|
45
|
-
local year = 1970
|
46
|
-
local days = math.ceil(unix_time / 86400)
|
47
|
-
local month = nil
|
48
|
-
|
49
|
-
year, days = get_year_and_day_number(year, days)
|
50
|
-
month, days = get_month_and_month_day(days, year)
|
51
|
-
local hours = math.floor(unix_time / 3600 % 24)
|
52
|
-
-- local minutes, seconds = math.floor(unix_time / 60 % 60), math.floor(unix_time % 60)
|
53
|
-
-- hours = hours > 12 and hours - 12 or hours == 0 and 12 or hours
|
54
|
-
return string.format("%04d-%02d-%02d-%02d", year, month, days, hours)
|
55
|
-
end
|
56
|
-
|
57
|
-
-- For: Relationship of IP to time of Request (Stream)
|
58
|
-
local function get_request_id(timestamp, ip, max_requests)
|
59
|
-
timestamp = timestamp or "*"
|
60
|
-
local request_id = redis.call("XADD", "ip-requests-stream", "MAXLEN", "~", max_requests, timestamp, "ip", ip)
|
61
|
-
return request_id
|
62
|
-
end
|
63
|
-
|
64
|
-
local function add_to_HLL_request_count(timebucket, request_id)
|
65
|
-
redis.call("PFADD", "unique-requests:" .. timebucket, request_id)
|
66
|
-
end
|
67
|
-
|
68
|
-
-- Configuration
|
69
|
-
local max_requests = 100000
|
70
|
-
local max_requests_per_ip = 10000
|
71
|
-
|
72
|
-
-- Interior of this for loop is what should go into wafris_core.lua
|
73
|
-
for i = 1, #timestampArray do
|
74
|
-
-- Setup
|
75
|
-
local ip = ipArray[math.random(#ipArray)]
|
76
|
-
local timestamp = timestampArray[i]
|
77
|
-
|
78
|
-
local request_id = get_request_id(timestamp, ip, max_requests)
|
79
|
-
|
80
|
-
-- GRAPH DATA COLLECTION
|
81
|
-
local current_timebucket = get_time_bucket_from_timestamp(timestamp)
|
82
|
-
add_to_HLL_request_count(current_timebucket, request_id)
|
83
|
-
|
84
|
-
-- For: Looking up Requests an IP has made (Stream) / time of request
|
85
|
-
local ip_stream_key = "ip-stream:" .. ip
|
86
|
-
local ip_stream_id =
|
87
|
-
redis.call("XADD", ip_stream_key, "MAXLEN", "~", max_requests_per_ip, "*", "request_id", request_id)
|
88
|
-
|
89
|
-
-- For: Precalc of Number of Requests (Key)
|
90
|
-
local requests_count_key = "requests-count:" .. current_timebucket
|
91
|
-
redis.call("INCR", requests_count_key)
|
92
|
-
|
93
|
-
-- For: Precalc of Number of Requests from an IP (Key)
|
94
|
-
local ips_count_bucket_key = "ips-count:" .. ip .. ":" .. current_timebucket
|
95
|
-
redis.call("INCR", ips_count_bucket_key)
|
96
|
-
|
97
|
-
-- For: Precalc of Number of Unique IPs making Requests (HLL)
|
98
|
-
local ips_count_hll_key = "unique-ips:" .. current_timebucket
|
99
|
-
redis.call("PFADD", ips_count_hll_key, ip)
|
100
|
-
|
101
|
-
-- For: Leaderboard of IPs with Request count as score
|
102
|
-
local ip_leaderboard_sset_key = "ip-leader-sset:" .. current_timebucket
|
103
|
-
redis.call("ZINCRBY", ip_leaderboard_sset_key, 1, ip)
|
104
|
-
end
|
data/lib/lua/src/time_bucket.lua
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
-- Code was pulled from https://otland.net/threads/how-convert-timestamp-to-date-type.251657/
|
2
|
-
-- An alternate solution is https://gist.github.com/markuman/e96d04139cd8acc33604
|
3
|
-
local function get_time_bucket_from_timestamp(unix_time_milliseconds)
|
4
|
-
local function calculate_years_number_of_days(yr)
|
5
|
-
return (yr % 4 == 0 and (yr % 100 ~= 0 or yr % 400 == 0)) and 366 or 365
|
6
|
-
end
|
7
|
-
|
8
|
-
local function get_year_and_day_number(year, days)
|
9
|
-
while days >= calculate_years_number_of_days(year) do
|
10
|
-
days = days - calculate_years_number_of_days(year)
|
11
|
-
year = year + 1
|
12
|
-
end
|
13
|
-
return year, days
|
14
|
-
end
|
15
|
-
|
16
|
-
local function get_month_and_month_day(days, year)
|
17
|
-
local days_in_each_month = {
|
18
|
-
31,
|
19
|
-
(calculate_years_number_of_days(year) == 366 and 29 or 28),
|
20
|
-
31, 30, 31,30,31,31,30,31,30,31
|
21
|
-
}
|
22
|
-
|
23
|
-
for month = 1, #days_in_each_month do
|
24
|
-
if days - days_in_each_month[month] <= 0 then return month, days end
|
25
|
-
days = days - days_in_each_month[month]
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
local unix_time = unix_time_milliseconds / 1000
|
30
|
-
local year = 1970
|
31
|
-
local days = math.ceil(unix_time/86400)
|
32
|
-
local month = nil
|
33
|
-
|
34
|
-
year, days = get_year_and_day_number(year, days)
|
35
|
-
month, days = get_month_and_month_day(days, year)
|
36
|
-
local hours = math.floor(unix_time / 3600 % 24)
|
37
|
-
-- local minutes, seconds = math.floor(unix_time / 60 % 60), math.floor(unix_time % 60)
|
38
|
-
-- hours = hours > 12 and hours - 12 or hours == 0 and 12 or hours
|
39
|
-
return string.format("%04d-%02d-%02d-%02d", year, month, days, hours)
|
40
|
-
end
|