Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion config/initializers/rack_attack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,14 @@

# 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!
24 changes: 21 additions & 3 deletions lib/decidim_app/rack_attack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
# see https://github.com/decidim/decidim/blob/release/0.27-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|
Expand Down
82 changes: 62 additions & 20 deletions spec/lib/decidim_app/rack_attack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,40 +62,76 @@
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.apply_configuration
Rack::Attack.reset!
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
# 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
# so we add some keys to test the method
Rack::Attack.throttle("limit confirmations attempts per code", limit: 5, period: 60.seconds) # added by module friendly_signup in decidim_app
Rack::Attack.throttle("requests by ip", limit: 5, period: 60.seconds)
described_class.deactivate_decidim_throttling!
end

after do
# delete the added keys
Rack::Attack.throttles.delete("requests by ip")
Rack::Attack.throttles.delete("limit confirmations attempts per code")
end

it "deactivates Decidim throttling" do
expect(Rack::Attack.throttles.keys.join).to include("limit confirmations attempts per code")
expect(Rack::Attack.throttles.keys.join).not_to include("requests by ip")
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
Expand All @@ -107,6 +143,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
Expand Down
Loading