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 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wafris
4
+ class Railtie < ::Rails::Railtie
5
+ initializer 'wafris.middleware' do |app|
6
+ app.middleware.use(Wafris::Middleware)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wafris
4
+ VERSION = "0.0.1"
5
+ 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: []