diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index ef91915ce38a5f8c9ef908473b5cc59b2f82b21c..0fd232a60888988ce470aa9848e1e9c0bcf36ce8 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -507,7 +507,8 @@ def visible_attributes :allow_account_deletion, :gitlab_shell_operation_limit, :namespace_aggregation_schedule_lease_duration_in_seconds, - :ci_max_total_yaml_size_bytes + :ci_max_total_yaml_size_bytes, + :project_jobs_api_rate_limit ].tap do |settings| next if Gitlab.com? diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index c7088908de81b17287ed2cb79534a631f15a2804..bf1916460ea41c8e1eebc775200ed46aab2587bb 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -651,6 +651,7 @@ def self.kroki_formats_attributes validates :throttle_authenticated_deprecated_api_period_in_seconds validates :throttle_protected_paths_requests_per_period validates :throttle_protected_paths_period_in_seconds + validates :project_jobs_api_rate_limit end with_options(numericality: { only_integer: true, greater_than_or_equal_to: 0 }) do diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 8ed24257d61664317a2d2c468e6de2ebf654845f..18f1a53f8aaea763a01d51ce9fe1e31fd687c18c 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -268,7 +268,8 @@ def defaults # rubocop:disable Metrics/AbcSize gitlab_dedicated_instance: false, ci_max_includes: 150, allow_account_deletion: true, - gitlab_shell_operation_limit: 600 + gitlab_shell_operation_limit: 600, + project_jobs_api_rate_limit: 600 }.tap do |hsh| hsh.merge!(non_production_defaults) unless Rails.env.production? end diff --git a/app/views/admin/application_settings/_ip_limits.html.haml b/app/views/admin/application_settings/_ip_limits.html.haml index 614b4076b87510036c54e9e813971034cd8158e6..cba37527606a38beef2f9b20e07b2573addd05c5 100644 --- a/app/views/admin/application_settings/_ip_limits.html.haml +++ b/app/views/admin/application_settings/_ip_limits.html.haml @@ -56,6 +56,11 @@ = f.label :throttle_authenticated_web_period_in_seconds, _('Authenticated web rate limit period in seconds'), class: 'label-bold' = f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control gl-form-input' + %fieldset + .form-group + = f.label :project_jobs_api_rate_limit, safe_format('Maximum authenticated requests to %{open}project/:id/jobs%{close} per minute', tag_pair(tag.code, :open, :close)), class: 'label-bold' + = f.number_field :project_jobs_api_rate_limit, class: 'form-control gl-form-input' + %fieldset %legend.h5.gl-border-none = _('Response text') diff --git a/db/migrate/20230814045150_add_jobs_index_rate_limit_to_application_settings.rb b/db/migrate/20230814045150_add_jobs_index_rate_limit_to_application_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..08e0a7252ce7824096c6b9c68a8bb2f4ff1681ce --- /dev/null +++ b/db/migrate/20230814045150_add_jobs_index_rate_limit_to_application_settings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddJobsIndexRateLimitToApplicationSettings < Gitlab::Database::Migration[2.1] + def change + add_column :application_settings, :project_jobs_api_rate_limit, :integer, default: 600, null: false + end +end diff --git a/db/schema_migrations/20230814045150 b/db/schema_migrations/20230814045150 new file mode 100644 index 0000000000000000000000000000000000000000..abac6edc144609fbe8b54e3e3ac934ce6dc1dded --- /dev/null +++ b/db/schema_migrations/20230814045150 @@ -0,0 +1 @@ +218b30bf9e844ec19b9388980aa5d505fc860a5fb1ad6340e620c1ac90fd799a \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 57e38bbfd1f05df4d1e96fc7fce67cc3b4cfef81..c60135f2c34946551e7dfaa8453cc5263d000691 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11851,6 +11851,7 @@ CREATE TABLE application_settings ( container_registry_db_enabled boolean DEFAULT false NOT NULL, encrypted_vertex_ai_access_token bytea, encrypted_vertex_ai_access_token_iv bytea, + project_jobs_api_rate_limit integer DEFAULT 600 NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 9616efbfe3778c1bb6bc51eff3dc71d828cd85df..9120421fadf6cd96bdb3dfa549861669dc8a82d8 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -224,6 +224,7 @@ def filter_attributes_using_license(attrs) requires :slack_app_verification_token, type: String, desc: 'The verification token of the GitLab for Slack app. This method of authentication is deprecated by Slack and used only for authenticating slash commands from the app' end optional :namespace_aggregation_schedule_lease_duration_in_seconds, type: Integer, desc: 'Maximum duration (in seconds) between refreshes of namespace statistics (Default: 300)' + optional :project_jobs_api_rate_limit, type: Integer, desc: 'Maximum authenticated requests to /project/:id/jobs per minute' Gitlab::SSHPublicKey.supported_types.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index eeaab633bb64aaf11f464a5ba9da1161eae7e0c6..bf3f5b61825a4d08409d667ceb78c73f8957c1f8 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -58,8 +58,8 @@ def rate_limits # rubocop:disable Metrics/AbcSize 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 }, 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 }, projects_api_rate_limit_unauthenticated: { threshold: -> { application_settings.projects_api_rate_limit_unauthenticated }, interval: 10.minutes diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index f372940a1e7f511c4498256fb54a74db097cb078..0f77a69f54b72b5adbd3a8a19774653f56b7dca2 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -730,6 +730,8 @@ fill_in 'Maximum authenticated web requests per rate limit period per user', with: 700 fill_in 'Authenticated web rate limit period in seconds', with: 800 + fill_in "Maximum authenticated requests to project/:id/jobs per minute", with: 1000 + fill_in 'Plain-text response to send to clients that hit a rate limit', with: 'Custom message' click_button 'Save changes' @@ -750,6 +752,7 @@ throttle_authenticated_web_enabled: true, throttle_authenticated_web_requests_per_period: 700, throttle_authenticated_web_period_in_seconds: 800, + project_jobs_api_rate_limit: 1000, rate_limiting_response_text: 'Custom message' ) end diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb index 19ac673308b5ca73c3a785067e67a724af4b366d..41e35de189e036af8766978aaa62bc87829434ea 100644 --- a/spec/requests/api/ci/jobs_spec.rb +++ b/spec/requests/api/ci/jobs_spec.rb @@ -556,7 +556,7 @@ def go before do allow_next_instance_of(Gitlab::ApplicationRateLimiter::BaseStrategy) do |strategy| - threshold = Gitlab::ApplicationRateLimiter.rate_limits[:jobs_index][:threshold] + threshold = Gitlab::ApplicationRateLimiter.rate_limits[:jobs_index][:threshold].call allow(strategy).to receive(:increment).and_return(threshold + 1) end