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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66b7b15717346a4c6cef883d8615d539cfe9a7a9aca15b1f430b11faf510f378
4
- data.tar.gz: be84cc1b2adea93af908914942d798ec56406dfbeff5caf063cf2d088b8e1299
3
+ metadata.gz: cf428bf85deefd6d35c17de90e06f1090710577d0216f2db9ebe5460e1a8260f
4
+ data.tar.gz: ea64374d9b9fe3e629ae84966ab5a35024f139a44c40265e52a46f55c2c41e1c
5
5
  SHA512:
6
- metadata.gz: b43cc330f0729a0ce0a74a77670819a474b2f28b11fcd8c45ea4ac30531aff0158fae6e468810666403ec90966444f1fe3f604f501fd173cc2ede8d509f38c53
7
- data.tar.gz: 6649a022d4213ca7ad57858aed9a33b9860a7a51b9387b4129d12fc349546ac1ab108ff969e3d9a07c70031104816017ec07f71b3854b2d5c216b74fe06944f2
6
+ metadata.gz: aae408ce2065bc030209eb9d5b68ac58917fed970b7af940022e1781659394d9ef32b254864e0ca97c5395f0c6e6d52b40cfc42ff26f5ac3b1302a6d9c76046e
7
+ data.tar.gz: 886557ee407acf070bd94aeec88e21fba8cbc40ab15f8af337be3507dc41d6e6aa7df4ed180c3d7d262c453f8567cd3c956502a3a793541b8c0b57d3c2f11cb5
@@ -1,136 +1,274 @@
1
- local version = "v0.8:"
2
- local wafris_prefix = "w:" .. version
1
+ local use_timestamps_as_request_ids = true
2
+ local EXPIRATION_IN_SECONDS = 86400
3
3
 
