diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 62de9c454c..559a117ab6 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -6,4 +6,13 @@ # Enabled by default in production # Can be deactivated with 'ENABLE_RACK_ATTACK=0' -DecidimApp::RackAttack.apply_configuration if DecidimApp::RackAttack.rack_enabled? +DecidimApp::RackAttack.deactivate_decidim_throttling! + +if DecidimApp::RackAttack.rack_enabled? + DecidimApp::RackAttack.enable_rack_attack! + DecidimApp::RackAttack.apply_configuration +else + DecidimApp::RackAttack.disable_rack_attack! +end + +DecidimApp::RackAttack.info! diff --git a/lib/decidim_app/rack_attack.rb b/lib/decidim_app/rack_attack.rb index da81cd82fa..fe60de11c1 100644 --- a/lib/decidim_app/rack_attack.rb +++ b/lib/decidim_app/rack_attack.rb @@ -4,21 +4,39 @@ module DecidimApp module RackAttack def self.rack_enabled? setting = Rails.application.secrets.dig(:decidim, :rack_attack, :enabled) - return setting == "1" if setting.present? + return setting.to_s == "1" if setting.present? Rails.env.production? end - def self.apply_configuration + def self.info! + Rails.logger.info("Rack::Attack is enabled: #{Rack::Attack.enabled}") + Rails.logger.info("Rack::Attack Fail2ban is enabled: #{DecidimApp::RackAttack::Fail2ban.enabled?}") + Rack::Attack.throttles.keys.each do |throttle| + Rails.logger.info("Rack::Attack throttling registered: #{throttle}") + end + end + + def self.enable_rack_attack! + Rails.logger.info("Rack::Attack is now enabled") Rack::Attack.enabled = true + end + def self.disable_rack_attack! + Rails.logger.info("Rack::Attack is now disabled") + Rack::Attack.enabled = false + end + + def self.deactivate_decidim_throttling! # Remove the original throttle from decidim-core # see https://github.com/decidim/decidim/blob/release/0.26-stable/decidim-core/config/initializers/rack_attack.rb#L19 DecidimApp::RackAttack::Throttling.deactivate_decidim_throttling! do Rails.logger.info("Deactivating 'requests by ip' from Decidim Core") Rack::Attack.throttles.delete("requests by ip") end + end + def self.apply_configuration Rack::Attack.throttled_response_retry_after_header = true Rack::Attack.throttled_responder = lambda do |request| diff --git a/spec/lib/decidim_app/rack_attack_spec.rb b/spec/lib/decidim_app/rack_attack_spec.rb index 0aca106378..608a7ce3c2 100644 --- a/spec/lib/decidim_app/rack_attack_spec.rb +++ b/spec/lib/decidim_app/rack_attack_spec.rb @@ -62,40 +62,66 @@ end end - describe "#apply_configuration" do + describe "#enable_rack_attack!" do + before do + described_class.enable_rack_attack! + end + + it "enables Rack::Attack" do + expect(Rack::Attack.enabled).to be_truthy + end + end + + describe "#disable_rack_attack!" do + before do + described_class.disable_rack_attack! + end + + it "enables Rack::Attack" do + expect(Rack::Attack.enabled).to be_falsey + end + end + + describe "#deactivate_decidim_throttling!" do before do - described_class.apply_configuration - Rack::Attack.reset! + described_class.deactivate_decidim_throttling! end + it "deactivates Decidim throttling" do + # Decidim throttling is deactivated by default in rails env test + # https://github.com/decidim/decidim/blob/release/0.27-stable/decidim-core/config/initializers/rack_attack.rb#L19 + expect(Rack::Attack.throttles.keys.join).to include("limit confirmations attempts per code") + end + end + + describe "#apply_configuration" do describe "Throttling" do let(:headers) { { "REMOTE_ADDR" => "1.2.3.4", "decidim.current_organization" => organization } } + let(:rack_max_requests) { 15 } - it "successful for 100 requests, then blocks the user" do - 100.times do - get decidim.root_path, params: {}, headers: headers - expect(response).to have_http_status(:ok) - end - - get decidim.root_path, params: {}, headers: headers - expect(response).to have_http_status(:too_many_requests) - expect(response.body).to include("Your connection has been slowed because server received too many requests.") + before do + allow(Rails.application.secrets).to receive(:dig).with(any_args).and_call_original + allow(Rails.application.secrets).to receive(:dig).with(:decidim, :rack_attack, :throttle, :max_requests).and_return(rack_max_requests) + described_class.apply_configuration + Rack::Attack.reset! + described_class.enable_rack_attack! + end - travel_to(1.minute.from_now) do - get decidim.root_path, params: {}, headers: headers - expect(response).to have_http_status(:ok) - end + it "defines default period and max_requests" do + expect(DecidimApp::RackAttack::Throttling.max_requests).to eq(rack_max_requests) + expect(DecidimApp::RackAttack::Throttling.period).to eq(60) end - it "successful for 99 requests" do - 99.times do + it "successful for 15 requests, then blocks the user" do + rack_max_requests.times do get decidim.root_path, params: {}, headers: headers expect(response).to have_http_status(:ok) + expect(response.body).not_to include("Your connection has been slowed because server received too many requests.") end get decidim.root_path, params: {}, headers: headers - expect(response.body).not_to include("Your connection has been slowed because server received too many requests.") - expect(response).not_to have_http_status(:too_many_requests) + expect(response).to have_http_status(:too_many_requests) + expect(response.body).to include("Your connection has been slowed because server received too many requests.") travel_to(1.minute.from_now) do get decidim.root_path, params: {}, headers: headers @@ -107,6 +133,12 @@ describe "Fail2Ban" do let(:headers) { { "REMOTE_ADDR" => "1.2.3.4", "decidim.current_organization" => organization } } + before do + described_class.apply_configuration + Rack::Attack.reset! + described_class.enable_rack_attack! + end + %w(/etc/passwd /wp-admin/index.php /wp-login/index.php SELECT CONCAT /.git/config).each do |path| it "blocks user for specific request : '#{path}'" do get "#{decidim.root_path}#{path}", params: {}, headers: headers diff --git a/spec/system/confirmation_spec.rb b/spec/system/confirmation_spec.rb index 3542bbbadb..b1294c5d46 100644 --- a/spec/system/confirmation_spec.rb +++ b/spec/system/confirmation_spec.rb @@ -76,6 +76,7 @@ def code_for(str) before do allow(Rails).to receive(:cache).and_return(memory_store) + DecidimApp::RackAttack.enable_rack_attack! DecidimApp::RackAttack.apply_configuration Rack::Attack.reset! @@ -88,7 +89,7 @@ def code_for(str) end after do - Rack::Attack.enabled = false + DecidimApp::RackAttack.disable_rack_attack! end it "throttles after 5 attempts per minute" do