wafris 0.0.1
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 +7 -0
- data/lib/wafris/configuration.rb +44 -0
- data/lib/wafris/middleware.rb +20 -0
- data/lib/wafris/railtie.rb +9 -0
- data/lib/wafris/version.rb +5 -0
- data/lib/wafris/wafris_core.lua +42 -0
- data/lib/wafris.rb +53 -0
- metadata +217 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dd571cbf8934f0b14384e1b85d42d5a70ffaaa98f17e8fcfd3545553b6b1d6af
|
4
|
+
data.tar.gz: a5a053621c83c0ed2892a8aca08fc26439e6e0cff95bcdbf8787b35c0ad1eaf3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 339e8b1fee46d79287716e20edfc24b80c720a82f12d07485508c0beb16c9813f4f35530b7473d0711d75a6e316e97236d7912024b984e96524649021d58be19
|
7
|
+
data.tar.gz: c674ca5de599c5f0bb7f7bfb6beba19d4bec38411f66cddf27897c4ecafe9a3486e723b30e39a7cb63472106ca62483ee7ffc5b98fd31dd331d7eae82efc13df
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wafris
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :redis
|
6
|
+
attr_accessor :redis_pool_size
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@redis = Redis.new
|
10
|
+
@redis_pool_size = 20
|
11
|
+
end
|
12
|
+
|
13
|
+
def connection_pool
|
14
|
+
@connection_pool ||=
|
15
|
+
ConnectionPool.new(size: redis_pool_size) { redis }
|
16
|
+
end
|
17
|
+
|
18
|
+
def enabled?
|
19
|
+
redis.ping
|
20
|
+
|
21
|
+
return true
|
22
|
+
rescue Redis::CannotConnectError
|
23
|
+
raise <<~CONNECTION_ERROR
|
24
|
+
Wafris cannot connect to Redis.
|
25
|
+
|
26
|
+
The current Redis instance points to a connection that
|
27
|
+
cannot be pinged.
|
28
|
+
CONNECTION_ERROR
|
29
|
+
end
|
30
|
+
|
31
|
+
def script_sha
|
32
|
+
@script_sha ||= redis.script(:load, wafris_core)
|
33
|
+
end
|
34
|
+
|
35
|
+
def wafris_core
|
36
|
+
File.read(
|
37
|
+
File.join(
|
38
|
+
File.dirname(__FILE__),
|
39
|
+
'wafris_core.lua'
|
40
|
+
)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wafris
|
4
|
+
class Middleware
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
request = Rack::Request.new(env)
|
11
|
+
|
12
|
+
if Wafris.configuration.enabled? && Wafris.allow_request?(request)
|
13
|
+
@app.call(env)
|
14
|
+
else
|
15
|
+
puts 'blocked'
|
16
|
+
[403, {}, ['Blocked']]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
local LAST_REQUESTS_TIME = 'last_requests_time'
|
2
|
+
local TWENTY_FOUR_HOURS = 86400
|
3
|
+
|
4
|
+
local ip = ARGV[1]
|
5
|
+
local ip_to_decimal = ARGV[2]
|
6
|
+
local unix_time = ARGV[3]
|
7
|
+
local expire_time = unix_time - TWENTY_FOUR_HOURS
|
8
|
+
local ip_request_string = "ip-requests-" .. ip
|
9
|
+
local hour_bucket = ARGV[4]
|
10
|
+
|
11
|
+
-- LEADERBOARD DATA COLLECTION
|
12
|
+
-- Add IP to last_requests_time key by integer timestamp
|
13
|
+
-- ZADD last_requets_time 1661356145 '192.168.1.1'
|
14
|
+
redis.call('ZADD', LAST_REQUESTS_TIME, unix_time, ip)
|
15
|
+
-- Remove IP from last_requests_time if it has been there for 24 hours
|
16
|
+
-- ZREMRANGEBYSCORE last_requests_time 0 (1661356145 - 86400)
|
17
|
+
redis.call('ZREMRANGEBYSCORE', LAST_REQUESTS_TIME, 0, expire_time)
|
18
|
+
-- Add IP to ip-requests-<ip> for leaderboard tracking
|
19
|
+
-- LPUSH ip-requests-192.168.1.1 1661356145
|
20
|
+
redis.call('LPUSH', ip_request_string, unix_time)
|
21
|
+
-- Have the key expire in 24 hours
|
22
|
+
-- EXPIRE ip-requests-192.168.1.1 86400
|
23
|
+
redis.call('EXPIRE', ip_request_string, TWENTY_FOUR_HOURS)
|
24
|
+
|
25
|
+
-- GRAPH DATA COLLECTION
|
26
|
+
-- Increment counter for hourly buckets
|
27
|
+
-- INC all-ips:2022-10-01:12
|
28
|
+
redis.call('INCR', hour_bucket)
|
29
|
+
-- EXPIRE all-ips:2022-10-01:12 86400
|
30
|
+
redis.call('EXPIRE', hour_bucket, TWENTY_FOUR_HOURS)
|
31
|
+
|
32
|
+
-- BLOCKING LOGIC
|
33
|
+
-- Safelist Range Check
|
34
|
+
if next(redis.call('ZRANGEBYSCORE', 'allowed_ranges', ip_to_decimal, "+inf", "LIMIT", 0, 1)) then
|
35
|
+
return 'Allowed'
|
36
|
+
-- Blocklist Range Check
|
37
|
+
elseif next(redis.call('ZRANGEBYSCORE', 'blocked_ranges', ip_to_decimal, "+inf", "LIMIT", 0, 1)) then
|
38
|
+
return 'Blocked'
|
39
|
+
-- No Matches
|
40
|
+
else
|
41
|
+
return 'Not found'
|
42
|
+
end
|
data/lib/wafris.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'connection_pool'
|
4
|
+
require 'rails'
|
5
|
+
require 'redis'
|
6
|
+
|
7
|
+
require 'wafris/configuration'
|
8
|
+
require 'wafris/middleware'
|
9
|
+
|
10
|
+
require 'wafris/railtie' if defined?(Rails::Railtie)
|
11
|
+
|
12
|
+
module Wafris
|
13
|
+
class << self
|
14
|
+
def configure
|
15
|
+
yield configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
def configuration
|
19
|
+
@configuration ||= Wafris::Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset
|
23
|
+
@configuration = Wafris::Configuration.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# ip: the IP of the client making the request, may be from x-forwarded-for
|
27
|
+
# user_agent: full user agent making the request
|
28
|
+
# path: path including parameters of the request
|
29
|
+
# host: host (website/domain) making the request
|
30
|
+
# time: UTC time of the request (from the logs to match things up)
|
31
|
+
|
32
|
+
def allow_request?(request)
|
33
|
+
configuration.connection_pool.with do |conn|
|
34
|
+
time = Time.now
|
35
|
+
status = conn.evalsha(
|
36
|
+
configuration.script_sha,
|
37
|
+
argv: [
|
38
|
+
request.ip,
|
39
|
+
IPAddr.new(request.ip).to_i,
|
40
|
+
time.to_i,
|
41
|
+
"all-ips:#{time.strftime('%Y-%m-%d')}:#{time.hour}"
|
42
|
+
]
|
43
|
+
)
|
44
|
+
|
45
|
+
if status.eql? 'Blocked'
|
46
|
+
return false
|
47
|
+
else
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wafris
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Micahel Buckbee
|
8
|
+
- Ryan Castillo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2023-02-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: connection_pool
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '2.3'
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.3.0
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - "~>"
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '2.3'
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.3.0
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rack
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
type: :runtime
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: redis
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.8'
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 4.8.0
|
58
|
+
type: :runtime
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - "~>"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '4.8'
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 4.8.0
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: minitest
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '5.1'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '5.1'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: pry
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0.14'
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 0.14.1
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "~>"
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0.14'
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.14.1
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: rack-test
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - "~>"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '2.0'
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 2.0.2
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '2.0'
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: 2.0.2
|
122
|
+
- !ruby/object:Gem::Dependency
|
123
|
+
name: rails
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - "~>"
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '7.0'
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 7.0.4
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '7.0'
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 7.0.4
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: railties
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - "~>"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '7.0'
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: 7.0.4
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '7.0'
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: 7.0.4
|
162
|
+
- !ruby/object:Gem::Dependency
|
163
|
+
name: rake
|
164
|
+
requirement: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - "~>"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '13.0'
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: 13.0.6
|
172
|
+
type: :development
|
173
|
+
prerelease: false
|
174
|
+
version_requirements: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - "~>"
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '13.0'
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: 13.0.6
|
182
|
+
description:
|
183
|
+
email:
|
184
|
+
executables: []
|
185
|
+
extensions: []
|
186
|
+
extra_rdoc_files: []
|
187
|
+
files:
|
188
|
+
- lib/wafris.rb
|
189
|
+
- lib/wafris/configuration.rb
|
190
|
+
- lib/wafris/middleware.rb
|
191
|
+
- lib/wafris/railtie.rb
|
192
|
+
- lib/wafris/version.rb
|
193
|
+
- lib/wafris/wafris_core.lua
|
194
|
+
homepage:
|
195
|
+
licenses:
|
196
|
+
- MIT
|
197
|
+
metadata: {}
|
198
|
+
post_install_message:
|
199
|
+
rdoc_options: []
|
200
|
+
require_paths:
|
201
|
+
- lib
|
202
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
203
|
+
requirements:
|
204
|
+
- - ">="
|
205
|
+
- !ruby/object:Gem::Version
|
206
|
+
version: '2.5'
|
207
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - ">="
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '0'
|
212
|
+
requirements: []
|
213
|
+
rubygems_version: 3.3.26
|
214
|
+
signing_key:
|
215
|
+
specification_version: 4
|
216
|
+
summary: Web application firewall for Rack apps
|
217
|
+
test_files: []
|