Merge pull request #4033 from pengfeidong/lock_account_by_brute_force_attack_use_rack_attack
add Gem rack_attack for prevent attack brute-force
This commit is contained in:
commit
3a1f17cce8
8 changed files with 108 additions and 7 deletions
1
Gemfile
1
Gemfile
|
@ -48,6 +48,7 @@ gem 'prawn_rails'
|
|||
gem 'premailer-rails'
|
||||
gem 'puma' # Use Puma as the app server
|
||||
gem 'pundit'
|
||||
gem 'rack-attack'
|
||||
gem 'rack-mini-profiler'
|
||||
gem 'rails'
|
||||
gem 'rails-i18n' # Locales par défaut
|
||||
|
|
|
@ -434,6 +434,8 @@ GEM
|
|||
pundit (2.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (2.0.6)
|
||||
rack-attack (6.0.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-mini-profiler (1.0.1)
|
||||
rack (>= 1.2.0)
|
||||
rack-oauth2 (1.9.3)
|
||||
|
@ -752,6 +754,7 @@ DEPENDENCIES
|
|||
pry-byebug
|
||||
puma
|
||||
pundit
|
||||
rack-attack
|
||||
rack-mini-profiler
|
||||
rails
|
||||
rails-controller-testing
|
||||
|
|
|
@ -3,13 +3,7 @@ class IPService
|
|||
def ip_trusted?(ip)
|
||||
ip_address = parse_address(ip)
|
||||
|
||||
if ip_address.nil?
|
||||
false
|
||||
elsif trusted_networks.present?
|
||||
trusted_networks.any? { |network| network.include?(ip_address) }
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -41,5 +41,6 @@ module TPS
|
|||
end
|
||||
|
||||
config.ds_weekly_overview = ENV['APP_NAME'] == 'tps'
|
||||
config.middleware.use Rack::Attack
|
||||
end
|
||||
end
|
||||
|
|
27
config/initializers/rack_attack.rb
Normal file
27
config/initializers/rack_attack.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class Rack::Attack
|
||||
throttle('/users/sign_in/ip', limit: 5, period: 20.seconds) do |req|
|
||||
if req.path == '/users/sign_in' && req.post? && rack_attack_enabled?
|
||||
req.remote_ip
|
||||
end
|
||||
end
|
||||
|
||||
throttle('stats/ip', limit: 5, period: 20.seconds) do |req|
|
||||
if req.path == '/stats' && rack_attack_enabled?
|
||||
req.remote_ip
|
||||
end
|
||||
end
|
||||
|
||||
throttle('contact/ip', limit: 5, period: 20.seconds) do |req|
|
||||
if req.path == '/contact' && req.post? && rack_attack_enabled?
|
||||
req.remote_ip
|
||||
end
|
||||
end
|
||||
|
||||
Rack::Attack.safelist('allow from localhost') do |req|
|
||||
IPService.ip_trusted?(req.remote_ip)
|
||||
end
|
||||
|
||||
def self.rack_attack_enabled?
|
||||
ENV['RACK_ATTACK_ENABLE'] == 'true'
|
||||
end
|
||||
end
|
7
config/initializers/rack_attack_request.rb
Normal file
7
config/initializers/rack_attack_request.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class Rack::Attack
|
||||
class Request < ::Rack::Request
|
||||
def remote_ip
|
||||
@remote_ip ||= (env['action_dispatch.remote_ip'] || ip).to_s
|
||||
end
|
||||
end
|
||||
end
|
56
spec/middlewares/rack_attack_spec.rb
Normal file
56
spec/middlewares/rack_attack_spec.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
require "rails_helper"
|
||||
|
||||
describe Rack::Attack, type: :request do
|
||||
let(:limit) { 5 }
|
||||
let(:period) { 20 }
|
||||
let(:ip) { "1.2.3.4" }
|
||||
|
||||
before(:each) do
|
||||
ENV['RACK_ATTACK_ENABLE'] = 'true'
|
||||
setup_rack_attack_cache_store
|
||||
avoid_test_overlaps_in_cache
|
||||
end
|
||||
|
||||
after do
|
||||
ENV['RACK_ATTACK_ENABLE'] = 'false'
|
||||
end
|
||||
|
||||
def setup_rack_attack_cache_store
|
||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
||||
end
|
||||
|
||||
def avoid_test_overlaps_in_cache
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
context '/users/sign_in' do
|
||||
before do
|
||||
limit.times do
|
||||
Rack::Attack.cache.count("/users/sign_in/ip:#{ip}", period)
|
||||
end
|
||||
end
|
||||
|
||||
subject do
|
||||
post "/users/sign_in", headers: { 'X-Forwarded-For': ip }
|
||||
end
|
||||
|
||||
it "throttle excessive requests by IP address" do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(:too_many_requests)
|
||||
end
|
||||
|
||||
context 'when the ip is whitelisted' do
|
||||
before do
|
||||
allow(IPService).to receive(:ip_trusted?).and_return(true)
|
||||
allow_any_instance_of(Users::SessionsController).to receive(:create).and_return(:ok)
|
||||
end
|
||||
|
||||
it "respects the whitelist" do
|
||||
subject
|
||||
|
||||
expect(response).not_to have_http_status(:too_many_requests)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,6 +28,18 @@ describe IPService do
|
|||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
context 'when the trusted network is not defined' do
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
context 'when the trusted network is malformed' do
|
||||
before do
|
||||
ENV['TRUSTED_NETWORKS'] = 'bad network'
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a trusted network is defined' do
|
||||
|
|
Loading…
Reference in a new issue