4
- local function get_time_bucket_from_timestamp(unix_time_milliseconds, minutes_flag)
5
- local function calculate_years_number_of_days(yr)
6
- return (yr % 4 == 0 and (yr % 100 ~= 0 or yr % 400 == 0)) and 366 or 365
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
- local function get_year_and_day_number(year, days)
10
- while days >= calculate_years_number_of_days(year) do
11
- days = days - calculate_years_number_of_days(year)
12
- year = year + 1
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
- local function get_month_and_month_day(days, year)
18
- local days_in_each_month = {
19
- 31,
20
- (calculate_years_number_of_days(year) == 366 and 29 or 28),
21
- 31,
22
- 30,
23
- 31,
24
- 30,
25
- 31,
26
- 31,
27
- 30,
28
- 31,
29
- 30,
30
- 31,
31
- }
32
-
33
- for month = 1, #days_in_each_month do
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
- local unix_time = unix_time_milliseconds / 1000
42
- local year = 1970
43
- local days = math.ceil(unix_time / 86400)
44
- local month = nil
45
-
46
- year, days = get_year_and_day_number(year, days)
47
- month, days = get_month_and_month_day(days, year)
48
- local hours = math.floor(unix_time / 3600 % 24)
49
- -- local minutes, seconds = math.floor(unix_time / 60 % 60), math.floor(unix_time % 60)
50
- -- hours = hours > 12 and hours - 12 or hours == 0 and 12 or hours
51
- if minutes_flag == false then
52
- return string.format("%04d%02d%02d%02d", year, month, days, hours)
53
- elseif minutes_flag == true then
54
- local minutes = math.floor(unix_time / 60 % 60)
55
- return string.format("%04d%02d%02d%02d%02d", year, month, days, hours, minutes)
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
- -- For: Relationship of IP to time of Request (Stream)
60
- local function get_request_id(timestamp, ip, max_requests)
61
- timestamp = timestamp or "*"
62
- local request_id = redis.call("XADD", "ip-requests-stream", "MAXLEN", "~", max_requests, timestamp, "ip", ip)
63
- return request_id
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 add_to_graph_timebucket(timebucket, request_id)
67
- local key = wafris_prefix .. "gr-ct:" .. timebucket
68
- redis.call("PFADD", key, request_id)
69
- -- Expire the key after 25 hours if it has no expiry
70
- redis.call("EXPIRE", key, 90000)
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
- -- For: Leaderboard of IPs with Request count as score
74
- local function increment_timebucket_for(type, timebucket, property)
75
- local key = wafris_prefix .. type .. "lb:" .. timebucket
76
- redis.call("ZINCRBY", key, 1, property)
77
- -- Expire the key after 25 hours if it has no expiry
78
- redis.call("EXPIRE", key, 90000)
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 function increment_partial_hourly_request_counters(unix_time_milliseconds)
82
- for i = 1, 60 do
83
- local timebucket_in_milliseconds = unix_time_milliseconds + 60000 * (i - 1)
84
- local timebucket = get_time_bucket_from_timestamp(timebucket_in_milliseconds, true)
85
- local key = wafris_prefix .. "hr-ct:" .. timebucket
86
- redis.call("INCR", key)
87
- -- Expire the key after 121 minutes if it has no expiry
88
- redis.call("EXPIRE", key, 7260)
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
- -- Configuration
93
- local max_requests = 100000
94
- local max_requests_per_ip = 10000
95
-
96
- local client_ip = ARGV[1]
97
- local client_ip_to_decimal = ARGV[2]
98
- local unix_time_milliseconds = ARGV[3]
99
- local unix_time = ARGV[3] / 1000
100
- local user_agent = ARGV[4]
101
- local request_path = ARGV[5]
102
- local host = ARGV[6]
103
-
104
- -- Initialize local variables
105
- local request_id = get_request_id(nil, client_ip, max_requests)
106
- local current_timebucket = get_time_bucket_from_timestamp(unix_time_milliseconds, false)
107
-
108
- -- CARD DATA COLLECTION
109
- increment_partial_hourly_request_counters(unix_time_milliseconds)
110
-
111
- -- GRAPH DATA COLLECTION
112
- add_to_graph_timebucket(current_timebucket, request_id)
113
-
114
- -- LEADERBOARD DATA COLLECTION
115
- increment_timebucket_for("ip:", current_timebucket, client_ip)
116
- increment_timebucket_for("ua:", current_timebucket, user_agent)
117
- increment_timebucket_for("path:", current_timebucket, request_path)
118
- increment_timebucket_for("host:", current_timebucket, host)
119
-
120
- -- BLOCKING LOGIC
121
- -- TODO: ZRANGEBYSCORE is deprecated in Redis 6.2+. Replace with ZRANGE
122
- if
123
- -- TODO: When we introduce ranges we'll have to do an exact check followed by a range starting with decimal ip to infinity.
124
- -- If the first result returned is "END" that means it falls in the range
125
-
126
- -- ZRANGEBYSCORE will always return a lua table, even if empty
127
- -- This call is checking if the table is empty
128
- next(redis.call("ZRANGEBYSCORE", "w:blocked-ranges", client_ip_to_decimal, client_ip_to_decimal, "LIMIT", 0, 1))
129
- ~= nil
130
- then
131
- increment_timebucket_for("blk:", current_timebucket, client_ip)
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
- -- No Matches
134
- else
270
+ elseif allowed_rule ~= false then
135
271
  return "Allowed"
272
+ else
273
+ return "Passed"
136
274
  end
@@ -12,7 +12,8 @@ module Wafris
12
12
  )
13
13
  @redis_pool_size = 20
14
14
 
15
- set_version if ENV['REDIS_URL']
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 set_version
37
- version_line = File.open(
38
- file_path("wafris_core"),
39
- &:readline
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
@@ -7,7 +7,7 @@ module Wafris
7
7
  end
8
8
 
9
9
  def call(env)
10
- user_defined_proxies = ENV['MY_PROXIES'].split(',') if ENV['MY_PROXIES']
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wafris
4
- VERSION = "0.8.5"
4
+ VERSION = "0.9.0"
5
5
  end
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.set_version
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.to_f * 1000
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.to_i,
35
+ time,
44
36
  request.user_agent,
45
37
  request.path,
46
- request.host
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.8.5
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-06-22 00:00:00.000000000 Z
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: |2
203
- Thank you for installing the wafris gem. Please note that this is
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 application firewall for Rack apps
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
@@ -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
@@ -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