diff options
Diffstat (limited to 'spec/support/shared_examples/requests/rack_attack_shared_examples.rb')
-rw-r--r-- | spec/support/shared_examples/requests/rack_attack_shared_examples.rb | 202 |
1 files changed, 198 insertions, 4 deletions
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index 95817624658..2a19ff6f590 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # # Requires let variables: -# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api" +# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs", "throttle_authenticated_files_api" # * request_method # * request_args # * other_user_request_args @@ -14,7 +14,9 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do "throttle_protected_paths" => "throttle_authenticated_protected_paths_api", "throttle_authenticated_api" => "throttle_authenticated_api", "throttle_authenticated_web" => "throttle_authenticated_web", - "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api" + "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api", + "throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs", + "throttle_authenticated_files_api" => "throttle_authenticated_files_api" } end @@ -165,7 +167,7 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do end # Requires let variables: -# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths" +# * throttle_setting_prefix: "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_git_lfs" # * user # * url_that_requires_authentication # * request_method @@ -176,7 +178,8 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do let(:throttle_types) do { "throttle_protected_paths" => "throttle_authenticated_protected_paths_web", - "throttle_authenticated_web" => "throttle_authenticated_web" + "throttle_authenticated_web" => "throttle_authenticated_web", + "throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs" } end @@ -385,3 +388,194 @@ RSpec.shared_examples 'tracking when dry-run mode is set' do end end end + +# Requires let variables: +# * throttle_name: "throttle_unauthenticated_api", "throttle_unauthenticated_web" +# * throttle_setting_prefix: "throttle_unauthenticated_api", "throttle_unauthenticated" +# * url_that_does_not_require_authentication +# * url_that_is_not_matched +# * requests_per_period +# * period_in_seconds +# * period +RSpec.shared_examples 'rate-limited unauthenticated requests' do + 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 + + 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 + # At first, allow requests under the rate limit. + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + # the last straw + expect_rejection { get url_that_does_not_require_authentication } + end + + context 'with custom response text' do + before do + stub_application_setting(rate_limiting_response_text: 'Custom response') + end + + it 'rejects requests over the rate limit' do + # At first, allow requests under the rate limit. + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + # the last straw + expect_rejection { get url_that_does_not_require_authentication } + expect(response.body).to eq("Custom response\n") + end + end + + it 'allows requests after throttling and then waiting for the next period' do + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + expect_rejection { get url_that_does_not_require_authentication } + + travel_to(period.from_now) do + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + expect_rejection { get url_that_does_not_require_authentication } + end + end + + it 'counts requests from different IPs separately' do + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + expect_next_instance_of(Rack::Attack::Request) do |instance| + expect(instance).to receive(:ip).at_least(:once).and_return('1.2.3.4') + end + + # would be over limit for the same IP + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + context 'when the request is not matched by the throttle' do + it 'does not throttle the requests' do + (1 + requests_per_period).times do + get url_that_is_not_matched + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context 'when the request is to the api internal endpoints' do + it 'allows requests over the rate limit' do + (1 + requests_per_period).times do + get '/api/v4/internal/check', params: { secret_token: Gitlab::Shell.secret_token } + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context 'when the request is authenticated by a runner token' do + let(:request_jobs_url) { '/api/v4/jobs/request' } + let(:runner) { create(:ci_runner) } + + it 'does not count as unauthenticated' do + (1 + requests_per_period).times do + post request_jobs_url, params: { token: runner.token } + expect(response).to have_gitlab_http_status(:no_content) + end + end + end + + context 'when the request is to a health endpoint' do + let(:health_endpoint) { '/-/metrics' } + + it 'does not throttle the requests' do + (1 + requests_per_period).times do + get health_endpoint + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context 'when the request is to a container registry notification endpoint' do + let(:secret_token) { 'secret_token' } + let(:events) { [{ action: 'push' }] } + let(:registry_endpoint) { '/api/v4/container_registry_event/events' } + let(:registry_headers) { { 'Content-Type' => ::API::ContainerRegistryEvent::DOCKER_DISTRIBUTION_EVENTS_V1_JSON } } + + before do + allow(Gitlab.config.registry).to receive(:notification_secret) { secret_token } + + event = spy(:event) + allow(::ContainerRegistry::Event).to receive(:new).and_return(event) + allow(event).to receive(:supported?).and_return(true) + end + + it 'does not throttle the requests' do + (1 + requests_per_period).times do + post registry_endpoint, + params: { events: events }.to_json, + headers: registry_headers.merge('Authorization' => secret_token) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + it 'logs RackAttack info into structured logs' do + requests_per_period.times do + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + + arguments = a_hash_including({ + message: 'Rack_Attack', + env: :throttle, + remote_ip: '127.0.0.1', + request_method: 'GET', + path: url_that_does_not_require_authentication, + matched: throttle_name + }) + + expect(Gitlab::AuthLogger).to receive(:error).with(arguments) + + get url_that_does_not_require_authentication + end + + it_behaves_like 'tracking when dry-run mode is set' do + def do_request + get url_that_does_not_require_authentication + end + end + 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 + get url_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + end + end +end |