rack-attack 5.1.0 → 5.2.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 +4 -4
- data/README.md +164 -79
- data/lib/rack/attack.rb +30 -8
- data/lib/rack/attack/cache.rb +24 -10
- data/lib/rack/attack/check.rb +1 -0
- data/lib/rack/attack/version.rb +1 -1
- data/spec/acceptance/allow2ban_spec.rb +71 -0
- data/spec/acceptance/blocking_ip_spec.rb +38 -0
- data/spec/acceptance/blocking_spec.rb +20 -0
- data/spec/acceptance/blocking_subnet_spec.rb +44 -0
- data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +111 -0
- data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +108 -0
- data/spec/acceptance/cache_store_config_for_throttle_spec.rb +48 -0
- data/spec/acceptance/cache_store_config_with_rails_spec.rb +31 -0
- data/spec/acceptance/customizing_blocked_response_spec.rb +41 -0
- data/spec/acceptance/customizing_throttled_response_spec.rb +59 -0
- data/spec/acceptance/extending_request_object_spec.rb +34 -0
- data/spec/acceptance/fail2ban_spec.rb +76 -0
- data/spec/acceptance/safelisting_ip_spec.rb +49 -0
- data/spec/acceptance/safelisting_spec.rb +16 -0
- data/spec/acceptance/safelisting_subnet_spec.rb +48 -0
- data/spec/acceptance/throttling_spec.rb +130 -1
- data/spec/acceptance/track_spec.rb +27 -0
- data/spec/acceptance/track_throttle_spec.rb +53 -0
- data/spec/spec_helper.rb +12 -0
- metadata +60 -4
- data/spec/rack_attack_store_config_spec.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21a52aca7aa6592b23e6a3e99f0b06cf6d4d9eedb8366ec57fe4f9cfe804ea82
|
4
|
+
data.tar.gz: 32e0db149bc10308fb8b5ae737147e1c11c9f854e08b9f453d9a7066911308fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 560d951d375a9114752b37a2858f48e85af9edcdeabb0073c6e6f8d179b6d10a0331a7abee625df4c0171f9c993bb105cc8e54fed9170fd47d288fb2bf29617a
|
7
|
+
data.tar.gz: b3993951744e2755873cc7ce0c6810c3780ad44c6c84491bda5277a46536589d15b26bd25e7849e7b20a4e28aebc86ed874cb0b61e0d988da54252a3b3e024dc
|
data/README.md
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
# Rack::Attack
|
2
|
-
*Rack middleware for blocking & throttling abusive requests*
|
1
|
+
# Rack::Attack
|
3
2
|
|
4
|
-
Rack
|
5
|
-
It allows *safelisting*, *blocklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
|
3
|
+
*Rack middleware for blocking & throttling abusive requests*
|
6
4
|
|
7
|
-
|
5
|
+
Protect your Rails and Rack apps from bad clients. Rack::Attack lets you easily decide when to *allow*, *block* and *throttle* based on properties of the request.
|
8
6
|
|
9
7
|
See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack.
|
10
8
|
|
@@ -12,98 +10,97 @@ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hac
|
|
12
10
|
[](https://travis-ci.org/kickstarter/rack-attack)
|
13
11
|
[](https://codeclimate.com/github/kickstarter/rack-attack)
|
14
12
|
|
15
|
-
## Looking for maintainers
|
16
|
-
|
17
|
-
I'm looking for new maintainers to help me support Rack::Attack. Check out
|
18
|
-
[issue #219 for details](https://github.com/kickstarter/rack-attack/issues/219).
|
19
|
-
|
20
13
|
## Getting started
|
21
14
|
|
22
|
-
|
15
|
+
### 1. Installing
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
23
18
|
|
24
19
|
```ruby
|
25
20
|
# In your Gemfile
|
21
|
+
|
26
22
|
gem 'rack-attack'
|
27
23
|
```
|
28
|
-
|
29
|
-
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install rack-attack
|
32
|
+
|
33
|
+
### 2. Plugging into the application
|
34
|
+
|
35
|
+
Then tell your ruby web application to use rack-attack as a middleware.
|
36
|
+
|
37
|
+
a) For __rails__ applications:
|
30
38
|
|
31
39
|
```ruby
|
32
40
|
# In config/application.rb
|
41
|
+
|
33
42
|
config.middleware.use Rack::Attack
|
34
43
|
```
|
35
44
|
|
36
|
-
|
45
|
+
b) For __rack__ applications:
|
37
46
|
|
38
47
|
```ruby
|
39
48
|
# In config.ru
|
49
|
+
|
50
|
+
require "rack/attack"
|
40
51
|
use Rack::Attack
|
41
52
|
```
|
42
53
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
class Rack::Attack
|
47
|
-
# your custom configuration...
|
48
|
-
end
|
49
|
-
```
|
54
|
+
__IMPORTANT__: By default, rack-attack won't perform any blocking or throttling, until you specifically tell it what to protect against by configuring some rules.
|
55
|
+
|
56
|
+
## Usage
|
50
57
|
|
51
58
|
*Tip:* The example in the wiki is a great way to get started:
|
52
59
|
[Example Configuration](https://github.com/kickstarter/rack-attack/wiki/Example-Configuration)
|
53
60
|
|
54
|
-
|
61
|
+
Define rules by calling `Rack::Attack` public methods, in any file that runs when your application is being initialized. For rails applications this means creating a new file named `config/initializers/rack_attack.rb` and writing your rules there.
|
55
62
|
|
56
|
-
|
57
|
-
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
|
58
|
-
```
|
63
|
+
### Safelisting
|
59
64
|
|
60
|
-
|
65
|
+
Safelists have the most precedence, so any request matching a safelist would be allowed despite matching any number of blocklists or throttles.
|
61
66
|
|
62
|
-
|
67
|
+
#### `safelist_ip(ip_address_string)`
|
63
68
|
|
64
|
-
|
69
|
+
E.g.
|
65
70
|
|
66
|
-
|
67
|
-
|
68
|
-
* Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked.
|
69
|
-
* Otherwise, all **tracks** are checked, and the request is allowed.
|
71
|
+
```ruby
|
72
|
+
# config/initializers/rack_attack.rb (for rails app)
|
70
73
|
|
71
|
-
|
74
|
+
Rack::Attack.safelist_ip("5.6.7.8")
|
75
|
+
```
|
76
|
+
|
77
|
+
#### `safelist_ip(ip_subnet_string)`
|
78
|
+
|
79
|
+
E.g.
|
72
80
|
|
73
81
|
```ruby
|
74
|
-
|
75
|
-
req = Rack::Attack::Request.new(env)
|
82
|
+
# config/initializers/rack_attack.rb (for rails app)
|
76
83
|
|
77
|
-
|
78
|
-
@app.call(env)
|
79
|
-
elsif blocklisted?(req)
|
80
|
-
self.class.blocklisted_response.call(env)
|
81
|
-
elsif throttled?(req)
|
82
|
-
self.class.throttled_response.call(env)
|
83
|
-
else
|
84
|
-
tracked?(req)
|
85
|
-
@app.call(env)
|
86
|
-
end
|
87
|
-
end
|
84
|
+
Rack::Attack.safelist_ip("5.6.7.0/24")
|
88
85
|
```
|
89
86
|
|
90
|
-
|
91
|
-
can cleanly monkey patch helper methods onto the
|
92
|
-
[request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb).
|
87
|
+
#### `safelist(name, &block)`
|
93
88
|
|
94
|
-
|
89
|
+
Name your custom safelist and make your ruby-block argument return a truthy value if you want the request to be blocked, and falsy otherwise.
|
95
90
|
|
96
|
-
|
91
|
+
The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request).
|
97
92
|
|
98
|
-
|
93
|
+
E.g.
|
99
94
|
|
100
|
-
|
101
|
-
|
102
|
-
A [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request) object is passed to the block (named 'req' in the examples).
|
95
|
+
```ruby
|
96
|
+
# config/initializers/rack_attack.rb (for rails apps)
|
103
97
|
|
104
|
-
|
98
|
+
# Provided that trusted users use an HTTP request header named APIKey
|
99
|
+
Rack::Attack.safelist("mark any authenticated access safe") do |request|
|
100
|
+
# Requests are allowed if the return value is truthy
|
101
|
+
request.env["APIKey"] == "secret-string"
|
102
|
+
end
|
105
103
|
|
106
|
-
```ruby
|
107
104
|
# Always allow requests from localhost
|
108
105
|
# (blocklist & throttles are skipped)
|
109
106
|
Rack::Attack.safelist('allow from localhost') do |req|
|
@@ -112,16 +109,44 @@ Rack::Attack.safelist('allow from localhost') do |req|
|
|
112
109
|
end
|
113
110
|
```
|
114
111
|
|
115
|
-
###
|
112
|
+
### Blocking
|
113
|
+
|
114
|
+
#### `blocklist_ip(ip_address_string)`
|
115
|
+
|
116
|
+
E.g.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
# config/initializers/rack_attack.rb (for rails apps)
|
120
|
+
|
121
|
+
Rack::Attack.blocklist_ip("1.2.3.4")
|
122
|
+
```
|
123
|
+
|
124
|
+
#### `blocklist_ip(ip_subnet_string)`
|
125
|
+
|
126
|
+
E.g.
|
116
127
|
|
117
128
|
```ruby
|
118
|
-
#
|
119
|
-
|
129
|
+
# config/initializers/rack_attack.rb (for rails apps)
|
130
|
+
|
131
|
+
Rack::Attack.blocklist_ip("1.2.0.0/16")
|
132
|
+
```
|
133
|
+
|
134
|
+
#### `blocklist(name, &block)`
|
135
|
+
|
136
|
+
Name your custom blocklist and make your ruby-block argument returna a truthy value if you want the request to be blocked, and falsy otherwise.
|
137
|
+
|
138
|
+
The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request).
|
139
|
+
|
140
|
+
E.g.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# config/initializers/rack_attack.rb (for rails apps)
|
144
|
+
|
145
|
+
Rack::Attack.blocklist("block all access to admin") do |request|
|
120
146
|
# Requests are blocked if the return value is truthy
|
121
|
-
|
147
|
+
request.path.start_with?("/admin")
|
122
148
|
end
|
123
149
|
|
124
|
-
# Block logins from a bad user agent
|
125
150
|
Rack::Attack.blocklist('block bad UA logins') do |req|
|
126
151
|
req.path == '/login' && req.post? && req.user_agent == 'BadUA'
|
127
152
|
end
|
@@ -134,6 +159,8 @@ This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Ma
|
|
134
159
|
See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on
|
135
160
|
how the parameters work. For multiple filters, be sure to put each filter in a separate blocklist and use a unique discriminator for each fail2ban filter.
|
136
161
|
|
162
|
+
Fail2ban state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present).
|
163
|
+
|
137
164
|
```ruby
|
138
165
|
# Block suspicious requests for '/etc/password' or wordpress specific paths.
|
139
166
|
# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
|
@@ -154,8 +181,12 @@ end
|
|
154
181
|
Note that `Fail2Ban` filters are not automatically scoped to the blocklist, so when using multiple filters in an application the scoping must be added to the discriminator e.g. `"pentest:#{req.ip}"`.
|
155
182
|
|
156
183
|
#### Allow2Ban
|
184
|
+
|
157
185
|
`Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
|
158
186
|
clients until such time as they reach maxretry at which they are cut off as per normal.
|
187
|
+
|
188
|
+
Allow2ban state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present).
|
189
|
+
|
159
190
|
```ruby
|
160
191
|
# Lockout IP addresses that are hammering your login page.
|
161
192
|
# After 20 requests in 1 minute, block all requests from that IP for 1 hour.
|
@@ -170,33 +201,40 @@ Rack::Attack.blocklist('allow2ban login scrapers') do |req|
|
|
170
201
|
end
|
171
202
|
```
|
172
203
|
|
204
|
+
### Throttling
|
205
|
+
|
206
|
+
Throttle state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present).
|
207
|
+
|
208
|
+
#### `throttle(name, options, &block)`
|
209
|
+
|
210
|
+
Name your custom throttle, provide `limit` and `period` as options, and make your ruby-block argument return the __discriminator__. This discriminator is how you tell rack-attack whether you're limiting per IP address, per user email or any other.
|
173
211
|
|
174
|
-
|
212
|
+
The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request).
|
213
|
+
|
214
|
+
E.g.
|
175
215
|
|
176
216
|
```ruby
|
177
|
-
#
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
# "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
|
182
|
-
#
|
183
|
-
# If falsy, the cache key is neither incremented nor checked.
|
184
|
-
|
185
|
-
req.ip
|
217
|
+
# config/initializers/rack_attack.rb (for rails apps)
|
218
|
+
|
219
|
+
Rack::Attack.throttle("requests by ip", limit: 5, period: 2) do |request|
|
220
|
+
request.ip
|
186
221
|
end
|
187
222
|
|
188
223
|
# Throttle login attempts for a given email parameter to 6 reqs/minute
|
189
224
|
# Return the email as a discriminator on POST /login requests
|
190
|
-
Rack::Attack.throttle('logins
|
191
|
-
|
225
|
+
Rack::Attack.throttle('limit logins per email', limit: 6, period: 60) do |req|
|
226
|
+
if req.path == '/login' && req.post?
|
227
|
+
req.params['email']
|
228
|
+
end
|
192
229
|
end
|
193
230
|
|
194
231
|
# You can also set a limit and period using a proc. For instance, after
|
195
232
|
# Rack::Auth::Basic has authenticated the user:
|
196
|
-
limit_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
|
197
|
-
period_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 1
|
198
|
-
|
199
|
-
|
233
|
+
limit_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 100 : 1 }
|
234
|
+
period_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 1 : 60 }
|
235
|
+
|
236
|
+
Rack::Attack.throttle('request per ip', limit: limit_proc, period: period_proc) do |request|
|
237
|
+
request.ip
|
200
238
|
end
|
201
239
|
```
|
202
240
|
|
@@ -222,7 +260,17 @@ ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, r
|
|
222
260
|
end
|
223
261
|
```
|
224
262
|
|
225
|
-
|
263
|
+
### Cache store configuration
|
264
|
+
|
265
|
+
Throttle, allow2ban and fail2ban state is stored in a configurable cache (which defaults to `Rails.cache` if present), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
|
269
|
+
```
|
270
|
+
|
271
|
+
Note that `Rack::Attack.cache` is only used for throttling, allow2ban and fail2ban filtering; not blocklisting and safelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
|
272
|
+
|
273
|
+
## Customizing responses
|
226
274
|
|
227
275
|
Customize the response of blocklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html).
|
228
276
|
|
@@ -287,6 +335,43 @@ ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, r
|
|
287
335
|
end
|
288
336
|
```
|
289
337
|
|
338
|
+
## How it works
|
339
|
+
|
340
|
+
The Rack::Attack middleware compares each request against *safelists*, *blocklists*, *throttles*, and *tracks* that you define. There are none by default.
|
341
|
+
|
342
|
+
* If the request matches any **safelist**, it is allowed.
|
343
|
+
* Otherwise, if the request matches any **blocklist**, it is blocked.
|
344
|
+
* Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked.
|
345
|
+
* Otherwise, all **tracks** are checked, and the request is allowed.
|
346
|
+
|
347
|
+
The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
def call(env)
|
351
|
+
req = Rack::Attack::Request.new(env)
|
352
|
+
|
353
|
+
if safelisted?(req)
|
354
|
+
@app.call(env)
|
355
|
+
elsif blocklisted?(req)
|
356
|
+
self.class.blocklisted_response.call(env)
|
357
|
+
elsif throttled?(req)
|
358
|
+
self.class.throttled_response.call(env)
|
359
|
+
else
|
360
|
+
tracked?(req)
|
361
|
+
@app.call(env)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
```
|
365
|
+
|
366
|
+
Note: `Rack::Attack::Request` is just a subclass of `Rack::Request` so that you
|
367
|
+
can cleanly monkey patch helper methods onto the
|
368
|
+
[request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb).
|
369
|
+
|
370
|
+
### About Tracks
|
371
|
+
|
372
|
+
`Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes.
|
373
|
+
|
374
|
+
|
290
375
|
## Testing
|
291
376
|
|
292
377
|
A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will
|
data/lib/rack/attack.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require 'forwardable'
|
3
|
+
require 'rack/attack/path_normalizer'
|
4
|
+
require 'rack/attack/request'
|
5
|
+
require "ipaddr"
|
3
6
|
|
4
7
|
class Rack::Attack
|
5
8
|
class MisconfiguredStoreError < StandardError; end
|
6
9
|
class MissingStoreError < StandardError; end
|
7
10
|
|
8
11
|
autoload :Cache, 'rack/attack/cache'
|
9
|
-
autoload :PathNormalizer, 'rack/attack/path_normalizer'
|
10
12
|
autoload :Check, 'rack/attack/check'
|
11
13
|
autoload :Throttle, 'rack/attack/throttle'
|
12
14
|
autoload :Safelist, 'rack/attack/safelist'
|
@@ -18,7 +20,6 @@ class Rack::Attack
|
|
18
20
|
autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy'
|
19
21
|
autoload :Fail2Ban, 'rack/attack/fail2ban'
|
20
22
|
autoload :Allow2Ban, 'rack/attack/allow2ban'
|
21
|
-
autoload :Request, 'rack/attack/request'
|
22
23
|
|
23
24
|
class << self
|
24
25
|
|
@@ -37,6 +38,18 @@ class Rack::Attack
|
|
37
38
|
self.blocklists[name] = Blocklist.new(name, block)
|
38
39
|
end
|
39
40
|
|
41
|
+
def blocklist_ip(ip)
|
42
|
+
@ip_blocklists ||= []
|
43
|
+
ip_blocklist_proc = lambda { |request| IPAddr.new(ip).include?(IPAddr.new(request.ip)) }
|
44
|
+
@ip_blocklists << Blocklist.new(nil, ip_blocklist_proc)
|
45
|
+
end
|
46
|
+
|
47
|
+
def safelist_ip(ip)
|
48
|
+
@ip_safelists ||= []
|
49
|
+
ip_safelist_proc = lambda { |request| IPAddr.new(ip).include?(IPAddr.new(request.ip)) }
|
50
|
+
@ip_safelists << Safelist.new(nil, ip_safelist_proc)
|
51
|
+
end
|
52
|
+
|
40
53
|
def blacklist(name, &block)
|
41
54
|
warn "[DEPRECATION] 'Rack::Attack.blacklist' is deprecated. Please use 'blocklist' instead."
|
42
55
|
blocklist(name, &block)
|
@@ -66,9 +79,8 @@ class Rack::Attack
|
|
66
79
|
end
|
67
80
|
|
68
81
|
def safelisted?(req)
|
69
|
-
|
70
|
-
safelist
|
71
|
-
end
|
82
|
+
ip_safelists.any? { |safelist| safelist.match?(req) } ||
|
83
|
+
safelists.any? { |_name, safelist| safelist.match?(req) }
|
72
84
|
end
|
73
85
|
|
74
86
|
def whitelisted?(req)
|
@@ -77,9 +89,8 @@ class Rack::Attack
|
|
77
89
|
end
|
78
90
|
|
79
91
|
def blocklisted?(req)
|
80
|
-
|
81
|
-
blocklist
|
82
|
-
end
|
92
|
+
ip_blocklists.any? { |blocklist| blocklist.match?(req) } ||
|
93
|
+
blocklists.any? { |_name, blocklist| blocklist.match?(req) }
|
83
94
|
end
|
84
95
|
|
85
96
|
def blacklisted?(req)
|
@@ -109,6 +120,8 @@ class Rack::Attack
|
|
109
120
|
|
110
121
|
def clear!
|
111
122
|
@safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {}
|
123
|
+
@ip_blocklists = []
|
124
|
+
@ip_safelists = []
|
112
125
|
end
|
113
126
|
|
114
127
|
def blacklisted_response=(res)
|
@@ -121,6 +134,15 @@ class Rack::Attack
|
|
121
134
|
blocklisted_response
|
122
135
|
end
|
123
136
|
|
137
|
+
private
|
138
|
+
|
139
|
+
def ip_blocklists
|
140
|
+
@ip_blocklists ||= []
|
141
|
+
end
|
142
|
+
|
143
|
+
def ip_safelists
|
144
|
+
@ip_safelists ||= []
|
145
|
+
end
|
124
146
|
end
|
125
147
|
|
126
148
|
# Set defaults
|