mirror of
https://github.com/samsonjs/rack-attack.git
synced 2026-04-27 15:07:41 +00:00
Whitelists support
This commit is contained in:
parent
885b19c633
commit
9284a08cc3
5 changed files with 130 additions and 27 deletions
|
|
@ -1,29 +1,83 @@
|
||||||
require 'rack'
|
require 'rack'
|
||||||
module Rack
|
module Rack::Attack
|
||||||
class Attack
|
require 'rack/attack/cache'
|
||||||
class << self
|
|
||||||
|
|
||||||
attr_reader :blocks, :throttles, :whitelists
|
class << self
|
||||||
|
|
||||||
def block(name, &block)
|
attr_reader :cache, :notifier
|
||||||
(@blocks ||= {})[name] = block
|
|
||||||
end
|
|
||||||
|
|
||||||
def throttle
|
|
||||||
end
|
|
||||||
|
|
||||||
def whitelist
|
|
||||||
end
|
|
||||||
|
|
||||||
|
def whitelist(name, &block)
|
||||||
|
(@whitelists ||= {})[name] = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(app)
|
def block(name, &block)
|
||||||
|
(@blocks ||= {})[name] = block
|
||||||
|
end
|
||||||
|
|
||||||
|
def throttle
|
||||||
|
end
|
||||||
|
|
||||||
|
def whitelists; @whitelists ||= {}; end
|
||||||
|
def blocks; @blocks ||= {}; end
|
||||||
|
def throttles; @throttles ||= {}; end
|
||||||
|
|
||||||
|
def new(app)
|
||||||
|
@cache = Cache.new
|
||||||
|
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
||||||
@app = app
|
@app = app
|
||||||
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
puts 'Rack attack!'
|
req = Rack::Request.new(env)
|
||||||
@app.call(env)
|
|
||||||
|
if whitelisted?(req)
|
||||||
|
return @app.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
if blocked?(req)
|
||||||
|
blocked_response
|
||||||
|
elsif throttled?(req)
|
||||||
|
else
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def whitelisted?(req)
|
||||||
|
whitelists.any? do |name, block|
|
||||||
|
block[req].tap{ |match|
|
||||||
|
instrument(:type => :whitelist, :name => name, :request => req) if match
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocked?(req)
|
||||||
|
blocks.any? do |name, block|
|
||||||
|
block[req].tap { |match|
|
||||||
|
instrument(:type => :block, :name => name, :request => req) if match
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def throttled?(req)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def instrument(payload)
|
||||||
|
notifier.instrument('rack.attack', payload) if notifier
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocked_response
|
||||||
|
[503, {}, ['Blocked']]
|
||||||
|
end
|
||||||
|
|
||||||
|
def throttled_response
|
||||||
|
[503, {}, ['Throttled']]
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear!
|
||||||
|
@whitelists, @blocks, @throttles = {}, {}, {}
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
21
lib/rack/attack/cache.rb
Normal file
21
lib/rack/attack/cache.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
module Rack
|
||||||
|
module Attack
|
||||||
|
class Cache
|
||||||
|
|
||||||
|
attr_accessor :store
|
||||||
|
def initialize
|
||||||
|
@store = ::Rails.cache if defined?(::Rails.cache)
|
||||||
|
end
|
||||||
|
|
||||||
|
def count(key, expires_in)
|
||||||
|
result = store.increment(1, :expires_in => expires_in)
|
||||||
|
# NB: Some stores return nil when incrementing uninitialized values
|
||||||
|
if result.nil?
|
||||||
|
store.write(key, 1, :expires_in => expires_in)
|
||||||
|
end
|
||||||
|
result || 1
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module Rack
|
module Rack
|
||||||
class Attack
|
module Attack
|
||||||
VERSION = '0.0.1'
|
VERSION = '0.0.1'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ Gem::Specification.new do |s|
|
||||||
s.add_dependency 'rack'
|
s.add_dependency 'rack'
|
||||||
s.add_development_dependency 'minitest'
|
s.add_development_dependency 'minitest'
|
||||||
s.add_development_dependency 'rack-test'
|
s.add_development_dependency 'rack-test'
|
||||||
|
s.add_development_dependency 'activesupport', '>= 3.0.0'
|
||||||
s.add_development_dependency 'debugger', '~> 1.1.3'
|
s.add_development_dependency 'debugger', '~> 1.1.3'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,6 @@ require_relative 'spec_helper'
|
||||||
describe 'Rack::Attack' do
|
describe 'Rack::Attack' do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
|
||||||
before do
|
|
||||||
Rack::Attack.block("ip 1.2.3.4") {|req| req.ip == '1.2.3.4' }
|
|
||||||
end
|
|
||||||
|
|
||||||
def app
|
def app
|
||||||
Rack::Builder.new {
|
Rack::Builder.new {
|
||||||
use Rack::Attack
|
use Rack::Attack
|
||||||
|
|
@ -14,14 +10,45 @@ describe 'Rack::Attack' do
|
||||||
}.to_app
|
}.to_app
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has a block' do
|
def self.allow_ok_requests
|
||||||
Rack::Attack.blocks.class.must_equal Hash
|
it "must allow ok requests" do
|
||||||
|
get '/', {}, 'REMOTE_ADDR' => '127.0.0.1'
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
last_response.body.must_equal 'Hello World'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "says hello" do
|
after { Rack::Attack.clear! }
|
||||||
get '/'
|
|
||||||
last_response.status.must_equal 200
|
allow_ok_requests
|
||||||
last_response.body.must_equal 'Hello World'
|
|
||||||
|
describe 'with a block' do
|
||||||
|
before do
|
||||||
|
@bad_ip = '1.2.3.4'
|
||||||
|
Rack::Attack.block("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
|
||||||
|
end
|
||||||
|
|
||||||
|
it('has a block') { Rack::Attack.blocks.key?("ip #{@bad_ip}") }
|
||||||
|
|
||||||
|
it "should block bad requests" do
|
||||||
|
get '/', {}, 'REMOTE_ADDR' => @bad_ip
|
||||||
|
last_response.status.must_equal 503
|
||||||
|
end
|
||||||
|
|
||||||
|
allow_ok_requests
|
||||||
|
|
||||||
|
describe "and with a whitelist" do
|
||||||
|
before do
|
||||||
|
@good_ua = 'GoodUA'
|
||||||
|
Rack::Attack.whitelist("good ua") {|req| req.user_agent == @good_ua }
|
||||||
|
end
|
||||||
|
|
||||||
|
it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") }
|
||||||
|
it "should allow whitelists before blocks" do
|
||||||
|
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
|
||||||
|
last_response.status.must_equal 200
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue