rack_attack_admin 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +11 -0
- data/Readme.md +93 -10
- data/Screenshot-not_find_allow2ban_rule.png +0 -0
- data/Screenshot.png +0 -0
- data/app/controllers/rack_attack_admin/application_controller.rb +15 -1
- data/app/controllers/rack_attack_admin/rack_attack_controller.rb +6 -2
- data/app/views/rack_attack_admin/banned_ips/_banned_ip.html.haml +4 -2
- data/app/views/rack_attack_admin/rack_attack/current_request.html.haml +10 -0
- data/app/views/rack_attack_admin/rack_attack/index.html.haml +44 -50
- data/lib/rack/attack_extensions.rb +60 -6
- data/lib/rack_attack_admin/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56573b4e1953af195a40c77e4ff7a43dc0092ab28752985ff3f21d92ae4b27ce
|
4
|
+
data.tar.gz: 2c224cb40e27bbabac363ba02ed2b37259595d1c4342be2ab2884167d85cb5c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6fd53b0f1e13e09370292b1e3121c1135096d0f4e4f211d11be87dcf588fa4e3a0fadcf8b5f2fab4f666d1600ebf35566022f3d5430833da801d76cde1816ae0
|
7
|
+
data.tar.gz: ca371334c028fcf6e1eb632ca0f8371e58e9cd02c3cf1126e522a430babc30cc953c5e67606d67ee0f73b25f167e7887d005f351cfdc892f9cf41bf8249524b0
|
data/Changelog.md
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.1.2 (2019-06-17)
|
4
|
+
|
5
|
+
- Various fixes and improvements to the dashboard templates
|
6
|
+
- Make it work also when Rack::Attack.cache.store is set to an instance of ActiveSupport::Cache::RedisCacheStore (from rails)
|
7
|
+
- Let current_request respond to .html as well as .json
|
8
|
+
|
9
|
+
## 0.1.1 (2019-04-02)
|
10
|
+
|
11
|
+
Initial release
|
data/Readme.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
# Rack::Attack Admin Dashboard
|
1
|
+
# [Rack::Attack](https://github.com/kickstarter/rack-attack) Admin Dashboard [![Gem Version](https://badge.fury.io/rb/rack_attack_admin.svg)](https://badge.fury.io/rb/rack_attack_admin)
|
2
|
+
|
3
|
+
Lets you see the current state of all throttles and bans. Delete existing keys/bans. Manually add bans.
|
4
|
+
|
5
|
+
![Screenshot](Screenshot.png)
|
2
6
|
|
3
7
|
Inspired by: https://www.backerkit.com/blog/building-a-rackattack-dashboard/
|
4
8
|
|
@@ -10,11 +14,6 @@ Add this line to your application's `Gemfile`:
|
|
10
14
|
gem 'rack_attack_admin'
|
11
15
|
```
|
12
16
|
|
13
|
-
And then execute:
|
14
|
-
|
15
|
-
$ bundle
|
16
|
-
|
17
|
-
|
18
17
|
Add this line to your application's `config/routes.rb`:
|
19
18
|
|
20
19
|
```ruby
|
@@ -23,13 +22,97 @@ mount RackAttackAdmin::Engine, at: '/admin/rack_attack'
|
|
23
22
|
|
24
23
|
## Usage
|
25
24
|
|
26
|
-
|
25
|
+
Go to http://localhost:3000/admin/rack_attack in your browser!
|
26
|
+
|
27
|
+
You can also use the provided read-only command-line command, `rake rack_attack_admin:watch` instead of the web interface:
|
28
|
+
```
|
29
|
+
+-------------------------------+--------------------+
|
30
|
+
| Banned IP | Previous (5 s ago) |
|
31
|
+
+-------------------------------+--------------------+
|
32
|
+
| allow2ban:ban:login-127.0.0.1 | |
|
33
|
+
+-------------------------------+--------------------+
|
34
|
+
+----------------------------------------------------------------------------------+---------------+--------------------+
|
35
|
+
| Key | Current Count | Previous (5 s ago) |
|
36
|
+
+----------------------------------------------------------------------------------+---------------+--------------------+
|
37
|
+
| ('allow2ban:count'):login-127.0.0.1 | 2 | 1 |
|
38
|
+
| throttle('logins/ip'):127.0.0.1 | 1 | |
|
39
|
+
| throttle('logins/email'):me@example.com | 1 | |
|
40
|
+
| throttle('req/ip'):127.0.0.1 | 2 | 1 |
|
41
|
+
+----------------------------------------------------------------------------------+---------------+--------------------+
|
42
|
+
```
|
43
|
+
|
27
44
|
|
28
|
-
##
|
45
|
+
## Fail2Ban/Allow2Ban
|
29
46
|
|
30
|
-
|
47
|
+
In order to allow your Fail2Ban/Allow2Ban rules to be introspected by this app, you must define them
|
48
|
+
slightly differently than the upstream [Rack::Attack
|
49
|
+
documentation](https://github.com/kickstarter/rack-attack#fail2ban) tells you
|
50
|
+
to define them:
|
51
|
+
|
52
|
+
If you have an Allow2Ban filter in a blocklist like this:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
blocklist('login:allow2ban') do |req|
|
56
|
+
Rack::Attack::Allow2Ban.filter("login-#{req.ip}", maxretry: 5, findtime: 1.minute, bantime: 10.minutes) do
|
57
|
+
# The count for the IP is incremented if this return value is truthy.
|
58
|
+
is_login.(req)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
, you can change it to this equivalent definition:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
blocklist('login:allow2ban') do |req|
|
67
|
+
def_allow2ban('login', limit: 5, period: 1.minute, bantime: 10.minutes)
|
68
|
+
allow2ban( 'login', req.ip) do
|
69
|
+
is_login.(req)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
31
73
|
|
32
|
-
|
74
|
+
`def_fail2ban`/`def_allow2ban` save your configuration in a hash (by name, _without_ discriminator in it), much the same as
|
75
|
+
`throttle` does.
|
76
|
+
|
77
|
+
`fail2ban`/`allow2ban` methods are simply wrappers for `Rack::Attack::Allow2Ban.filter` that look up and use the options from the definition (that matches the given name).
|
78
|
+
|
79
|
+
This has the following advantages:
|
80
|
+
- It actually stores your Fail2Ban rule so that we can look it up later
|
81
|
+
- It provides an API and options that are more similar to and consistent with regular `throttle`
|
82
|
+
commands:
|
83
|
+
```ruby
|
84
|
+
# Compare:
|
85
|
+
def_allow2ban('login', limit: 5, period: 1.minute, bantime: …)
|
86
|
+
allow2ban('login', discriminator) do
|
87
|
+
# Return truthy value to increment counter
|
88
|
+
end
|
89
|
+
# allow2ban returns true if counter reaches limit
|
90
|
+
|
91
|
+
throttle('logins/email', limit: 5, period: 1.minute) do |req|
|
92
|
+
discriminator.(req)
|
93
|
+
end
|
94
|
+
```
|
95
|
+
- You can use the familiar `limit` and `period` options instead of `maxretry` and `findtime`
|
96
|
+
options, respectively.
|
97
|
+
- You don't have to interpolate the discriminator into a string like you do with the standard
|
98
|
+
`Fail2Ban.filter` syntax.
|
99
|
+
|
100
|
+
|
101
|
+
This is completely optional. If you choose not to define them this way, it will still show your
|
102
|
+
fail2ban counter keys and value; it just won't be able to find the matching rule, and therefore
|
103
|
+
won't be able to show what the limit or time bucket is for that counter key. So instead of showing
|
104
|
+
the limit for `allow2ban('login')` like it shows in the screenshot above, it will fall back to
|
105
|
+
just showing what little it can show about that key:
|
106
|
+
|
107
|
+
![Screenshot](Screenshot-not_find_allow2ban_rule.png)
|
108
|
+
|
109
|
+
## Redis cache store compatibility
|
110
|
+
|
111
|
+
This has been tested with `Rack::Attack.cache.store` set to an instance of:
|
112
|
+
- `Redis::Store` from the fantastic [redis-store](https://github.com/redis-store/redis-store) gem.
|
113
|
+
(Which is used by the `ActiveSupport::Cache::RedisStore` (from redis-activesupport/redis-rails
|
114
|
+
gems))
|
115
|
+
- `ActiveSupport::Cache::RedisCacheStore` (provided by Rails 5.2+)
|
33
116
|
|
34
117
|
## Contributing
|
35
118
|
|
Binary file
|
data/Screenshot.png
ADDED
Binary file
|
@@ -13,7 +13,21 @@ module RackAttackAdmin
|
|
13
13
|
|
14
14
|
helper_method \
|
15
15
|
def is_redis?
|
16
|
-
Rack::Attack.cache.respond.store.
|
16
|
+
Rack::Attack.cache.respond.store.to_s.match?(/Redis/)
|
17
|
+
end
|
18
|
+
|
19
|
+
helper_method \
|
20
|
+
def redis
|
21
|
+
return unless is_redis?
|
22
|
+
store = Rack::Attack.cache.store
|
23
|
+
store = store.redis if store.respond_to?(:redis)
|
24
|
+
store = store.data if store.respond_to?(:data)
|
25
|
+
store
|
26
|
+
end
|
27
|
+
|
28
|
+
helper_method \
|
29
|
+
def has_ttl?
|
30
|
+
!!redis
|
17
31
|
end
|
18
32
|
|
19
33
|
helper_method \
|
@@ -5,14 +5,18 @@ module RackAttackAdmin
|
|
5
5
|
# Web version of lib/tasks/rack_attack_admin_tasks.rake
|
6
6
|
def index
|
7
7
|
@default_banned_ip = Rack::Attack::BannedIp.new(bantime: '60 m')
|
8
|
-
@banned_ip_keys = Rack::Attack::Fail2Ban.banned_ip_keys
|
9
8
|
@counters_h = Rack::Attack.counters_h.
|
10
9
|
without(*Rack::Attack::BannedIps.keys)
|
11
10
|
render
|
12
11
|
end
|
13
12
|
|
14
13
|
def current_request
|
15
|
-
|
14
|
+
respond_to do |format|
|
15
|
+
format.json do
|
16
|
+
render json: current_request_rack_attack_stats
|
17
|
+
end
|
18
|
+
format.html
|
19
|
+
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
- expires_column = local_assigns.key?(:expires_column) ? local_assigns[:expires_column] : has_ttl?
|
1
2
|
%tr
|
2
3
|
%td= key
|
3
4
|
|
@@ -6,9 +7,10 @@
|
|
6
7
|
= ip
|
7
8
|
(#{link_to 'Look up', "http://ip-lookup.net/?ip=#{ip}", target:'_blank'})
|
8
9
|
|
9
|
-
- if
|
10
|
+
- if expires_column
|
10
11
|
%td
|
11
|
-
-
|
12
|
+
- if has_ttl?
|
13
|
+
- interval = redis.ttl("#{Rack::Attack.prefix_with_namespace}:#{key}")
|
12
14
|
- if interval
|
13
15
|
=# distance_of_time_in_words(interval)
|
14
16
|
in #{ActiveSupport::Duration.build(interval)&.human_str}
|
@@ -4,14 +4,14 @@
|
|
4
4
|
%tr
|
5
5
|
%th Key
|
6
6
|
%th Banned IP
|
7
|
-
|
8
|
-
%th Expires
|
7
|
+
%th Expires
|
9
8
|
%th Actions
|
10
9
|
|
11
10
|
= render partial: 'rack_attack_admin/banned_ips/banned_ip',
|
12
11
|
collection: Rack::Attack::BannedIps.keys, as: 'key',
|
13
12
|
locals: { _:nil,
|
14
13
|
full_key_prefix: Rack::Attack::BannedIps.full_key_prefix,
|
14
|
+
expires_column: true,
|
15
15
|
}
|
16
16
|
|
17
17
|
= form_for @default_banned_ip, url: [rack_attack_admin, :banned_ips] do |f|
|
@@ -27,18 +27,17 @@
|
|
27
27
|
%tr
|
28
28
|
%th Key
|
29
29
|
%th Banned IP
|
30
|
-
- if
|
30
|
+
- if has_ttl?
|
31
31
|
%th Expires
|
32
32
|
%th Actions
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
collection: @banned_ip_keys, as: 'key',
|
34
|
+
= render partial: 'rack_attack_admin/banned_ips/banned_ip',
|
35
|
+
collection: Rack::Attack::Fail2Ban.banned_ip_keys, as: 'key',
|
37
36
|
locals: { _:nil,
|
38
37
|
full_key_prefix: Rack::Attack::Fail2Ban.full_key_prefix,
|
39
38
|
}
|
40
39
|
|
41
|
-
%h1 Throttle/Fail2Ban
|
40
|
+
%h1 Throttle/Fail2Ban Counters
|
42
41
|
%table.table.table-striped.mb-0
|
43
42
|
%thead
|
44
43
|
%tr
|
@@ -52,11 +51,25 @@
|
|
52
51
|
(Time bucket)
|
53
52
|
%th Actions
|
54
53
|
- @counters_h.each do |key, value|
|
54
|
+
:ruby
|
55
|
+
parsed = Rack::Attack.parse_key(key)
|
56
|
+
value = value.to_i
|
57
|
+
name = Rack::Attack.humanize_key(key).sub(":#{parsed[:discriminator]}", '')
|
58
|
+
|
59
|
+
# We can get expires_in from redis or directly from the matched throttle rule
|
60
|
+
interval =
|
61
|
+
if has_ttl?
|
62
|
+
redis.ttl("#{Rack::Attack.prefix_with_namespace}:#{key}")
|
63
|
+
elsif parsed and time_range = parsed[:time_range]
|
64
|
+
(time_range.end - Time.now)
|
65
|
+
end
|
66
|
+
if parsed
|
67
|
+
time_range = parsed[:time_range]
|
68
|
+
end
|
69
|
+
if time_range
|
70
|
+
human_duration = time_range.duration&.human_str
|
71
|
+
end
|
55
72
|
%tr
|
56
|
-
:ruby
|
57
|
-
parsed = Rack::Attack.parse_key(key)
|
58
|
-
value = value.to_i
|
59
|
-
name = Rack::Attack.humanize_key(key).sub(":#{parsed[:discriminator]}", '')
|
60
73
|
%td
|
61
74
|
= name
|
62
75
|
-# if parsed and rule = parsed[:rule]
|
@@ -65,52 +78,33 @@
|
|
65
78
|
%td= parsed[:discriminator]
|
66
79
|
|
67
80
|
- limit = parsed && (rule = parsed[:rule]) && rule.limit.to_i
|
68
|
-
- over_limit = value >= limit
|
81
|
+
- over_limit = limit && value >= limit
|
69
82
|
%td{class: ('over_limit' if over_limit), style: ('color: red' if over_limit)}
|
70
83
|
= value
|
84
|
+
<br/>
|
71
85
|
- if limit
|
72
|
-
|
86
|
+
Limit:
|
87
|
+
= "#{limit}"
|
88
|
+
= "/#{human_duration}" if human_duration
|
89
|
+
<br/>
|
90
|
+
- if time_range
|
91
|
+
%small
|
92
|
+
(#{(limit / (time_range.duration/60.0)).round(0)}/min)
|
73
93
|
|
74
94
|
%td
|
75
|
-
-# We can get expires_in from redis or directly from the mached throttle rule
|
76
|
-
:ruby
|
77
|
-
interval =
|
78
|
-
if is_redis?
|
79
|
-
Rack::Attack.cache.store.ttl("#{Rack::Attack.cache.prefix}:#{key}")
|
80
|
-
elsif parsed and time_range = parsed[:time_range]
|
81
|
-
(time_range.end - Time.now)
|
82
|
-
end
|
83
95
|
- if interval
|
84
|
-
|
96
|
+
- if interval < 0
|
97
|
+
expired<br/>
|
98
|
+
- elsif interval
|
99
|
+
in #{ActiveSupport::Duration.build(interval)&.human_str}<br/>
|
85
100
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
101
|
+
- if time_range
|
102
|
+
%small
|
103
|
+
(#{ time_range.begin.to_s(:time_with_s)}
|
104
|
+
%span><
|
105
|
+
\-
|
106
|
+
#{time_range.end .to_s(:time_with_s)}
|
107
|
+
\= #{human_duration})
|
93
108
|
|
94
109
|
%td= link_to 'Delete', rack_attack_admin.key_path(key), method: :delete, class: 'btn'
|
95
110
|
.current_time.mb-2 (Current time: #{Time.now.to_s(:time_with_s)})
|
96
|
-
|
97
|
-
%h1 Flags
|
98
|
-
%table.table.table-striped
|
99
|
-
%thead
|
100
|
-
%tr
|
101
|
-
%th Key
|
102
|
-
%th Value
|
103
|
-
%tr
|
104
|
-
%td cookies[:skip_safelist]
|
105
|
-
%td= cookies[:skip_safelist]
|
106
|
-
|
107
|
-
%h1 Current Request
|
108
|
-
%table.table.table-striped
|
109
|
-
%thead
|
110
|
-
%tr
|
111
|
-
%th Key
|
112
|
-
%th Value
|
113
|
-
- current_request_rack_attack_stats.each do |key, value|
|
114
|
-
%tr
|
115
|
-
%td= key
|
116
|
-
%td= value
|
@@ -6,8 +6,65 @@ class Rack::Attack
|
|
6
6
|
class << self
|
7
7
|
extend Memoist
|
8
8
|
|
9
|
+
def all_keys
|
10
|
+
store, namespace = cache_store_and_namespace_to_strip
|
11
|
+
keys = store.keys
|
12
|
+
if namespace
|
13
|
+
keys.map {|key| key.to_s.sub(/^#{namespace}:/, '') }
|
14
|
+
else
|
15
|
+
keys
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# The same as cache.prefix but prefixed with "{namespace}:" if namespace option is set and needs
|
20
|
+
# to be stripped from keys returned from store.keys.
|
21
|
+
# Like cache.prefix, this does not include the trailing ':'.
|
22
|
+
def prefix_with_namespace_to_strip
|
23
|
+
prefix = cache.prefix
|
24
|
+
store, namespace = cache_store_and_namespace_to_strip
|
25
|
+
if namespace
|
26
|
+
prefix = "#{namespace}:#{prefix}"
|
27
|
+
end
|
28
|
+
prefix
|
29
|
+
end
|
30
|
+
|
31
|
+
# The same as cache.prefix but prefixed with "{namespace}:" if namespace option is set.
|
32
|
+
# Needed when passing a key directly to a Redis command, like Redis#ttl, since Redis class
|
33
|
+
# doesn't know about namespacing.
|
34
|
+
def prefix_with_namespace
|
35
|
+
prefix = cache.prefix
|
36
|
+
if namespace = cache_namespace
|
37
|
+
prefix = "#{namespace}:#{prefix}"
|
38
|
+
end
|
39
|
+
prefix
|
40
|
+
end
|
41
|
+
|
42
|
+
def cache_namespace
|
43
|
+
cache.store&.options&.[](:namespace)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns an array of [cache_store, namespace_to_strip]
|
47
|
+
# This will either be [a Redis::Store, nil] or
|
48
|
+
# [a Redis , namespace_to_strip]
|
49
|
+
def cache_store_and_namespace_to_strip
|
50
|
+
store = cache.store
|
51
|
+
# Store can be a ActiveSupport::Cache::RedisCacheStore, a Redis::Store object, or a Redis object.
|
52
|
+
# If it is a ActiveSupport::Cache::RedisCacheStore, then we need to get the redis object in
|
53
|
+
# order to get keys from it.
|
54
|
+
store = store.redis if store.respond_to?(:redis)
|
55
|
+
if store.respond_to?(:data) # Redis::Store already stripped namespaced
|
56
|
+
store = store.data
|
57
|
+
[store, nil]
|
58
|
+
else
|
59
|
+
# Redis object (which is all we have available in the case of a
|
60
|
+
# ActiveSupport::Cache::RedisCacheStore) unfortunately returns keys with namespace prefix in
|
61
|
+
# each key, so we need to strip this out (Redis::Store does this already; see store.data.keys above)
|
62
|
+
[store, cache_namespace]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
9
66
|
def prefixed_keys
|
10
|
-
|
67
|
+
all_keys.grep(/^#{cache.prefix}:/)
|
11
68
|
end
|
12
69
|
|
13
70
|
# AKA unprefixed_keys
|
@@ -56,9 +113,6 @@ class Rack::Attack
|
|
56
113
|
)
|
57
114
|
end
|
58
115
|
|
59
|
-
class ParsedKey
|
60
|
-
end
|
61
|
-
|
62
116
|
# Reverse Cache#key_and_expiry:
|
63
117
|
# … "#{prefix}:#{(@last_epoch_time / period).to_i}:#{unprefixed_key}" …
|
64
118
|
# @return [Hash]
|
@@ -231,7 +285,7 @@ class Rack::Attack
|
|
231
285
|
|
232
286
|
class << self
|
233
287
|
def prefixed_keys
|
234
|
-
|
288
|
+
Rack::Attack.all_keys.grep(/^#{cache.prefix}:(allow|fail)2ban:/)
|
235
289
|
end
|
236
290
|
|
237
291
|
# AKA unprefixed_keys
|
@@ -273,7 +327,7 @@ class Rack::Attack
|
|
273
327
|
class BannedIps
|
274
328
|
class << self
|
275
329
|
def prefixed_keys
|
276
|
-
|
330
|
+
Rack::Attack.all_keys.grep(/^#{full_key_prefix}:/)
|
277
331
|
end
|
278
332
|
|
279
333
|
# Removes only the Rack::Attack.cache.prefix
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack_attack_admin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tyler Rick
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -164,11 +164,14 @@ files:
|
|
164
164
|
- License
|
165
165
|
- Rakefile
|
166
166
|
- Readme.md
|
167
|
+
- Screenshot-not_find_allow2ban_rule.png
|
168
|
+
- Screenshot.png
|
167
169
|
- app/controllers/rack_attack_admin/application_controller.rb
|
168
170
|
- app/controllers/rack_attack_admin/banned_ips_controller.rb
|
169
171
|
- app/controllers/rack_attack_admin/keys_controller.rb
|
170
172
|
- app/controllers/rack_attack_admin/rack_attack_controller.rb
|
171
173
|
- app/views/rack_attack_admin/banned_ips/_banned_ip.html.haml
|
174
|
+
- app/views/rack_attack_admin/rack_attack/current_request.html.haml
|
172
175
|
- app/views/rack_attack_admin/rack_attack/index.html.haml
|
173
176
|
- bin/console
|
174
177
|
- bin/setup
|
@@ -201,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
204
|
- !ruby/object:Gem::Version
|
202
205
|
version: '0'
|
203
206
|
requirements: []
|
204
|
-
rubygems_version: 3.0.
|
207
|
+
rubygems_version: 3.0.3
|
205
208
|
signing_key:
|
206
209
|
specification_version: 4
|
207
210
|
summary: A Rack::Attack admin dashboard
|