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:
LeSim 2019-08-20 13:45:01 +02:00 committed by GitHub
commit 3a1f17cce8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 7 deletions

View file

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

View file

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

View file

@ -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
trusted_networks.any? { |network| network.include?(ip_address) }
end
private

View file

@ -41,5 +41,6 @@ module TPS
end
config.ds_weekly_overview = ENV['APP_NAME'] == 'tps'
config.middleware.use Rack::Attack
end
end

View 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

View 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

View 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

View file

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