wafris 0.8.5 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66b7b15717346a4c6cef883d8615d539cfe9a7a9aca15b1f430b11faf510f378
4
- data.tar.gz: be84cc1b2adea93af908914942d798ec56406dfbeff5caf063cf2d088b8e1299
3
+ metadata.gz: 8513adabe26d6d0e250902e0e623e2da68abb1eaab787dfc33a637636a8fdf61
4
+ data.tar.gz: e515c1d91decc74401976aa68b0a241c5d06368fe33d654e43cc67fe67337479
5
5
  SHA512:
6
- metadata.gz: b43cc330f0729a0ce0a74a77670819a474b2f28b11fcd8c45ea4ac30531aff0158fae6e468810666403ec90966444f1fe3f604f501fd173cc2ede8d509f38c53
7
- data.tar.gz: 6649a022d4213ca7ad57858aed9a33b9860a7a51b9387b4129d12fc349546ac1ab108ff969e3d9a07c70031104816017ec07f71b3854b2d5c216b74fe06944f2
6
+ metadata.gz: fa98c3073ef1e614be18e40c867c05e5e9fb715a0722d5c8ef7483f6c643d049da373877329b16f44c0faca571ea336c181941ba27017b9c2e58f6d53fc1571e
7
+ data.tar.gz: be8909dc34636dc416ceb6d27b068fd3e5e2e1756d5ecadf764910ad657459da7534eba72012b8d2ac3c91d51dd972d982f516758b1494a00b94582d6e4a12a3
@@ -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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'version'
4
+
3
5
  module Wafris
4
6
  class Configuration
5
7
  attr_accessor :redis
@@ -12,7 +14,8 @@ module Wafris
12
14
  )
13
15
  @redis_pool_size = 20
14
16
 
15
- set_version if ENV['REDIS_URL']
17
+ # TODO: update HUB with the REDIS_URL on startup
18
+ create_settings if ENV['REDIS_URL']
16
19
  end
17
20
 
18
21
  def connection_pool
@@ -33,13 +36,11 @@ module Wafris
33
36
  CONNECTION_ERROR
34
37
  end
35
38
 
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)
39
+ def create_settings
40
+ redis.hset('waf-settings',
41
+ 'version', Wafris::VERSION,
42
+ 'client', 'ruby',
43
+ 'redis-host', 'heroku')
43
44
  end
44
45
 
45
46
  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.1"
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.1
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