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,
lib/gitlab/application_rate_limiter/increment_resource_usage_per_action.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, IncrementResourceUsagePerAction
Constant Summary collapse
- InvalidKeyError =
Class.new(StandardError)
- LIMIT_USAGE_BUCKET =
[0.25, 0.5, 0.75, 1].freeze
Class Method Summary collapse
- .application_rate_limiter_histogram ⇒ Object
-
.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.
- .report_metrics(key, value, threshold, peek) ⇒ Object
-
.resource_usage_throttled?(key, scope:, resource_key:, threshold:, interval:) ⇒ Boolean
Increments the resource usage for a given key and returns true if the action should be throttled.
-
.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
.application_rate_limiter_histogram ⇒ Object
204 205 206 207 208 209 210 211 |
# File 'lib/gitlab/application_rate_limiter.rb', line 204 def application_rate_limiter_histogram @application_rate_limiter_histogram ||= Gitlab::Metrics.histogram( :gitlab_application_rate_limiter_throttle_utilization_ratio, "The utilization-ratio of a throttle.", { peek: nil, throttle_key: nil, feature_category: nil }, LIMIT_USAGE_BUCKET ) end |
.log_request(request, type, current_user, logger = Gitlab::AuthLogger) ⇒ Object
Logs request using provided logger
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/gitlab/application_rate_limiter.rb', line 219 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.
189 190 191 |
# File 'lib/gitlab/application_rate_limiter.rb', line 189 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.
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/gitlab/application_rate_limiter.rb', line 19 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 }, 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_api: { threshold: -> { application_settings.group_api_limit }, interval: 1.minute }, group_invited_groups_api: { threshold: -> { application_settings.group_invited_groups_api_limit }, interval: 1.minute }, group_shared_groups_api: { threshold: -> { application_settings.group_shared_groups_api_limit }, interval: 1.minute }, group_projects_api: { threshold: -> { application_settings.group_projects_api_limit }, interval: 1.minute }, groups_api: { threshold: -> { application_settings.groups_api_limit }, interval: 1.minute }, project_api: { threshold: -> { application_settings.project_api_limit }, interval: 1.minute }, create_organization_api: { threshold: -> { application_settings.create_organization_api_limit }, interval: 1.minute }, project_invited_groups_api: { threshold: -> { application_settings.project_invited_groups_api_limit }, interval: 1.minute }, projects_api: { threshold: -> { application_settings.projects_api_limit }, interval: 10.minutes }, user_contributed_projects_api: { threshold: -> { application_settings.user_contributed_projects_api_limit }, interval: 1.minute }, user_projects_api: { threshold: -> { application_settings.user_projects_api_limit }, interval: 1.minute }, user_starred_projects_api: { threshold: -> { application_settings.user_starred_projects_api_limit }, interval: 1.minute }, members_delete: { threshold: -> { application_settings.members_delete_limit }, 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 }, web_hook_test: { threshold: 5, interval: 1.minute }, web_hook_event_resend: { threshold: 5, 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_followers: { threshold: 100, interval: 1.minute }, user_following: { threshold: 100, interval: 1.minute }, user_status: { threshold: 240, interval: 1.minute }, user_keys: { threshold: 120, interval: 1.minute }, user_specific_key: { threshold: 120, interval: 1.minute }, user_gpg_keys: { threshold: 120, interval: 1.minute }, user_specific_gpg_key: { threshold: 120, 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 }, notification_emails: { threshold: 1000, 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: 5, interval: 1.day }, phone_verification_verify_code: { threshold: 5, interval: 1.day }, namespace_exists: { threshold: 20, interval: 1.minute }, update_namespace_name: { threshold: -> { application_settings.update_namespace_name_rate_limit }, interval: 1.hour }, fetch_google_ip_list: { threshold: 10, interval: 1.minute }, project_fork_sync: { threshold: 10, interval: 30.minutes }, ai_action: { threshold: -> { application_settings.ai_action_api_rate_limit }, interval: 8.hours }, code_suggestions_api_endpoint: { threshold: -> { application_settings.code_suggestions_api_rate_limit }, interval: 1.minute }, vertex_embeddings_api: { threshold: 450, interval: 1.minute }, jobs_index: { threshold: -> { application_settings.project_jobs_api_rate_limit }, interval: 1.minute }, bulk_import: { threshold: 6, interval: 1.minute }, fogbugz_import: { threshold: 1, interval: 1.minute }, import_source_user_notification: { threshold: 1, interval: 8.hours }, projects_api_rate_limit_unauthenticated: { threshold: -> { application_settings.projects_api_rate_limit_unauthenticated }, interval: 10.minutes }, downstream_pipeline_trigger: { threshold: -> { application_settings.downstream_pipeline_trigger_limit_per_project_user_sha }, interval: 1.minute } }.freeze end |
.report_metrics(key, value, threshold, peek) ⇒ Object
193 194 195 196 197 198 199 200 201 202 |
# File 'lib/gitlab/application_rate_limiter.rb', line 193 def report_metrics(key, value, threshold, peek) return if threshold == 0 # guard against div-by-zero label = { throttle_key: key, peek: peek, feature_category: Gitlab::ApplicationContext.current_context_attribute(:feature_category) } application_rate_limiter_histogram.observe(label, value / threshold.to_f) end |
.resource_usage_throttled?(key, scope:, resource_key:, threshold:, interval:) ⇒ Boolean
Increments the resource usage for a given key and returns true if the action should be throttled.
142 143 144 145 146 |
# File 'lib/gitlab/application_rate_limiter.rb', line 142 def resource_usage_throttled?(key, scope:, resource_key:, threshold:, interval:) strategy = IncrementResourceUsagePerAction.new(resource_key) _throttled?(key, scope: scope, strategy: strategy, threshold: threshold, interval: interval) 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.
120 121 122 123 124 125 126 |
# File 'lib/gitlab/application_rate_limiter.rb', line 120 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 _throttled?(key, scope: scope, strategy: strategy, threshold: threshold, interval: interval, users_allowlist: users_allowlist, peek: peek) 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
170 171 172 173 174 175 176 177 178 |
# File 'lib/gitlab/application_rate_limiter.rb', line 170 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 |