rack-dedos 0.1.0 → 0.2.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 +4 -4
- checksums.yaml.gz.sig +2 -2
- data/CHANGELOG.md +14 -0
- data/README.md +22 -5
- data/lib/rack/dedos/filters/base.rb +66 -0
- data/lib/rack/dedos/filters/country.rb +48 -0
- data/lib/rack/dedos/filters/user_agent.rb +50 -0
- data/lib/rack/dedos/version.rb +1 -1
- data/lib/rack/dedos.rb +11 -7
- data.tar.gz.sig +0 -0
- metadata +16 -16
- metadata.gz.sig +0 -0
- data/lib/rack/dedos/base.rb +0 -40
- data/lib/rack/dedos/country.rb +0 -46
- data/lib/rack/dedos/user_agent.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44d58ef342f579aa4245e7e3f8dd41b505f576331c6bb5f65d4c7b6e5e07e74f
|
4
|
+
data.tar.gz: 6b63cf01106e9f09d2fea0daee7691b0d263fcdc48e4c7b44bcf04144c4468b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb872c5c6d1a339bbf62220fdd1c179349ed28d266d291fdca4013527039e5290539a5e60adee88c570e87de0b04a93ac808a99103f5faeda4d5616acaf88995
|
7
|
+
data.tar.gz: eedf962195f3eacea34330bfc4b158263ee15d57684a18f6bd48aa26a78e49e92fa73fcb96ec043e18e1b96dfcfbd586c5117cd805b8c3b96e2f3b52d14982e0
|
checksums.yaml.gz.sig
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
^细�-*��T:F�����t�+
|
2
|
+
A+�����
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,20 @@
|
|
2
2
|
|
3
3
|
Nothing so far
|
4
4
|
|
5
|
+
## 0.2.1
|
6
|
+
|
7
|
+
#### Fixes
|
8
|
+
|
9
|
+
* Fix paths on conditional requires
|
10
|
+
* Renew certificate
|
11
|
+
|
12
|
+
## 0.2.0
|
13
|
+
|
14
|
+
#### Changes
|
15
|
+
|
16
|
+
* Determine real client IP
|
17
|
+
* Drop autoload and put filters in proper namespace
|
18
|
+
|
5
19
|
## 0.1.0
|
6
20
|
|
7
21
|
#### Initial implementation
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
[](https://rubygems.org/gems/rack-dedos)
|
2
2
|
[](https://github.com/svoop/rack-dedos/actions?workflow=Test)
|
3
3
|
[](https://codeclimate.com/github/svoop/rack-dedos/)
|
4
|
-
[](https://github.com/sponsors/svoop)
|
5
5
|
|
6
6
|
<img src="https://github.com/svoop/rack-dedos/raw/main/doc/chop-chop.png" alt="chop-chop" align="right">
|
7
7
|
|
@@ -15,6 +15,8 @@ The filters have been proven to work against certain DoS attacks, however, they
|
|
15
15
|
* [API](https://www.rubydoc.info/gems/rack-dedos)
|
16
16
|
* Author: [Sven Schwyn - Bitcetera](https://bitcetera.com)
|
17
17
|
|
18
|
+
Thank you for supporting free and open-source software by sponsoring on [GitHub](https://github.com/sponsors/svoop) or on [Donorbox](https://donorbox.com/bitcetera). Any gesture is appreciated, from a single Euro for a ☕️ cup of coffee to 🍹 early retirement.
|
19
|
+
|
18
20
|
## Install
|
19
21
|
|
20
22
|
### Security
|
@@ -83,11 +85,11 @@ use Rack::Dedos,
|
|
83
85
|
|
84
86
|
## Filters
|
85
87
|
|
86
|
-
By default, all filters described below are applied. You can exclude
|
88
|
+
By default, all filters described below are applied. You can exclude certain filters:
|
87
89
|
|
88
90
|
```ruby
|
89
91
|
use Rack::Dedos,
|
90
|
-
|
92
|
+
except: [:user_agent]
|
91
93
|
```
|
92
94
|
|
93
95
|
To only apply one specific filter, use the corresponding class as shown below.
|
@@ -95,7 +97,7 @@ To only apply one specific filter, use the corresponding class as shown below.
|
|
95
97
|
### User Agent Filter
|
96
98
|
|
97
99
|
```ruby
|
98
|
-
use Rack::Dedos::UserAgent,
|
100
|
+
use Rack::Dedos::Filters::UserAgent,
|
99
101
|
cache_url: 'redis://redis.example.com:6379/12', # db 12 on default port
|
100
102
|
cache_key_prefix: 'dedos', # key prefix for shared caches (default: nil)
|
101
103
|
cache_period: 1800 # seconds (default: 900)
|
@@ -111,7 +113,7 @@ The following cache backends are supported:
|
|
111
113
|
### Country Filter
|
112
114
|
|
113
115
|
```ruby
|
114
|
-
use Rack::Dedos::Country,
|
116
|
+
use Rack::Dedos::Filters::Country,
|
115
117
|
maxmind_db_file: '/var/db/maxmind/GeoLite2-Country.mmdb',
|
116
118
|
allowed_countries: %i(AT CH DE),
|
117
119
|
denied_countries: %i(RU)
|
@@ -137,6 +139,21 @@ tar -xz -C /tmp -f /tmp/geoipupdate.tgz
|
|
137
139
|
/tmp/geoipupdate_${version}_${arch}/geoipupdate -f "${conf}" -d "${dir}"
|
138
140
|
```
|
139
141
|
|
142
|
+
## Real Client IP
|
143
|
+
|
144
|
+
A word on how the real client IP is determined. Both Rack 2 and Rack 3 (up to 3.0.7 at the time of writing) may populate the request `ip` incorrectly. Here's what a minimalistic Rack app deloyed to Render (behind Cloudflare) reports:
|
145
|
+
|
146
|
+
> request.ip = 172.71.135.17<br>
|
147
|
+
> request.forwarded_for = ["81.XXX.XXX.XXX", "172.71.135.17", "10.201.229.136"]
|
148
|
+
|
149
|
+
Obviously, the reported IP 172.71.135.17 is not the real client IP, the correct one is the (redacted) 81.XXX.XXX.XXX.
|
150
|
+
|
151
|
+
Due to this flaw, Rack::Dedos determines the real client IP as follows in order of priority:
|
152
|
+
|
153
|
+
1. [`Cf-Connecting-Ip` header](https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/#cf-connecting-ip)
|
154
|
+
2. First entry of the [`X-Forwarded-For` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)
|
155
|
+
3. [`ip` reported by Rack](https://github.com/rack/rack/blob/main/lib/rack/request.rb)
|
156
|
+
|
140
157
|
## Development
|
141
158
|
|
142
159
|
For all required test fixtures to be present, you have to check out the repo
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Dedos
|
5
|
+
module Filters
|
6
|
+
class Base
|
7
|
+
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
status: 403,
|
10
|
+
text: 'Forbidden (Temporarily Blocked by Rules)'
|
11
|
+
}
|
12
|
+
|
13
|
+
attr_reader :app
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
# @param app [#call]
|
17
|
+
# @param options [Hash{Symbol => Object}]
|
18
|
+
def initialize(app, options = {})
|
19
|
+
@app = app
|
20
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
request = Rack::Request.new(env)
|
25
|
+
ip = real_ip(request)
|
26
|
+
if allowed?(request, ip)
|
27
|
+
app.call(env)
|
28
|
+
else
|
29
|
+
warn("rack-dedos: request from #{ip} blocked by #{self.class} `#{@country_code.inspect}'")
|
30
|
+
[options[:status], { 'Content-Type' => 'text/plain' }, [options[:text]]]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def config
|
37
|
+
Rack::Dedos.config
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the real IP of the client
|
41
|
+
#
|
42
|
+
# If containers and/or proxies such as Cloudflare are in the mix, the
|
43
|
+
# client IP reported by Rack may be wrong. Therefore, we determine the
|
44
|
+
# real client IP using the following priorities:
|
45
|
+
#
|
46
|
+
# 1. Cf-Connecting-Ip header
|
47
|
+
# 2. X-Forwarded-For header (also remove port number)
|
48
|
+
# 3. IP reported by Rack
|
49
|
+
#
|
50
|
+
# @param request [Rack::Request]
|
51
|
+
# @return [String] real client IP
|
52
|
+
def real_ip(request)
|
53
|
+
case
|
54
|
+
when ip = request.get_header('HTTP_CF_CONNECTING_IP')
|
55
|
+
ip
|
56
|
+
when forwarded_for = request.forwarded_for
|
57
|
+
forwarded_for.split(/\s*,\s*/).first&.sub(/:\d+$/, '')
|
58
|
+
else
|
59
|
+
request.ip
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'maxmind/db'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Dedos
|
7
|
+
module Filters
|
8
|
+
class Country < Base
|
9
|
+
|
10
|
+
# @option options [String] :maxmind_db_file MaxMind database file
|
11
|
+
# @option options [Symbol, Array<Symbol>] :allowed_countries ISO 3166-1 alpha 2
|
12
|
+
# @option options [Symbol, Array<Symbol>] :denied_countries ISO 3166-1 alpha 2
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
@maxmind_db_file = options[:maxmind_db_file] or fail "MaxMind database file not set"
|
16
|
+
@allowed = case
|
17
|
+
when @countries = options[:allowed_countries] then true
|
18
|
+
when @countries = options[:denied_countries] then false
|
19
|
+
else fail "neither allowed nor denied countries set"
|
20
|
+
end
|
21
|
+
maxmind_db # hit once to fail on errors at boot
|
22
|
+
end
|
23
|
+
|
24
|
+
def allowed?(request, ip)
|
25
|
+
if country = maxmind_db.get(ip)
|
26
|
+
country_code = country.dig('country', 'iso_code').to_sym
|
27
|
+
@countries.include?(country_code) ? @allowed : !@allowed
|
28
|
+
else # not found in database
|
29
|
+
true
|
30
|
+
end
|
31
|
+
rescue => error
|
32
|
+
warn("rack-dedos: request from #{ip} allowed due to error: #{error.message}")
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def maxmind_db
|
39
|
+
config[:maxmind_db] ||= MaxMind::DB.new(
|
40
|
+
@maxmind_db_file,
|
41
|
+
mode: MaxMind::DB::MODE_FILE
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Dedos
|
5
|
+
module Filters
|
6
|
+
class UserAgent < Base
|
7
|
+
|
8
|
+
# @option options [String] :cache_url URL of the cache backend
|
9
|
+
# @option options [Integer] :cache_period how long to retain cached IP
|
10
|
+
# addresses in seconds (default: 900)
|
11
|
+
def initialize(*)
|
12
|
+
super
|
13
|
+
@cache_url = options[:cache_url] or fail "cache URL not set"
|
14
|
+
@cache_period = options[:cache_period] || 900
|
15
|
+
@cache_key_prefix = options[:cache_key_prefix]
|
16
|
+
cache # hit once to fail on errors at boot
|
17
|
+
end
|
18
|
+
|
19
|
+
def allowed?(request, ip)
|
20
|
+
case cache.get(ip)
|
21
|
+
when nil # first contact
|
22
|
+
cache.set(ip, request.user_agent)
|
23
|
+
true
|
24
|
+
when 'BLOCKED' # already blocked
|
25
|
+
false
|
26
|
+
when request.user_agent # user agent hasn't changed
|
27
|
+
true
|
28
|
+
else # user agent has changed
|
29
|
+
cache.set(ip, 'BLOCKED')
|
30
|
+
false
|
31
|
+
end
|
32
|
+
rescue => error
|
33
|
+
warn("rack-dedos: request from #{ip} allowed due to error: #{error.message}")
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def cache
|
40
|
+
config[:cache] ||= Cache.new(
|
41
|
+
url: @cache_url,
|
42
|
+
expires_in: @cache_period,
|
43
|
+
key_prefix: @cache_key_prefix
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/rack/dedos/version.rb
CHANGED
data/lib/rack/dedos.rb
CHANGED
@@ -6,11 +6,9 @@ require_relative 'dedos/version'
|
|
6
6
|
|
7
7
|
module Rack
|
8
8
|
module Dedos
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
autoload :UserAgent, lib_dir + '/dedos/user_agent'
|
13
|
-
autoload :Country, lib_dir + '/dedos/country'
|
9
|
+
|
10
|
+
require_relative 'dedos/cache'
|
11
|
+
require_relative 'dedos/filters/base'
|
14
12
|
|
15
13
|
class << self
|
16
14
|
def config
|
@@ -21,8 +19,14 @@ module Rack
|
|
21
19
|
except = Array options[:except]
|
22
20
|
|
23
21
|
Rack::Builder.new do
|
24
|
-
|
25
|
-
|
22
|
+
unless except.include? :user_agent
|
23
|
+
require_relative 'dedos/filters/user_agent'
|
24
|
+
use(::Rack::Dedos::Filters::UserAgent, options)
|
25
|
+
end
|
26
|
+
unless except.include? :country
|
27
|
+
require_relative 'dedos/filters/country'
|
28
|
+
use(::Rack::Dedos::Filters::Country, options)
|
29
|
+
end
|
26
30
|
run app
|
27
31
|
end.to_app
|
28
32
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-dedos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sven Schwyn
|
@@ -11,8 +11,8 @@ cert_chain:
|
|
11
11
|
- |
|
12
12
|
-----BEGIN CERTIFICATE-----
|
13
13
|
MIIDODCCAiCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhydWJ5
|
14
|
-
|
15
|
-
|
14
|
+
L0RDPWJpdGNldGVyYS9EQz1jb20wHhcNMjQxMTIwMjExMDIwWhcNMjUxMTIwMjEx
|
15
|
+
MDIwWjAjMSEwHwYDVQQDDBhydWJ5L0RDPWJpdGNldGVyYS9EQz1jb20wggEiMA0G
|
16
16
|
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcLg+IHjXYaUlTSU7R235lQKD8ZhEe
|
17
17
|
KMhoGlSUonZ/zo1OT3KXcqTCP1iMX743xYs6upEGALCWWwq+nxvlDdnWRjF3AAv7
|
18
18
|
ikC+Z2BEowjyeCCT/0gvn4ohKcR0JOzzRaIlFUVInlGSAHx2QHZ2N8ntf54lu7nd
|
@@ -21,15 +21,15 @@ cert_chain:
|
|
21
21
|
PVa0i729A4IhroNnFNmw4wOC93ARNbM1+LW36PLMmKjKudf5Exg8VmDVAgMBAAGj
|
22
22
|
dzB1MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSfK8MtR62mQ6oN
|
23
23
|
yoX/VKJzFjLSVDAdBgNVHREEFjAUgRJydWJ5QGJpdGNldGVyYS5jb20wHQYDVR0S
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
BBYwFIEScnVieUBiaXRjZXRlcmEuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQDSeB1x
|
25
|
+
8QK8F/ML37isgvwGiQxovDUqu6Sq14cQ1qE9y5prUBmL2AsDuCBpXXctcvamFqNC
|
26
|
+
PgfJtj7ZZcXmY0SfKCog7T1btkr6zYxPXpxwUqB45n0I6v5qc0UCNvMEfBzxlak5
|
27
|
+
VW7UMNlKD9qukeN55hxuLF2F/sLldMcHUo/ATgdV4zk1t3sK6A9+02wz5K5qfWdM
|
28
|
+
Mi+XWXmGd57uojk3RcIXNwBRRP4DTKcKgVXhuyHb7q1vjTXrS6bw1Ortu0KmWOIk
|
29
|
+
jTyRsT1gymASS2KHe+BaCTwD74GqO8q4woYLZgXnJ/PvgcFgY2FEi2Kn/sXLp4JE
|
30
|
+
boIgxQCMT+nxBHCD
|
31
31
|
-----END CERTIFICATE-----
|
32
|
-
date:
|
32
|
+
date: 2024-11-20 00:00:00.000000000 Z
|
33
33
|
dependencies:
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: rack
|
@@ -116,7 +116,7 @@ dependencies:
|
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '0'
|
118
118
|
- !ruby/object:Gem::Dependency
|
119
|
-
name: minitest-
|
119
|
+
name: minitest-flash
|
120
120
|
requirement: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - ">="
|
@@ -207,10 +207,10 @@ files:
|
|
207
207
|
- LICENSE.txt
|
208
208
|
- README.md
|
209
209
|
- lib/rack/dedos.rb
|
210
|
-
- lib/rack/dedos/base.rb
|
211
210
|
- lib/rack/dedos/cache.rb
|
212
|
-
- lib/rack/dedos/
|
213
|
-
- lib/rack/dedos/
|
211
|
+
- lib/rack/dedos/filters/base.rb
|
212
|
+
- lib/rack/dedos/filters/country.rb
|
213
|
+
- lib/rack/dedos/filters/user_agent.rb
|
214
214
|
- lib/rack/dedos/version.rb
|
215
215
|
homepage: https://github.com/svoop/rack-dedos
|
216
216
|
licenses:
|
@@ -243,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
243
243
|
- !ruby/object:Gem::Version
|
244
244
|
version: '0'
|
245
245
|
requirements: []
|
246
|
-
rubygems_version: 3.
|
246
|
+
rubygems_version: 3.5.23
|
247
247
|
signing_key:
|
248
248
|
specification_version: 4
|
249
249
|
summary: Radical filters to block denial-of-service (DoS) requests.
|
metadata.gz.sig
CHANGED
Binary file
|
data/lib/rack/dedos/base.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rack
|
4
|
-
module Dedos
|
5
|
-
class Base
|
6
|
-
|
7
|
-
DEFAULT_OPTIONS = {
|
8
|
-
status: 403,
|
9
|
-
text: 'Forbidden (Temporarily Blocked by Rules)'
|
10
|
-
}
|
11
|
-
|
12
|
-
attr_reader :app
|
13
|
-
attr_reader :options
|
14
|
-
|
15
|
-
# @param app [#call]
|
16
|
-
# @param options [Hash{Symbol => Object}]
|
17
|
-
def initialize(app, options = {})
|
18
|
-
@app = app
|
19
|
-
@options = DEFAULT_OPTIONS.merge(options)
|
20
|
-
end
|
21
|
-
|
22
|
-
def call(env)
|
23
|
-
request = Rack::Request.new(env)
|
24
|
-
if allowed?(request)
|
25
|
-
app.call(env)
|
26
|
-
else
|
27
|
-
warn("rack-dedos: request from #{request.ip} blocked by #{self.class}")
|
28
|
-
[options[:status], { 'Content-Type' => 'text/plain' }, [options[:text]]]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def config
|
35
|
-
Rack::Dedos.config
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
data/lib/rack/dedos/country.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'maxmind/db'
|
4
|
-
|
5
|
-
module Rack
|
6
|
-
module Dedos
|
7
|
-
class Country < Base
|
8
|
-
|
9
|
-
# @option options [String] :maxmind_db_file MaxMind database file
|
10
|
-
# @option options [Symbol, Array<Symbol>] :allowed_countries ISO 3166-1 alpha 2
|
11
|
-
# @option options [Symbol, Array<Symbol>] :denied_countries ISO 3166-1 alpha 2
|
12
|
-
def initialize(*)
|
13
|
-
super
|
14
|
-
@maxmind_db_file = options[:maxmind_db_file] or fail "MaxMind database file not set"
|
15
|
-
@allowed = case
|
16
|
-
when @countries = options[:allowed_countries] then true
|
17
|
-
when @countries = options[:denied_countries] then false
|
18
|
-
else fail "neither allowed nor denied countries set"
|
19
|
-
end
|
20
|
-
maxmind_db # hit once to fail on errors at boot
|
21
|
-
end
|
22
|
-
|
23
|
-
def allowed?(request)
|
24
|
-
if country = maxmind_db.get(request.ip)
|
25
|
-
country_code = country.dig('country', 'iso_code').to_sym
|
26
|
-
@countries.include?(country_code) ? @allowed : !@allowed
|
27
|
-
else # not found in database
|
28
|
-
true
|
29
|
-
end
|
30
|
-
rescue => error
|
31
|
-
warn("rack-dedos: request from #{request.ip} allowed due to error: #{error.message}")
|
32
|
-
true
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def maxmind_db
|
38
|
-
config[:maxmind_db] ||= MaxMind::DB.new(
|
39
|
-
@maxmind_db_file,
|
40
|
-
mode: MaxMind::DB::MODE_FILE
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rack
|
4
|
-
module Dedos
|
5
|
-
class UserAgent < Base
|
6
|
-
|
7
|
-
# @option options [String] :cache_url URL of the cache backend
|
8
|
-
# @option options [Integer] :cache_period how long to retain cached IP
|
9
|
-
# addresses in seconds (default: 900)
|
10
|
-
def initialize(*)
|
11
|
-
super
|
12
|
-
@cache_url = options[:cache_url] or fail "cache URL not set"
|
13
|
-
@cache_period = options[:cache_period] || 900
|
14
|
-
@cache_key_prefix = options[:cache_key_prefix]
|
15
|
-
cache # hit once to fail on errors at boot
|
16
|
-
end
|
17
|
-
|
18
|
-
def allowed?(request)
|
19
|
-
case cache.get(request.ip)
|
20
|
-
when nil # first contact
|
21
|
-
cache.set(request.ip, request.user_agent)
|
22
|
-
true
|
23
|
-
when 'BLOCKED' # already blocked
|
24
|
-
false
|
25
|
-
when request.user_agent # user agent hasn't changed
|
26
|
-
true
|
27
|
-
else # user agent has changed
|
28
|
-
cache.set(request.ip, 'BLOCKED')
|
29
|
-
false
|
30
|
-
end
|
31
|
-
rescue => error
|
32
|
-
warn("rack-dedos: request from #{request.ip} allowed due to error: #{error.message}")
|
33
|
-
true
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def cache
|
39
|
-
config[:cache] ||= Cache.new(
|
40
|
-
url: @cache_url,
|
41
|
-
expires_in: @cache_period,
|
42
|
-
key_prefix: @cache_key_prefix
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|