Initial working version

This commit is contained in:
Aaron Suggs 2012-07-27 17:40:11 -04:00
parent e166e87fb9
commit dc2e402812
9 changed files with 66 additions and 39 deletions

View file

@ -1 +1,6 @@
# 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)

View file

@ -15,17 +15,11 @@ Rack::Attack.throttle "logins/email", :limit => 2, :period => 60 do |req|
req.params['email'] unless req.params['email'].blank?
end
# Block cloud IPs from accessing PATH regexp
Rack::Attack.block "bad_ips from logging in" do |req|
# Blacklist cloud IPs from accessing PATH regexp
Rack::Attack.blacklist "bad_ips from logging in" do |req|
req.path =~ /^login/ && bad_ips.include?(req.ip)
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
Rack::Attack.whitelist 'internal user agent' do |req|
req.user_agent =~ 'InternalUserAgent'

View file

@ -2,17 +2,19 @@ require 'rack'
module Rack::Attack
require 'rack/attack/cache'
require 'rack/attack/throttle'
require 'rack/attack/whitelist'
require 'rack/attack/blacklist'
class << self
attr_reader :cache, :notifier
def whitelist(name, &block)
(@whitelists ||= {})[name] = block
(@whitelists ||= {})[name] = Whitelist.new(name, block)
end
def block(name, &block)
(@blocks ||= {})[name] = block
def blacklist(name, &block)
(@blacklists ||= {})[name] = Blacklist.new(name, block)
end
def throttle(name, options, &block)
@ -20,7 +22,7 @@ module Rack::Attack
end
def whitelists; @whitelists ||= {}; end
def blocks; @blocks ||= {}; end
def blacklists; @blacklists ||= {}; end
def throttles; @throttles ||= {}; end
def new(app)
@ -38,8 +40,8 @@ module Rack::Attack
return @app.call(env)
end
if blocked?(req)
blocked_response
if blacklisted?(req)
blacklisted_response
elsif throttled?(req)
throttled_response
else
@ -48,18 +50,14 @@ module Rack::Attack
end
def whitelisted?(req)
whitelists.any? do |name, block|
block[req].tap{ |match|
instrument(:type => :whitelist, :name => name, :request => req) if match
}
whitelists.any? do |name, whitelist|
whitelist[req]
end
end
def blocked?(req)
blocks.any? do |name, block|
block[req].tap { |match|
instrument(:type => :block, :name => name, :request => req) if match
}
def blacklisted?(req)
blacklists.any? do |name, blacklist|
blacklist[req]
end
end
@ -73,7 +71,7 @@ module Rack::Attack
notifier.instrument('rack.attack', payload) if notifier
end
def blocked_response
def blacklisted_response
[503, {}, ['Blocked']]
end
@ -82,7 +80,7 @@ module Rack::Attack
end
def clear!
@whitelists, @blocks, @throttles = {}, {}, {}
@whitelists, @blacklists, @throttles = {}, {}, {}
end
end

View 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
View 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

View file

@ -16,7 +16,7 @@ module Rack
end
def [](req)
discriminator = @block[req]
discriminator = block[req]
return false unless discriminator
key = "#{name}:#{discriminator}"

View file

@ -1,5 +1,5 @@
module Rack
module Attack
VERSION = '0.0.2'
VERSION = '0.0.3'
end
end

View file

@ -1,12 +1,10 @@
require_relative 'check'
module Rack
module Attack
class Whitelist
attr_reader :name, :block
def initialize(name, &block)
@name, @block = name, block
end
def [](req)
class Whitelist < Check
def initialize(name, block)
super
@type = :whitelist
end
end

View file

@ -22,15 +22,15 @@ describe 'Rack::Attack' do
allow_ok_requests
describe 'with a block' do
describe 'with a blacklist' do
before do
@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
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
last_response.status.must_equal 503
end
@ -44,7 +44,7 @@ describe 'Rack::Attack' do
end
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
last_response.status.must_equal 200
end