debian-mirror-gitlab/spec/support/shared_examples/requests/rack_attack_shared_examples.rb

387 lines
12 KiB
Ruby
Raw Normal View History

2019-12-04 20:38:33 +05:30
# frozen_string_literal: true
#
# Requires let variables:
# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths"
2019-12-21 20:55:43 +05:30
# * request_method
# * request_args
# * other_user_request_args
2019-12-04 20:38:33 +05:30
# * requests_per_period
# * period_in_seconds
# * period
2020-03-13 15:44:24 +05:30
RSpec.shared_examples 'rate-limited token-authenticated requests' do
2019-12-21 20:55:43 +05:30
let(:throttle_types) do
{
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
"throttle_authenticated_api" => "throttle_authenticated_api",
"throttle_authenticated_web" => "throttle_authenticated_web"
}
end
2019-12-04 20:38:33 +05:30
before do
# Set low limits
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
end
2021-02-22 17:27:13 +05:30
after do
stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', nil)
Gitlab::RackAttack.configure_user_allowlist
end
2019-12-04 20:38:33 +05:30
context 'when the throttle is enabled' do
before do
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
stub_application_setting(settings_to_set)
end
it 'rejects requests over the rate limit' do
2021-02-22 17:27:13 +05:30
expect(Gitlab::Instrumentation::Throttle).not_to receive(:safelist=)
2019-12-04 20:38:33 +05:30
# At first, allow requests under the rate limit.
requests_per_period.times do
2019-12-21 20:55:43 +05:30
make_request(request_args)
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
# the last straw
2019-12-21 20:55:43 +05:30
expect_rejection { make_request(request_args) }
2019-12-04 20:38:33 +05:30
end
2021-02-22 17:27:13 +05:30
it 'does not reject requests if the user is in the allowlist' do
stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s)
Gitlab::RackAttack.configure_user_allowlist
expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once)
(requests_per_period + 1).times do
make_request(request_args)
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
end
2019-12-04 20:38:33 +05:30
it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do
2019-12-21 20:55:43 +05:30
make_request(request_args)
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
2019-12-21 20:55:43 +05:30
expect_rejection { make_request(request_args) }
2019-12-04 20:38:33 +05:30
2021-01-03 14:25:43 +05:30
travel_to(period.from_now) do
2019-12-04 20:38:33 +05:30
requests_per_period.times do
2019-12-21 20:55:43 +05:30
make_request(request_args)
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
2019-12-21 20:55:43 +05:30
expect_rejection { make_request(request_args) }
2019-12-04 20:38:33 +05:30
end
end
it 'counts requests from different users separately, even from the same IP' do
requests_per_period.times do
2019-12-21 20:55:43 +05:30
make_request(request_args)
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
# would be over the limit if this wasn't a different user
2019-12-21 20:55:43 +05:30
make_request(other_user_request_args)
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
it 'counts all requests from the same user, even via different IPs' do
requests_per_period.times do
2019-12-21 20:55:43 +05:30
make_request(request_args)
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
2019-12-26 22:10:19 +05:30
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4')
2019-12-04 20:38:33 +05:30
2019-12-21 20:55:43 +05:30
expect_rejection { make_request(request_args) }
2019-12-04 20:38:33 +05:30
end
it 'logs RackAttack info into structured logs' do
2021-01-29 00:20:46 +05:30
control_count = 0
requests_per_period.times do |i|
if i == 0
control_count = ActiveRecord::QueryRecorder.new { make_request(request_args) }.count
else
make_request(request_args)
end
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
arguments = {
message: 'Rack_Attack',
env: :throttle,
remote_ip: '127.0.0.1',
2019-12-21 20:55:43 +05:30
request_method: request_method,
path: request_args.first,
2019-12-04 20:38:33 +05:30
user_id: user.id,
2021-01-29 00:20:46 +05:30
'meta.user' => user.username,
matched: throttle_types[throttle_setting_prefix]
2019-12-04 20:38:33 +05:30
}
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
2021-01-29 00:20:46 +05:30
expect_rejection do
expect { make_request(request_args) }.not_to exceed_query_limit(control_count)
end
2019-12-04 20:38:33 +05:30
end
2021-02-22 17:27:13 +05:30
it_behaves_like 'tracking when dry-run mode is set' do
let(:throttle_name) { throttle_types[throttle_setting_prefix] }
def do_request
make_request(request_args)
end
end
2019-12-04 20:38:33 +05:30
end
context 'when the throttle is disabled' do
before do
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
stub_application_setting(settings_to_set)
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
2019-12-21 20:55:43 +05:30
make_request(request_args)
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
end
end
2019-12-21 20:55:43 +05:30
def make_request(args)
2021-03-08 18:12:59 +05:30
path, options = args
2019-12-21 20:55:43 +05:30
if request_method == 'POST'
2021-03-08 18:12:59 +05:30
post(path, **options)
2019-12-21 20:55:43 +05:30
else
2021-03-08 18:12:59 +05:30
get(path, **options)
2019-12-21 20:55:43 +05:30
end
end
2019-12-04 20:38:33 +05:30
end
# Requires let variables:
# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths"
# * user
# * url_that_requires_authentication
2019-12-21 20:55:43 +05:30
# * request_method
2019-12-04 20:38:33 +05:30
# * requests_per_period
# * period_in_seconds
# * period
2020-03-13 15:44:24 +05:30
RSpec.shared_examples 'rate-limited web authenticated requests' do
2019-12-21 20:55:43 +05:30
let(:throttle_types) do
{
"throttle_protected_paths" => "throttle_authenticated_protected_paths_web",
"throttle_authenticated_web" => "throttle_authenticated_web"
}
end
2019-12-04 20:38:33 +05:30
before do
login_as(user)
# Set low limits
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
end
2021-02-22 17:27:13 +05:30
after do
stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', nil)
Gitlab::RackAttack.configure_user_allowlist
end
2019-12-04 20:38:33 +05:30
context 'when the throttle is enabled' do
before do
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
stub_application_setting(settings_to_set)
end
it 'rejects requests over the rate limit' do
2021-02-22 17:27:13 +05:30
expect(Gitlab::Instrumentation::Throttle).not_to receive(:safelist=)
2019-12-04 20:38:33 +05:30
# At first, allow requests under the rate limit.
requests_per_period.times do
2019-12-21 20:55:43 +05:30
request_authenticated_web_url
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
# the last straw
2019-12-21 20:55:43 +05:30
expect_rejection { request_authenticated_web_url }
2019-12-04 20:38:33 +05:30
end
2021-02-22 17:27:13 +05:30
it 'does not reject requests if the user is in the allowlist' do
stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s)
Gitlab::RackAttack.configure_user_allowlist
expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once)
(requests_per_period + 1).times do
request_authenticated_web_url
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
end
2019-12-04 20:38:33 +05:30
it 'allows requests after throttling and then waiting for the next period' do
requests_per_period.times do
2019-12-21 20:55:43 +05:30
request_authenticated_web_url
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
2019-12-21 20:55:43 +05:30
expect_rejection { request_authenticated_web_url }
2019-12-04 20:38:33 +05:30
2021-01-03 14:25:43 +05:30
travel_to(period.from_now) do
2019-12-04 20:38:33 +05:30
requests_per_period.times do
2019-12-21 20:55:43 +05:30
request_authenticated_web_url
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
2019-12-21 20:55:43 +05:30
expect_rejection { request_authenticated_web_url }
2019-12-04 20:38:33 +05:30
end
end
it 'counts requests from different users separately, even from the same IP' do
requests_per_period.times do
2019-12-21 20:55:43 +05:30
request_authenticated_web_url
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
# would be over the limit if this wasn't a different user
login_as(create(:user))
2019-12-21 20:55:43 +05:30
request_authenticated_web_url
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
it 'counts all requests from the same user, even via different IPs' do
requests_per_period.times do
2019-12-21 20:55:43 +05:30
request_authenticated_web_url
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
2019-12-26 22:10:19 +05:30
expect_any_instance_of(Rack::Attack::Request).to receive(:ip).at_least(:once).and_return('1.2.3.4')
2019-12-04 20:38:33 +05:30
2019-12-21 20:55:43 +05:30
expect_rejection { request_authenticated_web_url }
2019-12-04 20:38:33 +05:30
end
it 'logs RackAttack info into structured logs' do
2021-01-29 00:20:46 +05:30
control_count = 0
requests_per_period.times do |i|
if i == 0
control_count = ActiveRecord::QueryRecorder.new { request_authenticated_web_url }.count
else
request_authenticated_web_url
end
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
arguments = {
message: 'Rack_Attack',
env: :throttle,
remote_ip: '127.0.0.1',
2019-12-21 20:55:43 +05:30
request_method: request_method,
path: url_that_requires_authentication,
2019-12-04 20:38:33 +05:30
user_id: user.id,
2021-01-29 00:20:46 +05:30
'meta.user' => user.username,
matched: throttle_types[throttle_setting_prefix]
2019-12-04 20:38:33 +05:30
}
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
2021-01-29 00:20:46 +05:30
expect { request_authenticated_web_url }.not_to exceed_query_limit(control_count)
2019-12-04 20:38:33 +05:30
end
2021-02-22 17:27:13 +05:30
it_behaves_like 'tracking when dry-run mode is set' do
let(:throttle_name) { throttle_types[throttle_setting_prefix] }
def do_request
request_authenticated_web_url
end
end
2019-12-04 20:38:33 +05:30
end
context 'when the throttle is disabled' do
before do
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
stub_application_setting(settings_to_set)
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
2019-12-21 20:55:43 +05:30
request_authenticated_web_url
2020-03-13 15:44:24 +05:30
expect(response).not_to have_gitlab_http_status(:too_many_requests)
2019-12-04 20:38:33 +05:30
end
end
end
2019-12-21 20:55:43 +05:30
def request_authenticated_web_url
if request_method == 'POST'
post url_that_requires_authentication
else
get url_that_requires_authentication
end
end
2019-12-04 20:38:33 +05:30
end
2021-02-22 17:27:13 +05:30
# Requires:
# - #do_request - This needs to be a method so the result isn't memoized
# - throttle_name
RSpec.shared_examples 'tracking when dry-run mode is set' do
let(:dry_run_config) { '*' }
# we can't use `around` here, because stub_env isn't supported outside of the
# example itself
before do
stub_env('GITLAB_THROTTLE_DRY_RUN', dry_run_config)
reset_rack_attack
end
after do
stub_env('GITLAB_THROTTLE_DRY_RUN', '')
reset_rack_attack
end
def reset_rack_attack
Rack::Attack.reset!
Rack::Attack.clear_configuration
Gitlab::RackAttack.configure(Rack::Attack)
end
it 'does not throttle the requests when `*` is configured' do
(1 + requests_per_period).times do
do_request
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
end
it 'logs RackAttack info into structured logs' do
arguments = a_hash_including({
message: 'Rack_Attack',
env: :track,
remote_ip: '127.0.0.1',
matched: throttle_name
})
expect(Gitlab::AuthLogger).to receive(:error).with(arguments)
(1 + requests_per_period).times do
do_request
end
end
context 'when configured with the the throttled name in a list' do
let(:dry_run_config) do
"throttle_list, #{throttle_name}, other_throttle"
end
it 'does not throttle' do
(1 + requests_per_period).times do
do_request
expect(response).not_to have_gitlab_http_status(:too_many_requests)
end
end
end
end