Module: Gitlab::ApplicationRateLimiter
- Extended by:
- Utils::StrongMemoize
- 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:, peek: false) ⇒ 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
218 219 220 221 222 223 224 225 |
# File 'lib/gitlab/application_rate_limiter.rb', line 218 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
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/gitlab/application_rate_limiter.rb', line 233 def log_request(request, type, current_user, logger = Gitlab::AuthLogger) request_information = { message: 'Application_Rate_Limiter_Request', env: type, remote_ip: request.ip, method: request.request_method, path: request_path(request) } 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.
203 204 205 |
# File 'lib/gitlab/application_rate_limiter.rb', line 203 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.
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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/gitlab/application_rate_limiter.rb', line 20 def rate_limits # rubocop:disable Metrics/AbcSize { ai_action: { threshold: -> { application_settings.ai_action_api_rate_limit }, interval: 8.hours }, auto_rollback_deployment: { threshold: 1, interval: 3.minutes }, autocomplete_users: { threshold: -> { application_settings.autocomplete_users_limit }, interval: 1.minute }, autocomplete_users_unauthenticated: { threshold: -> { application_settings.autocomplete_users_unauthenticated_limit }, interval: 1.minute }, bulk_delete_todos: { threshold: 6, interval: 1.minute }, bulk_import: { threshold: 6, interval: 1.minute }, code_suggestions_api_endpoint: { threshold: -> { application_settings.code_suggestions_api_rate_limit }, interval: 1.minute }, create_organization_api: { threshold: -> { application_settings.create_organization_api_limit }, interval: 1.minute }, delete_all_todos: { threshold: 1, interval: 5.minutes }, downstream_pipeline_trigger: { threshold: -> { application_settings.downstream_pipeline_trigger_limit_per_project_user_sha }, interval: 1.minute }, email_verification: { threshold: 10, interval: 10.minutes }, email_verification_code_send: { threshold: 10, interval: 1.hour }, expanded_diff_files: { threshold: 6, interval: 1.minute }, fetch_google_ip_list: { threshold: 10, interval: 1.minute }, github_import: { threshold: 6, interval: 1.minute }, fogbugz_import: { threshold: 1, interval: 1.minute }, gitea_import: { threshold: 1, interval: 1.minute }, gitlab_shell_operation: { threshold: application_settings.gitlab_shell_operation_limit, interval: 1.minute }, glql: { threshold: 1, interval: 15.minutes }, group_api: { threshold: -> { application_settings.group_api_limit }, interval: 1.minute }, group_archive_unarchive_api: { threshold: -> { application_settings.group_archive_unarchive_api_limit }, interval: 1.minute }, group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute }, group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute }, group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute }, group_invited_groups_api: { threshold: -> { application_settings.group_invited_groups_api_limit }, interval: 1.minute }, group_projects_api: { threshold: -> { application_settings.group_projects_api_limit }, interval: 1.minute }, group_shared_groups_api: { threshold: -> { application_settings.group_shared_groups_api_limit }, interval: 1.minute }, groups_api: { threshold: -> { application_settings.groups_api_limit }, interval: 1.minute }, import_source_user_notification: { threshold: 1, interval: 8.hours }, issues_create: { threshold: -> { application_settings.issues_create_limit }, interval: 1.minute }, jobs_index: { threshold: -> { application_settings.project_jobs_api_rate_limit }, interval: 1.minute }, large_blob_download: { threshold: 5, interval: 1.minute }, members_delete: { threshold: -> { application_settings.members_delete_limit }, interval: 1.minute }, namespace_exists: { threshold: 20, interval: 1.minute }, notes_create: { threshold: -> { application_settings.notes_create_limit }, interval: 1.minute }, notification_emails: { threshold: 1000, interval: 1.day }, oauth_dynamic_registration: { threshold: 5, interval: 1.hour }, permanent_email_failure: { threshold: 5, interval: 1.day }, phone_verification_send_code: { threshold: 5, interval: 1.day }, phone_verification_verify_code: { threshold: 5, interval: 1.day }, pipelines_create: { threshold: -> { application_settings.pipeline_limit_per_project_user_sha }, interval: 1.minute }, play_pipeline_schedule: { threshold: 1, interval: 1.minute }, profile_add_new_email: { threshold: 5, interval: 1.minute }, profile_resend_email_confirmation: { threshold: 5, interval: 1.minute }, profile_update_username: { threshold: 10, interval: 1.minute }, project_api: { threshold: -> { application_settings.project_api_limit }, interval: 1.minute }, project_download_export: { threshold: -> { application_settings.project_download_export_limit }, interval: 1.minute }, project_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute }, project_fork_sync: { threshold: 10, interval: 30.minutes }, 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_invited_groups_api: { threshold: -> { application_settings.project_invited_groups_api_limit }, interval: 1.minute }, project_repositories_archive: { threshold: 5, interval: 1.minute }, project_repositories_changelog: { threshold: 5, interval: 1.minute }, project_repositories_health: { threshold: 5, interval: 1.hour }, project_testing_integration: { threshold: 5, interval: 1.minute }, projects_api: { threshold: -> { application_settings.projects_api_limit }, interval: 10.minutes }, projects_api_rate_limit_unauthenticated: { threshold: -> { application_settings.projects_api_rate_limit_unauthenticated }, interval: 10.minutes }, raw_blob: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute }, 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 }, temporary_email_failure: { threshold: 300, interval: 1.day }, update_environment_canary_ingress: { threshold: 1, interval: 1.minute }, update_namespace_name: { threshold: -> { application_settings.update_namespace_name_rate_limit }, interval: 1.hour }, user_contributed_projects_api: { threshold: -> { application_settings.user_contributed_projects_api_limit }, interval: 1.minute }, user_followers: { threshold: -> { application_settings.users_api_limit_followers }, interval: 1.minute }, user_following: { threshold: -> { application_settings.users_api_limit_following }, interval: 1.minute }, user_gpg_key: { threshold: -> { application_settings.users_api_limit_gpg_key }, interval: 1.minute }, user_gpg_keys: { threshold: -> { application_settings.users_api_limit_gpg_keys }, interval: 1.minute }, user_projects_api: { threshold: -> { application_settings.user_projects_api_limit }, interval: 1.minute }, user_sign_in: { threshold: 5, interval: 10.minutes }, user_sign_up: { threshold: 20, interval: 1.minute }, user_ssh_key: { threshold: -> { application_settings.users_api_limit_ssh_key }, interval: 1.minute }, user_ssh_keys: { threshold: -> { application_settings.users_api_limit_ssh_keys }, interval: 1.minute }, user_starred_projects_api: { threshold: -> { application_settings.user_starred_projects_api_limit }, interval: 1.minute }, user_status: { threshold: -> { application_settings.users_api_limit_status }, interval: 1.minute }, username_exists: { threshold: 20, interval: 1.minute }, users_get_by_id: { threshold: -> { application_settings.users_get_by_id_limit }, interval: 10.minutes }, vertex_embeddings_api: { threshold: 450, interval: 1.minute }, web_hook_calls: { interval: 1.minute }, web_hook_calls_low: { interval: 1.minute }, web_hook_calls_mid: { interval: 1.minute }, web_hook_event_resend: { threshold: 5, interval: 1.minute }, web_hook_test: { threshold: 5, interval: 1.minute } }.freeze end |
.report_metrics(key, value, threshold, peek) ⇒ Object
207 208 209 210 211 212 213 214 215 216 |
# File 'lib/gitlab/application_rate_limiter.rb', line 207 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:, peek: false) ⇒ Boolean
Increments the resource usage for a given key and returns true if the action should be throttled.
156 157 158 159 160 |
# File 'lib/gitlab/application_rate_limiter.rb', line 156 def resource_usage_throttled?(key, scope:, resource_key:, threshold:, interval:, peek: false) strategy = IncrementResourceUsagePerAction.new(resource_key) _throttled?(key, scope: scope, strategy: strategy, threshold: threshold, interval: interval, peek: peek) 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.
134 135 136 137 138 139 140 |
# File 'lib/gitlab/application_rate_limiter.rb', line 134 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
184 185 186 187 188 189 190 191 192 |
# File 'lib/gitlab/application_rate_limiter.rb', line 184 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 |