From 4f5ecae1f91bafc4425c0102911eb45567bd223d Mon Sep 17 00:00:00 2001 From: Gerardo Date: Wed, 13 Mar 2024 14:56:55 +0100 Subject: [PATCH 1/9] feat: Add throttle app settings for unauthenticated GIT HTTP requests - Context: When cloning a git repository via HTTP, the command `git clone` always starts with an unauthenticated HTTP GET request to the backend before continuing with authenticated requests; when many `git clone` commands are performaned in parallel then these requests are throttled based on the application settings e.g. in CI pipelines. - Add the ability to throttle these unauthenticated GIT HTTP requests - Add new section to the admin settings page to configure these settings Changelog: added --- app/helpers/application_settings_helper.rb | 3 ++ app/models/application_setting.rb | 2 + .../_git_http_limits.html.haml | 18 ++++++++ .../application_settings/network.html.haml | 12 ++++++ ...icated_git_http_to_application_settings.rb | 14 +++++++ db/schema_migrations/20240315092716 | 1 + db/structure.sql | 3 ++ .../settings/git_http_rate_limits.md | 37 ++++++++++++++++ lib/gitlab/rack_attack.rb | 6 +++ lib/gitlab/rack_attack/request.rb | 11 +++++ lib/gitlab/throttle.rb | 7 ++++ locale/gitlab.pot | 18 ++++++++ spec/lib/gitlab/rack_attack/request_spec.rb | 32 ++++++++++++++ spec/requests/rack_attack_global_spec.rb | 42 +++++++++++++++++++ .../requests/rack_attack_shared_examples.rb | 35 +++++++++------- .../network.html.haml_spec.rb | 10 +++++ 16 files changed, 237 insertions(+), 14 deletions(-) create mode 100644 app/views/admin/application_settings/_git_http_limits.html.haml create mode 100644 db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb create mode 100644 db/schema_migrations/20240315092716 create mode 100644 doc/administration/settings/git_http_rate_limits.md diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 2e680d502becc9..f05fca92f3d7ee 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -411,6 +411,9 @@ def visible_attributes :throttle_unauthenticated_files_api_enabled, :throttle_unauthenticated_files_api_period_in_seconds, :throttle_unauthenticated_files_api_requests_per_period, + :throttle_unauthenticated_git_http_enabled, + :throttle_unauthenticated_git_http_period_in_seconds, + :throttle_unauthenticated_git_http_requests_per_period, :throttle_unauthenticated_deprecated_api_enabled, :throttle_unauthenticated_deprecated_api_period_in_seconds, :throttle_unauthenticated_deprecated_api_requests_per_period, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 959000af9ed86f..d3302813508a85 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -554,6 +554,8 @@ def self.kroki_formats_attributes :throttle_unauthenticated_deprecated_api_requests_per_period, :throttle_unauthenticated_files_api_period_in_seconds, :throttle_unauthenticated_files_api_requests_per_period, + :throttle_unauthenticated_git_http_period_in_seconds, + :throttle_unauthenticated_git_http_requests_per_period, :throttle_unauthenticated_packages_api_period_in_seconds, :throttle_unauthenticated_packages_api_requests_per_period, :throttle_unauthenticated_period_in_seconds, diff --git a/app/views/admin/application_settings/_git_http_limits.html.haml b/app/views/admin/application_settings/_git_http_limits.html.haml new file mode 100644 index 00000000000000..f4440db49561ab --- /dev/null +++ b/app/views/admin/application_settings/_git_http_limits.html.haml @@ -0,0 +1,18 @@ += gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-git-http-limits-settings'), html: { class: 'fieldset-form' } do |f| + = form_errors(@application_setting) + + %fieldset + %h5 + = _('Unauthenticated Git http request rate limit') + .form-group + = f.gitlab_ui_checkbox_component :throttle_unauthenticated_git_http_enabled, + _('Enable unauthenticated Git HTTP request rate limit'), + help_text: _('Helps reduce request volume (for example, from crawlers or abusive bots)') + .form-group + = f.label :throttle_unauthenticated_git_http_requests_per_period, _('Max unauthenticated Git http requests per period per user'), class: 'gl-font-weight-bold' + = f.number_field :throttle_unauthenticated_git_http_requests_per_period, class: 'form-control gl-form-input' + .form-group + = f.label :throttle_unauthenticated_git_http_period_in_seconds, _('Unauthenticated Git http rate limit period in seconds'), class: 'gl-font-weight-bold' + = f.number_field :throttle_unauthenticated_git_http_period_in_seconds, class: 'form-control gl-form-input' + + = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index 86fbbef8dac87e..57736812fbb160 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -72,6 +72,18 @@ .settings-content = render partial: 'network_rate_limits', locals: { anchor: 'js-deprecated-limits-settings', setting_fragment: 'deprecated_api' } +%section.settings.as-git-http-limits.no-animate#js-git-http-limits-settings{ class: ('expanded' if expanded_by_default?) } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only + = _('Git HTTP rate limits') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded_by_default? ? _('Collapse') : _('Expand') + %p.gl-text-secondary + = _('Configure specific limits for Git HTTP requests that supersede the general user and IP rate limits.') + = link_to _('Learn more.'), help_page_path('administration/settings/git_http_rate_limits'), target: '_blank', rel: 'noopener noreferrer' + .settings-content + = render 'git_http_limits' + %section.settings.as-git-lfs-limits.no-animate#js-git-lfs-limits-settings{ class: ('expanded' if expanded_by_default?) } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only diff --git a/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb b/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb new file mode 100644 index 00000000000000..4c028a8ce61c4c --- /dev/null +++ b/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class AddThrottleUnauthenticatedGitHttpToApplicationSettings < Gitlab::Database::Migration[2.2] + milestone '16.10' + + def change + add_column :application_settings, :throttle_unauthenticated_git_http_enabled, :boolean, null: false, + default: false, if_not_exists: true + add_column :application_settings, :throttle_unauthenticated_git_http_requests_per_period, :integer, null: false, + default: 3600, if_not_exists: true + add_column :application_settings, :throttle_unauthenticated_git_http_period_in_seconds, :integer, null: false, + default: 3600, if_not_exists: true + end +end diff --git a/db/schema_migrations/20240315092716 b/db/schema_migrations/20240315092716 new file mode 100644 index 00000000000000..ee536aca8cbd07 --- /dev/null +++ b/db/schema_migrations/20240315092716 @@ -0,0 +1 @@ +d09e369f77a317d4590676a6aa1c368ac040c136a4b70470ad4ad16adafaecdf \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 5deb5598d1f8d4..5a256d513f1f28 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4267,6 +4267,9 @@ CREATE TABLE application_settings ( include_optional_metrics_in_service_ping boolean DEFAULT true NOT NULL, zoekt_settings jsonb DEFAULT '{}'::jsonb NOT NULL, service_ping_settings jsonb DEFAULT '{}'::jsonb NOT NULL, + throttle_unauthenticated_git_http_enabled boolean DEFAULT false NOT NULL, + throttle_unauthenticated_git_http_requests_per_period integer DEFAULT 3600 NOT NULL, + throttle_unauthenticated_git_http_period_in_seconds integer DEFAULT 3600 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/doc/administration/settings/git_http_rate_limits.md b/doc/administration/settings/git_http_rate_limits.md new file mode 100644 index 00000000000000..3c366f1e7caf89 --- /dev/null +++ b/doc/administration/settings/git_http_rate_limits.md @@ -0,0 +1,37 @@ +--- +stage: Create +group: Source Code +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + +# Rate limits on Git HTTP + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** Self-managed + +If you use Git HTTP in your repository, +common Git operations can generate many Git HTTP requests. +Some of these Git HTTP requests do not contain authentication parameter and +are considered unauthenticated. You can enforce rate limits on Git HTTP requests. +This can improve the security and durability of your web application. +[General user and IP rate limits](../settings/user_and_ip_rate_limits.md) will not be applied +to Git HTTP requests. + +## Configure Git HTTP rate limits + +Git HTTP rate limits are disabled by default. If enabled and configured, these limits +are appplied to Git HTTP requests: + +1. On the left sidebar, at the bottom, select **Admin Area**. +1. Select **Settings > Network**. +1. Expand **Git HTTP rate limits**. +1. Select **Enable unauthenticated Git HTTP request rate limit**. +1. Enter a value for **Max unauthenticated Git http requests per period per user**. +1. Enter a value for **Unauthenticated Git http rate limit period in seconds**. +1. Select **Save changes**. + +## Related topics + +- [Rate limiting](../../security/rate_limits.md) +- [User and IP rate limits](../settings/user_and_ip_rate_limits.md) diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb index 2182f5b56e4561..86c0df0e73fad3 100644 --- a/lib/gitlab/rack_attack.rb +++ b/lib/gitlab/rack_attack.rb @@ -81,6 +81,7 @@ def self.configure_user_allowlist user_allowlist end + # rubocop:disable Metrics/AbcSize ThrottleDefinition = Struct.new(:options, :request_identifier) def self.throttle_definitions { @@ -126,9 +127,14 @@ def self.throttle_definitions 'throttle_authenticated_git_lfs' => ThrottleDefinition.new( Gitlab::Throttle.throttle_authenticated_git_lfs_options, ->(req) { req.throttled_identifer([:api]) if req.throttle_authenticated_git_lfs? } + ), + 'throttle_unauthenticated_git_http' => ThrottleDefinition.new( + Gitlab::Throttle.throttle_unauthenticated_git_http_options, + ->(req) { req.ip if req.throttle_unauthenticated_git_http? } ) } end + # rubocop:enable Metrics/AbcSize def self.configure_throttles(rack_attack) # Each of these settings follows the same pattern of specifying separate diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb index e45782b8be0b8b..63c0215c65a2b2 100644 --- a/lib/gitlab/rack_attack/request.rb +++ b/lib/gitlab/rack_attack/request.rb @@ -100,6 +100,7 @@ def throttle_unauthenticated_api? def throttle_unauthenticated_web? (web_request? || frontend_request?) && !should_be_skipped? && + !git_path? && # TODO: Column will be renamed in https://gitlab.com/gitlab-org/gitlab/-/issues/340031 Gitlab::Throttle.settings.throttle_unauthenticated_enabled && unauthenticated? @@ -175,6 +176,12 @@ def throttle_authenticated_packages_api? Gitlab::Throttle.settings.throttle_authenticated_packages_api_enabled end + def throttle_unauthenticated_git_http? + git_path? && + Gitlab::Throttle.settings.throttle_unauthenticated_git_http_enabled && + unauthenticated? + end + def throttle_authenticated_git_lfs? git_lfs_path? && Gitlab::Throttle.settings.throttle_authenticated_git_lfs_enabled @@ -242,6 +249,10 @@ def packages_api_path? matches?(::Gitlab::Regex::Packages::API_PATH_REGEX) end + def git_path? + matches?(::Gitlab::PathRegex.repository_git_route_regex) + end + def git_lfs_path? matches?(::Gitlab::PathRegex.repository_git_lfs_route_regex) end diff --git a/lib/gitlab/throttle.rb b/lib/gitlab/throttle.rb index 384953533b5e9b..1aef19f1e6a453 100644 --- a/lib/gitlab/throttle.rb +++ b/lib/gitlab/throttle.rb @@ -71,6 +71,13 @@ def self.protected_paths_options { limit: limit_proc, period: period_proc } end + def self.throttle_unauthenticated_git_http_options + limit_proc = proc { |req| settings.throttle_unauthenticated_git_http_requests_per_period } + period_proc = proc { |req| settings.throttle_unauthenticated_git_http_period_in_seconds.seconds } + + { limit: limit_proc, period: period_proc } + end + def self.throttle_authenticated_git_lfs_options limit_proc = proc { |req| settings.throttle_authenticated_git_lfs_requests_per_period } period_proc = proc { |req| settings.throttle_authenticated_git_lfs_period_in_seconds.seconds } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e032315abf9054..349f92ed5b5ba8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13672,6 +13672,9 @@ msgstr "" msgid "Configure specific limits for Files API requests that supersede the general user and IP rate limits." msgstr "" +msgid "Configure specific limits for Git HTTP requests that supersede the general user and IP rate limits." +msgstr "" + msgid "Configure specific limits for Git LFS requests that supersede the general user and IP rate limits." msgstr "" @@ -19656,6 +19659,9 @@ msgstr "" msgid "Enable unauthenticated API request rate limit" msgstr "" +msgid "Enable unauthenticated Git HTTP request rate limit" +msgstr "" + msgid "Enable unauthenticated web request rate limit" msgstr "" @@ -23019,6 +23025,9 @@ msgstr "" msgid "Git" msgstr "" +msgid "Git HTTP rate limits" +msgstr "" + msgid "Git LFS is not enabled on this GitLab server, contact your admin." msgstr "" @@ -31040,6 +31049,9 @@ msgstr "" msgid "Max session time" msgstr "" +msgid "Max unauthenticated Git http requests per period per user" +msgstr "" + msgid "Maximum 20 characters" msgstr "" @@ -54658,6 +54670,12 @@ msgstr "" msgid "Unauthenticated API rate limit period in seconds" msgstr "" +msgid "Unauthenticated Git http rate limit period in seconds" +msgstr "" + +msgid "Unauthenticated Git http request rate limit" +msgstr "" + msgid "Unauthenticated requests" msgstr "" diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb index 92c9acb83cf67f..a076cb557a061d 100644 --- a/spec/lib/gitlab/rack_attack/request_spec.rb +++ b/spec/lib/gitlab/rack_attack/request_spec.rb @@ -248,6 +248,38 @@ end end + describe '#throttle_unauthenticated_git_http?' do + let_it_be(:project) { create(:project) } + + let(:git_clone_project_path_get_info_refs) { "/#{project.full_path}.git/info/refs?service=git-upload-pack" } + let(:git_clone_path_post_git_upload_pack) { "/#{project.full_path}.git/git-upload-pack" } + + subject { request.throttle_unauthenticated_git_http? } + + where(:path, :request_unauthenticated?, :application_setting_throttle_unauthenticated_git_http_enabled, :expected) do + ref(:git_clone_project_path_get_info_refs) | true | true | true + ref(:git_clone_project_path_get_info_refs) | false | true | false + ref(:git_clone_project_path_get_info_refs) | true | false | false + ref(:git_clone_project_path_get_info_refs) | false | false | false + + ref(:git_clone_path_post_git_upload_pack) | true | true | true + ref(:git_clone_path_post_git_upload_pack) | false | false | false + + '/users/sign_in' | true | true | false + '/users/sign_in' | false | false | false + end + + with_them do + before do + stub_application_setting(throttle_unauthenticated_git_http_enabled: application_setting_throttle_unauthenticated_git_http_enabled) + + allow(request).to receive(:unauthenticated?).and_return(request_unauthenticated?) + end + + it { is_expected.to eq expected } + end + end + describe '#protected_path?' do subject { request.protected_path? } diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 28cb295d3edd1c..56473dbc7d7faa 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -5,6 +5,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_caching, feature_category: :system_access do include RackAttackSpecHelpers + include WorkhorseHelpers include SessionHelpers let(:settings) { Gitlab::CurrentSettings.current_application_settings } @@ -27,6 +28,8 @@ throttle_unauthenticated_packages_api_period_in_seconds: 1, throttle_authenticated_packages_api_requests_per_period: 100, throttle_authenticated_packages_api_period_in_seconds: 1, + throttle_unauthenticated_git_http_requests_per_period: 100, + throttle_unauthenticated_git_http_period_in_seconds: 1, throttle_authenticated_git_lfs_requests_per_period: 100, throttle_authenticated_git_lfs_period_in_seconds: 1, throttle_unauthenticated_files_api_requests_per_period: 100, @@ -697,6 +700,45 @@ def do_request end end + describe 'unauthenticated git http requests' do + let_it_be(:project) { create(:project, :repository, :public) } + + let(:git_http_clone_path) { "/#{project.full_path}.git/info/refs?service=git-upload-pack" } + + it_behaves_like 'rate-limited unauthenticated requests' do + let(:throttle_name) { 'throttle_unauthenticated_git_http' } + let(:throttle_setting_prefix) { 'throttle_unauthenticated_git_http' } + let(:url_that_does_not_require_authentication) { "/#{project.full_path}.git/info/refs?service=git-upload-pack" } + let(:url_that_is_not_matched) { '/users/sign_in' } + let(:headers) { WorkhorseHelpers.workhorse_internal_api_request_header } + end + + context 'when authenticated' do + let_it_be(:user) { create(:user) } + let_it_be(:token) { create(:personal_access_token, user: user) } + + let(:headers) { WorkhorseHelpers.workhorse_internal_api_request_header.merge(basic_auth_headers(user, token)) } + + def do_request + get git_http_clone_path, headers: headers + end + + before do + settings_to_set[:throttle_unauthenticated_git_http_requests_per_period] = requests_per_period + settings_to_set[:throttle_unauthenticated_git_http_period_in_seconds] = period_in_seconds + settings_to_set[:throttle_unauthenticated_git_http_enabled] = true + stub_application_setting(settings_to_set) + end + + it 'rejects requests over the rate limit' do + (requests_per_period + 1).times do + do_request + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + describe 'authenticated git lfs requests', :api do let_it_be(:project) { create(:project, :internal) } let_it_be(:user) { create(:user) } diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index 89ae165f3fab6b..d15c88244f84b7 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -459,7 +459,10 @@ def reset_rack_attack # * requests_per_period # * period_in_seconds # * period +# * headers RSpec.shared_examples 'rate-limited unauthenticated requests' do + let(:headers) { {} } + before do # Set low limits settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period @@ -467,6 +470,10 @@ def reset_rack_attack travel_back end + def do_request + get url_that_does_not_require_authentication, headers: headers + end + context 'when the throttle is enabled' do before do settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true @@ -476,12 +483,12 @@ def reset_rack_attack it 'rejects requests over the rate limit' do # At first, allow requests under the rate limit. requests_per_period.times do - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end # the last straw - expect_rejection { get url_that_does_not_require_authentication } + expect_rejection { do_request } end context 'with custom response text' do @@ -492,37 +499,37 @@ def reset_rack_attack it 'rejects requests over the rate limit' do # At first, allow requests under the rate limit. requests_per_period.times do - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end # the last straw - expect_rejection { get url_that_does_not_require_authentication } + expect_rejection { do_request } expect(response.body).to eq("Custom response\n") end end it 'allows requests after throttling and then waiting for the next period' do requests_per_period.times do - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end - expect_rejection { get url_that_does_not_require_authentication } + expect_rejection { do_request } travel_to(period.from_now) do requests_per_period.times do - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end - expect_rejection { get url_that_does_not_require_authentication } + expect_rejection { do_request } end end it 'counts requests from different IPs separately' do requests_per_period.times do - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end @@ -531,7 +538,7 @@ def reset_rack_attack end # would be over limit for the same IP - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end @@ -603,7 +610,7 @@ def reset_rack_attack it 'logs RackAttack info into structured logs' do requests_per_period.times do - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end @@ -619,12 +626,12 @@ def reset_rack_attack expect(Gitlab::AuthLogger).to receive(:error).with(arguments) - get url_that_does_not_require_authentication + do_request end it_behaves_like 'tracking when dry-run mode is set' do def do_request - get url_that_does_not_require_authentication + get url_that_does_not_require_authentication, headers: headers end end end @@ -637,7 +644,7 @@ def do_request it 'allows requests over the rate limit' do (1 + requests_per_period).times do - get url_that_does_not_require_authentication + do_request expect(response).to have_gitlab_http_status(:ok) end end diff --git a/spec/views/admin/application_settings/network.html.haml_spec.rb b/spec/views/admin/application_settings/network.html.haml_spec.rb index 193ee8a32d5ade..defaa97f82ab61 100644 --- a/spec/views/admin/application_settings/network.html.haml_spec.rb +++ b/spec/views/admin/application_settings/network.html.haml_spec.rb @@ -11,6 +11,16 @@ allow(view).to receive(:current_user) { admin } end + context 'for Git HTTP rate limit' do + it 'renders the `git_http_rate_limit_unauthenticated` field' do + render + + expect(rendered).to have_field('application_setting_throttle_unauthenticated_git_http_enabled') + expect(rendered).to have_field('application_setting_throttle_unauthenticated_git_http_requests_per_period') + expect(rendered).to have_field('application_setting_throttle_unauthenticated_git_http_period_in_seconds') + end + end + context 'for Projects API rate limit' do it 'renders the `projects_api_rate_limit_unauthenticated` field' do render -- GitLab From 358e4c549f64c7af51b523f85f597b157d2d113b Mon Sep 17 00:00:00 2001 From: Jon Glassman Date: Fri, 22 Mar 2024 13:06:11 +0000 Subject: [PATCH 2/9] refactor: Apply suggestion from @jglassman1 --- .../application_settings/_git_http_limits.html.haml | 6 +++--- .../admin/application_settings/network.html.haml | 2 +- locale/gitlab.pot | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/admin/application_settings/_git_http_limits.html.haml b/app/views/admin/application_settings/_git_http_limits.html.haml index f4440db49561ab..7aeae8f9ab4811 100644 --- a/app/views/admin/application_settings/_git_http_limits.html.haml +++ b/app/views/admin/application_settings/_git_http_limits.html.haml @@ -3,16 +3,16 @@ %fieldset %h5 - = _('Unauthenticated Git http request rate limit') + = _('Unauthenticated Git HTTP request rate limit') .form-group = f.gitlab_ui_checkbox_component :throttle_unauthenticated_git_http_enabled, _('Enable unauthenticated Git HTTP request rate limit'), help_text: _('Helps reduce request volume (for example, from crawlers or abusive bots)') .form-group - = f.label :throttle_unauthenticated_git_http_requests_per_period, _('Max unauthenticated Git http requests per period per user'), class: 'gl-font-weight-bold' + = f.label :throttle_unauthenticated_git_http_requests_per_period, _('Maximum unauthenticated Git HTTP requests per period per user'), class: 'gl-font-weight-bold' = f.number_field :throttle_unauthenticated_git_http_requests_per_period, class: 'form-control gl-form-input' .form-group - = f.label :throttle_unauthenticated_git_http_period_in_seconds, _('Unauthenticated Git http rate limit period in seconds'), class: 'gl-font-weight-bold' + = f.label :throttle_unauthenticated_git_http_period_in_seconds, _('Unauthenticated Git HTTP rate limit period in seconds'), class: 'gl-font-weight-bold' = f.number_field :throttle_unauthenticated_git_http_period_in_seconds, class: 'form-control gl-form-input' = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index 57736812fbb160..b951c97814f7ec 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -79,7 +79,7 @@ = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do = expanded_by_default? ? _('Collapse') : _('Expand') %p.gl-text-secondary - = _('Configure specific limits for Git HTTP requests that supersede the general user and IP rate limits.') + = _('Configure specific limits for Git HTTP requests.') = link_to _('Learn more.'), help_page_path('administration/settings/git_http_rate_limits'), target: '_blank', rel: 'noopener noreferrer' .settings-content = render 'git_http_limits' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 349f92ed5b5ba8..fbcce8c864e5d7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13672,7 +13672,7 @@ msgstr "" msgid "Configure specific limits for Files API requests that supersede the general user and IP rate limits." msgstr "" -msgid "Configure specific limits for Git HTTP requests that supersede the general user and IP rate limits." +msgid "Configure specific limits for Git HTTP requests." msgstr "" msgid "Configure specific limits for Git LFS requests that supersede the general user and IP rate limits." @@ -31049,9 +31049,6 @@ msgstr "" msgid "Max session time" msgstr "" -msgid "Max unauthenticated Git http requests per period per user" -msgstr "" - msgid "Maximum 20 characters" msgstr "" @@ -31301,6 +31298,9 @@ msgstr "" msgid "Maximum unauthenticated API requests per rate limit period per IP" msgstr "" +msgid "Maximum unauthenticated Git HTTP requests per period per user" +msgstr "" + msgid "Maximum unauthenticated web requests per rate limit period per IP" msgstr "" @@ -54670,10 +54670,10 @@ msgstr "" msgid "Unauthenticated API rate limit period in seconds" msgstr "" -msgid "Unauthenticated Git http rate limit period in seconds" +msgid "Unauthenticated Git HTTP rate limit period in seconds" msgstr "" -msgid "Unauthenticated Git http request rate limit" +msgid "Unauthenticated Git HTTP request rate limit" msgstr "" msgid "Unauthenticated requests" -- GitLab From 30fea34151475877173ad38613ff34e908e766d7 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 22 Mar 2024 14:14:11 +0100 Subject: [PATCH 3/9] refactor: Fix rubocop error --- lib/gitlab/rack_attack.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb index 86c0df0e73fad3..113c6dec6c1b9d 100644 --- a/lib/gitlab/rack_attack.rb +++ b/lib/gitlab/rack_attack.rb @@ -81,7 +81,6 @@ def self.configure_user_allowlist user_allowlist end - # rubocop:disable Metrics/AbcSize ThrottleDefinition = Struct.new(:options, :request_identifier) def self.throttle_definitions { @@ -128,13 +127,18 @@ def self.throttle_definitions Gitlab::Throttle.throttle_authenticated_git_lfs_options, ->(req) { req.throttled_identifer([:api]) if req.throttle_authenticated_git_lfs? } ), + **throttle_definitions_unauthenticated_git_http + } + end + + def self.throttle_definitions_unauthenticated_git_http + { 'throttle_unauthenticated_git_http' => ThrottleDefinition.new( Gitlab::Throttle.throttle_unauthenticated_git_http_options, ->(req) { req.ip if req.throttle_unauthenticated_git_http? } ) } end - # rubocop:enable Metrics/AbcSize def self.configure_throttles(rack_attack) # Each of these settings follows the same pattern of specifying separate -- GitLab From ce4440a3d2e0e40282362da716e17472195d4dbf Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 22 Mar 2024 14:14:43 +0100 Subject: [PATCH 4/9] refactor: Apply suggestion from @jnutt --- ...throttle_unauthenticated_git_http_to_application_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb b/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb index 4c028a8ce61c4c..7dd8f058923c9a 100644 --- a/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb +++ b/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class AddThrottleUnauthenticatedGitHttpToApplicationSettings < Gitlab::Database::Migration[2.2] - milestone '16.10' + milestone '16.11' def change add_column :application_settings, :throttle_unauthenticated_git_http_enabled, :boolean, null: false, -- GitLab From 15c5f27d81c294619926244c4a5de030c9262792 Mon Sep 17 00:00:00 2001 From: Marcin Sedlak-Jakubowski Date: Mon, 25 Mar 2024 13:38:17 +0000 Subject: [PATCH 5/9] refactor: Apply suggestion from @msedlakjakubowski --- .../settings/git_http_rate_limits.md | 16 ++++++++++------ doc/administration/settings/rate_limits.md | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/administration/settings/git_http_rate_limits.md b/doc/administration/settings/git_http_rate_limits.md index 3c366f1e7caf89..8ce65fe239bdb6 100644 --- a/doc/administration/settings/git_http_rate_limits.md +++ b/doc/administration/settings/git_http_rate_limits.md @@ -8,27 +8,31 @@ info: To determine the technical writer assigned to the Stage/Group associated w DETAILS: **Tier:** Free, Premium, Ultimate -**Offering:** Self-managed +**Offering:** Self-managed, GitLab Dedicated + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147112) in GitLab 16.11. If you use Git HTTP in your repository, common Git operations can generate many Git HTTP requests. Some of these Git HTTP requests do not contain authentication parameter and are considered unauthenticated. You can enforce rate limits on Git HTTP requests. -This can improve the security and durability of your web application. -[General user and IP rate limits](../settings/user_and_ip_rate_limits.md) will not be applied +This can improve the security and durability of your web application. +[General user and IP rate limits](../settings/user_and_ip_rate_limits.md) aren't applied to Git HTTP requests. ## Configure Git HTTP rate limits Git HTTP rate limits are disabled by default. If enabled and configured, these limits -are appplied to Git HTTP requests: +are applied to Git HTTP requests. + +To configure Git HTTP rate limits: 1. On the left sidebar, at the bottom, select **Admin Area**. 1. Select **Settings > Network**. 1. Expand **Git HTTP rate limits**. 1. Select **Enable unauthenticated Git HTTP request rate limit**. -1. Enter a value for **Max unauthenticated Git http requests per period per user**. -1. Enter a value for **Unauthenticated Git http rate limit period in seconds**. +1. Enter a value for **Max unauthenticated Git HTTP requests per period per user**. +1. Enter a value for **Unauthenticated Git HTTP rate limit period in seconds**. 1. Select **Save changes**. ## Related topics diff --git a/doc/administration/settings/rate_limits.md b/doc/administration/settings/rate_limits.md index 89e64344e0c3f6..3bcd47ebdc3103 100644 --- a/doc/administration/settings/rate_limits.md +++ b/doc/administration/settings/rate_limits.md @@ -9,6 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w You can change network settings to limit the rate of connections with your instance. - [Deprecated API rate limits](deprecated_api_rate_limits.md) +- [Git HTTP](git_http_rate_limits.md) - [Git LFS](git_lfs_rate_limits.md) - [Git SSH operations](rate_limits_on_git_ssh_operations.md) - [Incident management](incident_management_rate_limits.md) -- GitLab From e98254c901440c4772e1a27ae77fc1a8dc626783 Mon Sep 17 00:00:00 2001 From: Gerardo Navarro Date: Mon, 25 Mar 2024 18:33:21 +0100 Subject: [PATCH 6/9] refactor: Apply suggestion from @bauerdominic - Change to jsonb column type for `ApplicationSetting#throttle_unauthenticated_git_http` --- app/models/application_setting.rb | 5 +++++ ...icated_git_http_to_application_settings.rb | 22 +++++++++++++------ db/structure.sql | 5 ++--- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index d3302813508a85..415687c18b765d 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -615,6 +615,11 @@ def self.kroki_formats_attributes jsonb_accessor :service_ping_settings, gitlab_environment_toolkit_instance: [:boolean, { default: false }] + jsonb_accessor :rate_limits_unauthenticated_git_http, + throttle_unauthenticated_git_http_enabled: [:boolean, { default: false }], + throttle_unauthenticated_git_http_requests_per_period: [:integer, { default: 3600 }], + throttle_unauthenticated_git_http_period_in_seconds: [:integer, { default: 3600 }] + validates :rate_limits, json_schema: { filename: "application_setting_rate_limits" } validates :search_rate_limit_allowlist, diff --git a/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb b/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb index 7dd8f058923c9a..7ff50947b5cff6 100644 --- a/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb +++ b/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb @@ -3,12 +3,20 @@ class AddThrottleUnauthenticatedGitHttpToApplicationSettings < Gitlab::Database::Migration[2.2] milestone '16.11' - def change - add_column :application_settings, :throttle_unauthenticated_git_http_enabled, :boolean, null: false, - default: false, if_not_exists: true - add_column :application_settings, :throttle_unauthenticated_git_http_requests_per_period, :integer, null: false, - default: 3600, if_not_exists: true - add_column :application_settings, :throttle_unauthenticated_git_http_period_in_seconds, :integer, null: false, - default: 3600, if_not_exists: true + disable_ddl_transaction! + + def up + add_column :application_settings, :rate_limits_unauthenticated_git_http, :jsonb, default: {}, null: false, + if_not_exists: true + + add_check_constraint( + :application_settings, + "(jsonb_typeof(rate_limits_unauthenticated_git_http) = 'object')", + 'check_application_settings_rate_limits_unauth_git_http_is_hash' + ) + end + + def down + remove_column :application_settings, :rate_limits_unauthenticated_git_http, it_exists: true end end diff --git a/db/structure.sql b/db/structure.sql index 5a256d513f1f28..24540faa0caa13 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -4267,9 +4267,7 @@ CREATE TABLE application_settings ( include_optional_metrics_in_service_ping boolean DEFAULT true NOT NULL, zoekt_settings jsonb DEFAULT '{}'::jsonb NOT NULL, service_ping_settings jsonb DEFAULT '{}'::jsonb NOT NULL, - throttle_unauthenticated_git_http_enabled boolean DEFAULT false NOT NULL, - throttle_unauthenticated_git_http_requests_per_period integer DEFAULT 3600 NOT NULL, - throttle_unauthenticated_git_http_period_in_seconds integer DEFAULT 3600 NOT NULL, + rate_limits_unauthenticated_git_http jsonb DEFAULT '{}'::jsonb 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)), @@ -4322,6 +4320,7 @@ CREATE TABLE application_settings ( CONSTRAINT check_app_settings_sentry_clientside_traces_sample_rate_range CHECK (((sentry_clientside_traces_sample_rate >= (0)::double precision) AND (sentry_clientside_traces_sample_rate <= (1)::double precision))), CONSTRAINT check_application_settings_clickhouse_is_hash CHECK ((jsonb_typeof(clickhouse) = 'object'::text)), CONSTRAINT check_application_settings_rate_limits_is_hash CHECK ((jsonb_typeof(rate_limits) = 'object'::text)), + CONSTRAINT check_application_settings_rate_limits_unauth_git_http_is_hash CHECK ((jsonb_typeof(rate_limits_unauthenticated_git_http) = 'object'::text)), CONSTRAINT check_application_settings_service_ping_settings_is_hash CHECK ((jsonb_typeof(service_ping_settings) = 'object'::text)), CONSTRAINT check_b8c74ea5b3 CHECK ((char_length(deactivation_email_additional_text) <= 1000)), CONSTRAINT check_cdfbd99405 CHECK ((char_length(security_txt_content) <= 2048)), -- GitLab From 275dc42610aa81c9a75d4cc86e6d98bb612bf1b7 Mon Sep 17 00:00:00 2001 From: Gerardo Navarro Date: Thu, 28 Mar 2024 09:48:13 +0000 Subject: [PATCH 7/9] refactor: Apply suggestion from @f_santos --- .../admin/application_settings/_git_http_limits.html.haml | 4 ++-- locale/gitlab.pot | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/admin/application_settings/_git_http_limits.html.haml b/app/views/admin/application_settings/_git_http_limits.html.haml index 7aeae8f9ab4811..fc777a17443101 100644 --- a/app/views/admin/application_settings/_git_http_limits.html.haml +++ b/app/views/admin/application_settings/_git_http_limits.html.haml @@ -7,9 +7,9 @@ .form-group = f.gitlab_ui_checkbox_component :throttle_unauthenticated_git_http_enabled, _('Enable unauthenticated Git HTTP request rate limit'), - help_text: _('Helps reduce request volume (for example, from crawlers or abusive bots)') + help_text: _('Helps reduce unauthenticated Git HTTP request volume for git paths.') .form-group - = f.label :throttle_unauthenticated_git_http_requests_per_period, _('Maximum unauthenticated Git HTTP requests per period per user'), class: 'gl-font-weight-bold' + = f.label :throttle_unauthenticated_git_http_requests_per_period, _('Maximum unauthenticated Git HTTP requests per period per IP'), class: 'gl-font-weight-bold' = f.number_field :throttle_unauthenticated_git_http_requests_per_period, class: 'form-control gl-form-input' .form-group = f.label :throttle_unauthenticated_git_http_period_in_seconds, _('Unauthenticated Git HTTP rate limit period in seconds'), class: 'gl-font-weight-bold' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fbcce8c864e5d7..707ecbf248fcb8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -25608,6 +25608,9 @@ msgstr "" msgid "Helps reduce request volume for protected paths." msgstr "" +msgid "Helps reduce unauthenticated Git HTTP request volume for git paths." +msgstr "" + msgid "Hi %{recipient_name}," msgstr "" @@ -31298,7 +31301,7 @@ msgstr "" msgid "Maximum unauthenticated API requests per rate limit period per IP" msgstr "" -msgid "Maximum unauthenticated Git HTTP requests per period per user" +msgid "Maximum unauthenticated Git HTTP requests per period per IP" msgstr "" msgid "Maximum unauthenticated web requests per rate limit period per IP" -- GitLab From 6e775ef8545d4a98aaac3adf5a3b02aca5fe9f23 Mon Sep 17 00:00:00 2001 From: Gerardo Navarro Date: Fri, 5 Apr 2024 16:17:52 +0200 Subject: [PATCH 8/9] refactor: Apply suggestion from @jnutt - Added migration to update the throttle_unauthenticated_git_http with the values of the attributes `throttle_unauthenticated_xxx` - This was a good suggestion made by @jnutt in the code review, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147112#note_1835662909 --- ...cated_git_http_to_application_settings.rb} | 0 ...icated_git_http_in_application_settings.rb | 24 +++++++++++++++++++ db/schema_migrations/20240315092716 | 1 - db/schema_migrations/20240405090000 | 1 + db/schema_migrations/20240405090010 | 1 + 5 files changed, 26 insertions(+), 1 deletion(-) rename db/migrate/{20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb => 20240405090000_add_throttle_unauthenticated_git_http_to_application_settings.rb} (100%) create mode 100644 db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb delete mode 100644 db/schema_migrations/20240315092716 create mode 100644 db/schema_migrations/20240405090000 create mode 100644 db/schema_migrations/20240405090010 diff --git a/db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb b/db/migrate/20240405090000_add_throttle_unauthenticated_git_http_to_application_settings.rb similarity index 100% rename from db/migrate/20240315092716_add_throttle_unauthenticated_git_http_to_application_settings.rb rename to db/migrate/20240405090000_add_throttle_unauthenticated_git_http_to_application_settings.rb diff --git a/db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb b/db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb new file mode 100644 index 00000000000000..55619354219df2 --- /dev/null +++ b/db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class UpdateThrottleUnauthenticatedGitHttpInApplicationSettings < Gitlab::Database::Migration[2.2] + restrict_gitlab_migration gitlab_schema: :gitlab_main + + milestone '16.11' + + disable_ddl_transaction! + + def up + execute <<~SQL + UPDATE application_settings + SET rate_limits_unauthenticated_git_http = jsonb_build_object( + 'throttle_unauthenticated_git_http_enabled', throttle_unauthenticated_enabled, + 'throttle_unauthenticated_git_http_requests_per_period', throttle_unauthenticated_requests_per_period, + 'throttle_unauthenticated_git_http_period_in_seconds', throttle_unauthenticated_period_in_seconds + ); + SQL + end + + def down + execute "UPDATE application_settings SET rate_limits_unauthenticated_git_http = CAST('{}' AS jsonb)" + end +end diff --git a/db/schema_migrations/20240315092716 b/db/schema_migrations/20240315092716 deleted file mode 100644 index ee536aca8cbd07..00000000000000 --- a/db/schema_migrations/20240315092716 +++ /dev/null @@ -1 +0,0 @@ -d09e369f77a317d4590676a6aa1c368ac040c136a4b70470ad4ad16adafaecdf \ No newline at end of file diff --git a/db/schema_migrations/20240405090000 b/db/schema_migrations/20240405090000 new file mode 100644 index 00000000000000..42e1ac97298bcc --- /dev/null +++ b/db/schema_migrations/20240405090000 @@ -0,0 +1 @@ +1f79208f10eac682970b91dd12159c9c7446fab637409d2f62dc91231af1dd13 \ No newline at end of file diff --git a/db/schema_migrations/20240405090010 b/db/schema_migrations/20240405090010 new file mode 100644 index 00000000000000..3b93267c8f9955 --- /dev/null +++ b/db/schema_migrations/20240405090010 @@ -0,0 +1 @@ +c84be5380fb2c1e90aabd987b8a7c020ec17405a8d0b97f0ca44e4ea724b7c6d \ No newline at end of file -- GitLab From 9118613a81f49bea99403a5004656a83d15b2239 Mon Sep 17 00:00:00 2001 From: Gerardo Navarro Date: Mon, 22 Apr 2024 12:57:21 +0000 Subject: [PATCH 9/9] refactor: Apply suggestion from @jnutt --- ...throttle_unauthenticated_git_http_to_application_settings.rb | 2 +- ...throttle_unauthenticated_git_http_in_application_settings.rb | 2 +- doc/administration/settings/git_http_rate_limits.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/migrate/20240405090000_add_throttle_unauthenticated_git_http_to_application_settings.rb b/db/migrate/20240405090000_add_throttle_unauthenticated_git_http_to_application_settings.rb index 7ff50947b5cff6..1881b8d630b856 100644 --- a/db/migrate/20240405090000_add_throttle_unauthenticated_git_http_to_application_settings.rb +++ b/db/migrate/20240405090000_add_throttle_unauthenticated_git_http_to_application_settings.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class AddThrottleUnauthenticatedGitHttpToApplicationSettings < Gitlab::Database::Migration[2.2] - milestone '16.11' + milestone '17.0' disable_ddl_transaction! diff --git a/db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb b/db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb index 55619354219df2..d4458912262d09 100644 --- a/db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb +++ b/db/migrate/20240405090010_update_throttle_unauthenticated_git_http_in_application_settings.rb @@ -3,7 +3,7 @@ class UpdateThrottleUnauthenticatedGitHttpInApplicationSettings < Gitlab::Database::Migration[2.2] restrict_gitlab_migration gitlab_schema: :gitlab_main - milestone '16.11' + milestone '17.0' disable_ddl_transaction! diff --git a/doc/administration/settings/git_http_rate_limits.md b/doc/administration/settings/git_http_rate_limits.md index 8ce65fe239bdb6..751e41eec9b4af 100644 --- a/doc/administration/settings/git_http_rate_limits.md +++ b/doc/administration/settings/git_http_rate_limits.md @@ -10,7 +10,7 @@ DETAILS: **Tier:** Free, Premium, Ultimate **Offering:** Self-managed, GitLab Dedicated -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147112) in GitLab 16.11. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147112) in GitLab 17.0. If you use Git HTTP in your repository, common Git operations can generate many Git HTTP requests. -- GitLab