Module: Gitlab::ApplicationRateLimiter
- Defined in:
- lib/gitlab/application_rate_limiter.rb,
lib/gitlab/application_rate_limiter/base_strategy.rb,
lib/gitlab/application_rate_limiter/increment_per_action.rb,
lib/gitlab/application_rate_limiter/increment_per_actioned_resource.rb
Overview
This module implements a simple rate limiter that can be used to throttle certain actions. Unlike Rack Attack and Rack::Throttle, which operate at the middleware level, this can be used at the controller or API level. See CheckRateLimit concern for usage.
Defined Under Namespace
Classes: BaseStrategy, IncrementPerAction, IncrementPerActionedResource
Constant Summary collapse
- InvalidKeyError =
Class.new(StandardError)
Class Method Summary collapse
-
.log_request(request, type, current_user, logger = Gitlab::AuthLogger) ⇒ Object
Logs request using provided logger.
-
.peek(key, scope:, threshold: nil, interval: nil, users_allowlist: nil) ⇒ Boolean
Returns the current rate limited state without incrementing the count.
-
.rate_limits ⇒ Object
Application rate limits.
-
.throttled?(key, scope:, resource: nil, threshold: nil, interval: nil, users_allowlist: nil, peek: false) ⇒ Boolean
Increments the given key and returns true if the action should be throttled.
-
.throttled_request?(request, current_user, key, scope:, **options) ⇒ Boolean
Similar to #throttled? above but checks for the bypass header in the request and logs the request when it is over the rate limit.
Class Method Details
.log_request(request, type, current_user, logger = Gitlab::AuthLogger) ⇒ Object
Logs request using provided logger
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/gitlab/application_rate_limiter.rb', line 176 def log_request(request, type, current_user, logger = Gitlab::AuthLogger) request_information = { message: 'Application_Rate_Limiter_Request', env: type, remote_ip: request.ip, request_method: request.request_method, path: request.fullpath } if current_user request_information.merge!({ user_id: current_user.id, username: current_user.username }) end logger.error(request_information) end |
.peek(key, scope:, threshold: nil, interval: nil, users_allowlist: nil) ⇒ Boolean
Returns the current rate limited state without incrementing the count.
166 167 168 |
# File 'lib/gitlab/application_rate_limiter.rb', line 166 def peek(key, scope:, threshold: nil, interval: nil, users_allowlist: nil) throttled?(key, peek: true, scope: scope, threshold: threshold, interval: interval, users_allowlist: users_allowlist) end |
.rate_limits ⇒ Object
Application rate limits
Threshold value can be either an Integer or a Proc in order to not evaluate it’s value every time this method is called and only do that when it’s needed.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/gitlab/application_rate_limiter.rb', line 17 def rate_limits # rubocop:disable Metrics/AbcSize { issues_create: { threshold: -> { application_settings.issues_create_limit }, interval: 1.minute }, notes_create: { threshold: -> { application_settings.notes_create_limit }, interval: 1.minute }, project_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute }, project_download_export: { threshold: -> { application_settings.project_download_export_limit }, interval: 1.minute }, project_repositories_archive: { threshold: 5, interval: 1.minute }, project_generate_new_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute }, project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute }, project_testing_hook: { threshold: 5, interval: 1.minute }, play_pipeline_schedule: { threshold: 1, interval: 1.minute }, raw_blob: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute }, group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute }, group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute }, group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute }, group_testing_hook: { threshold: 5, interval: 1.minute }, member_delete: { threshold: 60, interval: 1.minute }, profile_add_new_email: { threshold: 5, interval: 1.minute }, web_hook_calls: { interval: 1.minute }, web_hook_calls_mid: { interval: 1.minute }, web_hook_calls_low: { interval: 1.minute }, users_get_by_id: { threshold: -> { application_settings.users_get_by_id_limit }, interval: 10.minutes }, username_exists: { threshold: 20, interval: 1.minute }, user_sign_up: { threshold: 20, interval: 1.minute }, user_sign_in: { threshold: 5, interval: 10.minutes }, profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }, profile_update_username: { threshold: 10, interval: 1.minute }, update_environment_canary_ingress: { threshold: 1, interval: 1.minute }, auto_rollback_deployment: { threshold: 1, interval: 3.minutes }, search_rate_limit: { threshold: -> { application_settings.search_rate_limit }, interval: 1.minute }, search_rate_limit_unauthenticated: { threshold: -> { application_settings.search_rate_limit_unauthenticated }, interval: 1.minute }, gitlab_shell_operation: { threshold: application_settings.gitlab_shell_operation_limit, interval: 1.minute }, pipelines_create: { threshold: -> { application_settings.pipeline_limit_per_project_user_sha }, interval: 1.minute }, temporary_email_failure: { threshold: 300, interval: 1.day }, permanent_email_failure: { threshold: 5, interval: 1.day }, project_testing_integration: { threshold: 5, interval: 1.minute }, email_verification: { threshold: 10, interval: 10.minutes }, email_verification_code_send: { threshold: 10, interval: 1.hour }, phone_verification_send_code: { threshold: 10, interval: 1.hour }, phone_verification_verify_code: { threshold: 10, interval: 10.minutes }, namespace_exists: { threshold: 20, interval: 1.minute }, fetch_google_ip_list: { threshold: 10, interval: 1.minute }, project_fork_sync: { threshold: 10, interval: 30.minutes }, ai_action: { threshold: 160, interval: 8.hours }, jobs_index: { threshold: 600, interval: 1.minute }, bulk_import: { threshold: 6, interval: 1.minute }, projects_api_rate_limit_unauthenticated: { threshold: -> { application_settings.projects_api_rate_limit_unauthenticated }, interval: 10.minutes } }.freeze end |
.throttled?(key, scope:, resource: nil, threshold: nil, interval: nil, users_allowlist: nil, peek: false) ⇒ Boolean
Increments the given key and returns true if the action should be throttled.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/gitlab/application_rate_limiter.rb', line 90 def throttled?(key, scope:, resource: nil, threshold: nil, interval: nil, users_allowlist: nil, peek: false) raise InvalidKeyError unless rate_limits[key] strategy = resource.present? ? IncrementPerActionedResource.new(resource.id) : IncrementPerAction.new ::Gitlab::Instrumentation::RateLimitingGates.track(key) return false if scoped_user_in_allowlist?(scope, users_allowlist) threshold_value = threshold || threshold(key) return false if threshold_value == 0 interval_value = interval || interval(key) return false if interval_value == 0 # `period_key` is based on the current time and interval so when time passes to the next interval # the key changes and the rate limit count starts again from 0. # Based on https://github.com/rack/rack-attack/blob/886ba3a18d13c6484cd511a4dc9b76c0d14e5e96/lib/rack/attack/cache.rb#L63-L68 period_key, time_elapsed_in_period = Time.now.to_i.divmod(interval_value) cache_key = cache_key(key, scope, period_key) value = if peek strategy.read(cache_key) else # We add a 1 second buffer to avoid timing issues when we're at the end of a period expiry = interval_value - time_elapsed_in_period + 1 strategy.increment(cache_key, expiry) end value > threshold_value end |
.throttled_request?(request, current_user, key, scope:, **options) ⇒ Boolean
Similar to #throttled? above but checks for the bypass header in the request and logs the request when it is over the rate limit
147 148 149 150 151 152 153 154 155 |
# File 'lib/gitlab/application_rate_limiter.rb', line 147 def throttled_request?(request, current_user, key, scope:, **) if ::Gitlab::Throttle.bypass_header.present? && request.get_header(Gitlab::Throttle.bypass_header) == '1' return false end throttled?(key, scope: scope, **).tap do |throttled| log_request(request, "#{key}_request_limit".to_sym, current_user) if throttled end end |