# frozen_string_literal: true # When adding new user-configurable throttles, remember to update the documentation # in doc/user/admin_area/settings/user_and_ip_rate_limits.md # # Integration specs for throttling can be found in: # spec/requests/rack_attack_global_spec.rb module Gitlab module RackAttack def self.configure(rack_attack) # This adds some methods used by our throttles to the `Rack::Request` rack_attack::Request.include(Gitlab::RackAttack::Request) # This is Rack::Attack::DEFAULT_THROTTLED_RESPONSE, modified to allow a custom response Rack::Attack.throttled_response = lambda do |env| # Send the Retry-After header so clients (e.g. python-gitlab) can make good choices about delays match_data = env['rack.attack.match_data'] now = match_data[:epoch_time] retry_after = match_data[:period] - (now % match_data[:period]) [429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, [Gitlab::Throttle.rate_limiting_response_text]] end # Configure the throttles configure_throttles(rack_attack) configure_user_allowlist end def self.configure_user_allowlist @user_allowlist = nil user_allowlist end def self.configure_throttles(rack_attack) throttle_or_track(rack_attack, 'throttle_unauthenticated', Gitlab::Throttle.unauthenticated_options) do |req| if !req.should_be_skipped? && Gitlab::Throttle.settings.throttle_unauthenticated_enabled && req.unauthenticated? req.ip end end throttle_or_track(rack_attack, 'throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req| if req.api_request? && Gitlab::Throttle.settings.throttle_authenticated_api_enabled req.throttled_user_id([:api]) end end # Product analytics feature is in experimental stage. # At this point we want to limit amount of events registered # per application (aid stands for application id). throttle_or_track(rack_attack, 'throttle_product_analytics_collector', limit: 100, period: 60) do |req| if req.product_analytics_collector_request? req.params['aid'] end end throttle_or_track(rack_attack, 'throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req| if req.web_request? && Gitlab::Throttle.settings.throttle_authenticated_web_enabled req.throttled_user_id([:api, :rss, :ics]) end end throttle_or_track(rack_attack, 'throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req| if req.post? && !req.should_be_skipped? && req.protected_path? && Gitlab::Throttle.protected_paths_enabled? && req.unauthenticated? req.ip end end throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req| if req.post? && req.api_request? && req.protected_path? && Gitlab::Throttle.protected_paths_enabled? req.throttled_user_id([:api]) end end throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req| if req.post? && req.web_request? && req.protected_path? && Gitlab::Throttle.protected_paths_enabled? req.throttled_user_id([:api, :rss, :ics]) end end rack_attack.safelist('throttle_bypass_header') do |req| Gitlab::Throttle.bypass_header.present? && req.get_header(Gitlab::Throttle.bypass_header) == '1' end end def self.throttle_or_track(rack_attack, throttle_name, *args, &block) if track?(throttle_name) rack_attack.track(throttle_name, *args, &block) else rack_attack.throttle(throttle_name, *args, &block) end end def self.track?(name) dry_run_config = ENV['GITLAB_THROTTLE_DRY_RUN'].to_s.strip return false if dry_run_config.empty? return true if dry_run_config == '*' dry_run_config.split(',').map(&:strip).include?(name) end def self.user_allowlist @user_allowlist ||= begin list = UserAllowlist.new(ENV['GITLAB_THROTTLE_USER_ALLOWLIST']) Gitlab::AuthLogger.info(gitlab_throttle_user_allowlist: list.to_a) list end end end end ::Gitlab::RackAttack.prepend_if_ee('::EE::Gitlab::RackAttack')