wafris 0.8.5 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|