mirror of
https://github.com/samsonjs/rack-attack.git
synced 2026-04-25 14:47:43 +00:00
Initial working version
This commit is contained in:
parent
e166e87fb9
commit
dc2e402812
9 changed files with 66 additions and 39 deletions
|
|
@ -1 +1,6 @@
|
||||||
# Rack::Attack - middleware for throttling & blocking abusive clients
|
# Rack::Attack - middleware for throttling & blocking abusive clients
|
||||||
|
|
||||||
|
## Processing order
|
||||||
|
* If any whitelist matches, the request is allowed
|
||||||
|
* If any blacklist matches, the request is blocked (unless a whitelist matched)
|
||||||
|
* If any throttle matches, the request is throttled (unless a whitelist or blacklist matched)
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,11 @@ Rack::Attack.throttle "logins/email", :limit => 2, :period => 60 do |req|
|
||||||
req.params['email'] unless req.params['email'].blank?
|
req.params['email'] unless req.params['email'].blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Blacklist cloud IPs from accessing PATH regexp
|
||||||
# Block cloud IPs from accessing PATH regexp
|
Rack::Attack.blacklist "bad_ips from logging in" do |req|
|
||||||
Rack::Attack.block "bad_ips from logging in" do |req|
|
|
||||||
req.path =~ /^login/ && bad_ips.include?(req.ip)
|
req.path =~ /^login/ && bad_ips.include?(req.ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Throttle login attempts
|
|
||||||
Rack::Attack.throttle "logins/ip", :limit => 2, :period => 1.second do | req|
|
|
||||||
req.ip if req.post? && req.path_info =~ /^login/
|
|
||||||
end
|
|
||||||
|
|
||||||
# Whitelist a User-Agent
|
# Whitelist a User-Agent
|
||||||
Rack::Attack.whitelist 'internal user agent' do |req|
|
Rack::Attack.whitelist 'internal user agent' do |req|
|
||||||
req.user_agent =~ 'InternalUserAgent'
|
req.user_agent =~ 'InternalUserAgent'
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@ require 'rack'
|
||||||
module Rack::Attack
|
module Rack::Attack
|
||||||
require 'rack/attack/cache'
|
require 'rack/attack/cache'
|
||||||
require 'rack/attack/throttle'
|
require 'rack/attack/throttle'
|
||||||
|
require 'rack/attack/whitelist'
|
||||||
|
require 'rack/attack/blacklist'
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
||||||
attr_reader :cache, :notifier
|
attr_reader :cache, :notifier
|
||||||
|
|
||||||
def whitelist(name, &block)
|
def whitelist(name, &block)
|
||||||
(@whitelists ||= {})[name] = block
|
(@whitelists ||= {})[name] = Whitelist.new(name, block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def block(name, &block)
|
def blacklist(name, &block)
|
||||||
(@blocks ||= {})[name] = block
|
(@blacklists ||= {})[name] = Blacklist.new(name, block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def throttle(name, options, &block)
|
def throttle(name, options, &block)
|
||||||
|
|
@ -20,7 +22,7 @@ module Rack::Attack
|
||||||
end
|
end
|
||||||
|
|
||||||
def whitelists; @whitelists ||= {}; end
|
def whitelists; @whitelists ||= {}; end
|
||||||
def blocks; @blocks ||= {}; end
|
def blacklists; @blacklists ||= {}; end
|
||||||
def throttles; @throttles ||= {}; end
|
def throttles; @throttles ||= {}; end
|
||||||
|
|
||||||
def new(app)
|
def new(app)
|
||||||
|
|
@ -38,8 +40,8 @@ module Rack::Attack
|
||||||
return @app.call(env)
|
return @app.call(env)
|
||||||
end
|
end
|
||||||
|
|
||||||
if blocked?(req)
|
if blacklisted?(req)
|
||||||
blocked_response
|
blacklisted_response
|
||||||
elsif throttled?(req)
|
elsif throttled?(req)
|
||||||
throttled_response
|
throttled_response
|
||||||
else
|
else
|
||||||
|
|
@ -48,18 +50,14 @@ module Rack::Attack
|
||||||
end
|
end
|
||||||
|
|
||||||
def whitelisted?(req)
|
def whitelisted?(req)
|
||||||
whitelists.any? do |name, block|
|
whitelists.any? do |name, whitelist|
|
||||||
block[req].tap{ |match|
|
whitelist[req]
|
||||||
instrument(:type => :whitelist, :name => name, :request => req) if match
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocked?(req)
|
def blacklisted?(req)
|
||||||
blocks.any? do |name, block|
|
blacklists.any? do |name, blacklist|
|
||||||
block[req].tap { |match|
|
blacklist[req]
|
||||||
instrument(:type => :block, :name => name, :request => req) if match
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -73,7 +71,7 @@ module Rack::Attack
|
||||||
notifier.instrument('rack.attack', payload) if notifier
|
notifier.instrument('rack.attack', payload) if notifier
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocked_response
|
def blacklisted_response
|
||||||
[503, {}, ['Blocked']]
|
[503, {}, ['Blocked']]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -82,7 +80,7 @@ module Rack::Attack
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear!
|
def clear!
|
||||||
@whitelists, @blocks, @throttles = {}, {}, {}
|
@whitelists, @blacklists, @throttles = {}, {}, {}
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
13
lib/rack/attack/blacklist.rb
Normal file
13
lib/rack/attack/blacklist.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
require_relative 'check'
|
||||||
|
module Rack
|
||||||
|
module Attack
|
||||||
|
class Blacklist < Check
|
||||||
|
def initialize(name, block)
|
||||||
|
super
|
||||||
|
@type = :blacklist
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
19
lib/rack/attack/check.rb
Normal file
19
lib/rack/attack/check.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
module Rack
|
||||||
|
module Attack
|
||||||
|
class Check
|
||||||
|
attr_reader :name, :block, :type
|
||||||
|
def initialize(name, block)
|
||||||
|
@name, @block = name, block
|
||||||
|
@type = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](req)
|
||||||
|
block[req].tap {|match|
|
||||||
|
Rack::Attack.instrument(:type => type, :name => name, :request => req) if match
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ module Rack
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](req)
|
def [](req)
|
||||||
discriminator = @block[req]
|
discriminator = block[req]
|
||||||
return false unless discriminator
|
return false unless discriminator
|
||||||
|
|
||||||
key = "#{name}:#{discriminator}"
|
key = "#{name}:#{discriminator}"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module Rack
|
module Rack
|
||||||
module Attack
|
module Attack
|
||||||
VERSION = '0.0.2'
|
VERSION = '0.0.3'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
|
require_relative 'check'
|
||||||
module Rack
|
module Rack
|
||||||
module Attack
|
module Attack
|
||||||
class Whitelist
|
class Whitelist < Check
|
||||||
attr_reader :name, :block
|
def initialize(name, block)
|
||||||
def initialize(name, &block)
|
super
|
||||||
@name, @block = name, block
|
@type = :whitelist
|
||||||
end
|
|
||||||
|
|
||||||
def [](req)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,15 @@ describe 'Rack::Attack' do
|
||||||
|
|
||||||
allow_ok_requests
|
allow_ok_requests
|
||||||
|
|
||||||
describe 'with a block' do
|
describe 'with a blacklist' do
|
||||||
before do
|
before do
|
||||||
@bad_ip = '1.2.3.4'
|
@bad_ip = '1.2.3.4'
|
||||||
Rack::Attack.block("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
|
Rack::Attack.blacklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
|
||||||
end
|
end
|
||||||
|
|
||||||
it('has a block') { Rack::Attack.blocks.key?("ip #{@bad_ip}") }
|
it('has a blacklist') { Rack::Attack.blacklists.key?("ip #{@bad_ip}") }
|
||||||
|
|
||||||
it "should block bad requests" do
|
it "should blacklist bad requests" do
|
||||||
get '/', {}, 'REMOTE_ADDR' => @bad_ip
|
get '/', {}, 'REMOTE_ADDR' => @bad_ip
|
||||||
last_response.status.must_equal 503
|
last_response.status.must_equal 503
|
||||||
end
|
end
|
||||||
|
|
@ -44,7 +44,7 @@ describe 'Rack::Attack' do
|
||||||
end
|
end
|
||||||
|
|
||||||
it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") }
|
it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") }
|
||||||
it "should allow whitelists before blocks" do
|
it "should allow whitelists before blacklists" do
|
||||||
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
|
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue