diff --git a/config/patches/gitlab-rails/security-patch-13.12.patch b/config/patches/gitlab-rails/security-patch-13.12.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3NlY3VyaXR5LXBhdGNoLTEzLjEyLnBhdGNo --- /dev/null +++ b/config/patches/gitlab-rails/security-patch-13.12.patch @@ -0,0 +1,55 @@ +diff --git a/app/models/note.rb b/app/models/note.rb +index 96977f3c6c3..c3ebd618afd 100644 +--- a/app/models/note.rb ++++ b/app/models/note.rb +@@ -44,7 +44,7 @@ class Note < ApplicationRecord + attr_accessor :user_visible_reference_count + + # Attribute used to store the attributes that have been changed by quick actions. +- attr_accessor :commands_changes ++ attr_writer :commands_changes + + default_value_for :system, false + +@@ -561,6 +561,41 @@ def skip_notification? + review.present? || !author.can_trigger_notifications? + end + ++ def commands_changes ++ @commands_changes&.slice( ++ :due_date, ++ :label_ids, ++ :remove_label_ids, ++ :add_label_ids, ++ :canonical_issue_id, ++ :clone_with_notes, ++ :confidential, ++ :create_merge_request, ++ :add_contacts, ++ :remove_contacts, ++ :assignee_ids, ++ :milestone_id, ++ :time_estimate, ++ :spend_time, ++ :discussion_locked, ++ :merge, ++ :rebase, ++ :wip_event, ++ :target_branch, ++ :reviewer_ids, ++ :health_status, ++ :promote_to_epic, ++ :weight, ++ :emoji_award, ++ :todo_event, ++ :subscription_event, ++ :state_event, ++ :title, ++ :tag_message, ++ :tag_name ++ ) ++ end ++ + private + + # Using this method followed by a call to *save* may result in *ActiveRecord::RecordNotUnique* exception diff --git a/config/patches/gitlab-rails/security-patch-14.0.patch b/config/patches/gitlab-rails/security-patch-14.0.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3NlY3VyaXR5LXBhdGNoLTE0LjAucGF0Y2g= --- /dev/null +++ b/config/patches/gitlab-rails/security-patch-14.0.patch @@ -0,0 +1,55 @@ +diff --git a/app/models/note.rb b/app/models/note.rb +index a76f4170a5b..70df20c0486 100644 +--- a/app/models/note.rb ++++ b/app/models/note.rb +@@ -44,7 +44,7 @@ class Note < ApplicationRecord + attr_accessor :user_visible_reference_count + + # Attribute used to store the attributes that have been changed by quick actions. +- attr_accessor :commands_changes ++ attr_writer :commands_changes + + default_value_for :system, false + +@@ -569,6 +569,41 @@ def skip_notification? + review.present? || !author.can_trigger_notifications? + end + ++ def commands_changes ++ @commands_changes&.slice( ++ :due_date, ++ :label_ids, ++ :remove_label_ids, ++ :add_label_ids, ++ :canonical_issue_id, ++ :clone_with_notes, ++ :confidential, ++ :create_merge_request, ++ :add_contacts, ++ :remove_contacts, ++ :assignee_ids, ++ :milestone_id, ++ :time_estimate, ++ :spend_time, ++ :discussion_locked, ++ :merge, ++ :rebase, ++ :wip_event, ++ :target_branch, ++ :reviewer_ids, ++ :health_status, ++ :promote_to_epic, ++ :weight, ++ :emoji_award, ++ :todo_event, ++ :subscription_event, ++ :state_event, ++ :title, ++ :tag_message, ++ :tag_name ++ ) ++ end ++ + private + + # Using this method followed by a call to *save* may result in *ActiveRecord::RecordNotUnique* exception diff --git a/config/patches/gitlab-rails/security-patch-14.1.patch b/config/patches/gitlab-rails/security-patch-14.1.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3NlY3VyaXR5LXBhdGNoLTE0LjEucGF0Y2g= --- /dev/null +++ b/config/patches/gitlab-rails/security-patch-14.1.patch @@ -0,0 +1,55 @@ +diff --git a/app/models/note.rb b/app/models/note.rb +index 2ad6df85e5f..ed64a01756a 100644 +--- a/app/models/note.rb ++++ b/app/models/note.rb +@@ -44,7 +44,7 @@ class Note < ApplicationRecord + attr_accessor :user_visible_reference_count + + # Attribute used to store the attributes that have been changed by quick actions. +- attr_accessor :commands_changes ++ attr_writer :commands_changes + + default_value_for :system, false + +@@ -576,6 +576,41 @@ def skip_notification? + review.present? || !author.can_trigger_notifications? + end + ++ def commands_changes ++ @commands_changes&.slice( ++ :due_date, ++ :label_ids, ++ :remove_label_ids, ++ :add_label_ids, ++ :canonical_issue_id, ++ :clone_with_notes, ++ :confidential, ++ :create_merge_request, ++ :add_contacts, ++ :remove_contacts, ++ :assignee_ids, ++ :milestone_id, ++ :time_estimate, ++ :spend_time, ++ :discussion_locked, ++ :merge, ++ :rebase, ++ :wip_event, ++ :target_branch, ++ :reviewer_ids, ++ :health_status, ++ :promote_to_epic, ++ :weight, ++ :emoji_award, ++ :todo_event, ++ :subscription_event, ++ :state_event, ++ :title, ++ :tag_message, ++ :tag_name ++ ) ++ end ++ + private + + # Using this method followed by a call to *save* may result in *ActiveRecord::RecordNotUnique* exception diff --git a/config/patches/gitlab-rails/security-patch-14.2.patch b/config/patches/gitlab-rails/security-patch-14.2.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3NlY3VyaXR5LXBhdGNoLTE0LjIucGF0Y2g= --- /dev/null +++ b/config/patches/gitlab-rails/security-patch-14.2.patch @@ -0,0 +1,55 @@ +diff --git a/app/models/note.rb b/app/models/note.rb +index 34ffd7c91af..18199b34c0e 100644 +--- a/app/models/note.rb ++++ b/app/models/note.rb +@@ -46,7 +46,7 @@ class Note < ApplicationRecord + attr_accessor :user_visible_reference_count + + # Attribute used to store the attributes that have been changed by quick actions. +- attr_accessor :commands_changes ++ attr_writer :commands_changes + + default_value_for :system, false + +@@ -601,6 +601,41 @@ def user_mention_identifier + }) + end + ++ def commands_changes ++ @commands_changes&.slice( ++ :due_date, ++ :label_ids, ++ :remove_label_ids, ++ :add_label_ids, ++ :canonical_issue_id, ++ :clone_with_notes, ++ :confidential, ++ :create_merge_request, ++ :add_contacts, ++ :remove_contacts, ++ :assignee_ids, ++ :milestone_id, ++ :time_estimate, ++ :spend_time, ++ :discussion_locked, ++ :merge, ++ :rebase, ++ :wip_event, ++ :target_branch, ++ :reviewer_ids, ++ :health_status, ++ :promote_to_epic, ++ :weight, ++ :emoji_award, ++ :todo_event, ++ :subscription_event, ++ :state_event, ++ :title, ++ :tag_message, ++ :tag_name ++ ) ++ end ++ + private + + # Using this method followed by a call to *save* may result in *ActiveRecord::RecordNotUnique* exception diff --git a/config/patches/gitlab-rails/security-patch-14.3.patch b/config/patches/gitlab-rails/security-patch-14.3.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3NlY3VyaXR5LXBhdGNoLTE0LjMucGF0Y2g= --- /dev/null +++ b/config/patches/gitlab-rails/security-patch-14.3.patch @@ -0,0 +1,55 @@ +diff --git a/app/models/note.rb b/app/models/note.rb +index a8f5c305d9b..439323a6ee9 100644 +--- a/app/models/note.rb ++++ b/app/models/note.rb +@@ -46,7 +46,7 @@ class Note < ApplicationRecord + attr_accessor :user_visible_reference_count + + # Attribute used to store the attributes that have been changed by quick actions. +- attr_accessor :commands_changes ++ attr_writer :commands_changes + + # Attribute used to determine whether keep_around_commits will be skipped for diff notes. + attr_accessor :skip_keep_around_commits +@@ -604,6 +604,41 @@ def user_mention_identifier + }) + end + ++ def commands_changes ++ @commands_changes&.slice( ++ :due_date, ++ :label_ids, ++ :remove_label_ids, ++ :add_label_ids, ++ :canonical_issue_id, ++ :clone_with_notes, ++ :confidential, ++ :create_merge_request, ++ :add_contacts, ++ :remove_contacts, ++ :assignee_ids, ++ :milestone_id, ++ :time_estimate, ++ :spend_time, ++ :discussion_locked, ++ :merge, ++ :rebase, ++ :wip_event, ++ :target_branch, ++ :reviewer_ids, ++ :health_status, ++ :promote_to_epic, ++ :weight, ++ :emoji_award, ++ :todo_event, ++ :subscription_event, ++ :state_event, ++ :title, ++ :tag_message, ++ :tag_name ++ ) ++ end ++ + private + + def system_note_viewable_by?(user) diff --git a/config/patches/gitlab-rails/security-patch-14.4.patch b/config/patches/gitlab-rails/security-patch-14.4.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3NlY3VyaXR5LXBhdGNoLTE0LjQucGF0Y2g= --- /dev/null +++ b/config/patches/gitlab-rails/security-patch-14.4.patch @@ -0,0 +1,55 @@ +diff --git a/app/models/note.rb b/app/models/note.rb +index 37473518892..c37297bdc87 100644 +--- a/app/models/note.rb ++++ b/app/models/note.rb +@@ -46,7 +46,7 @@ class Note < ApplicationRecord + attr_accessor :user_visible_reference_count + + # Attribute used to store the attributes that have been changed by quick actions. +- attr_accessor :commands_changes ++ attr_writer :commands_changes + + # Attribute used to determine whether keep_around_commits will be skipped for diff notes. + attr_accessor :skip_keep_around_commits +@@ -602,6 +602,41 @@ def user_mention_identifier + }) + end + ++ def commands_changes ++ @commands_changes&.slice( ++ :due_date, ++ :label_ids, ++ :remove_label_ids, ++ :add_label_ids, ++ :canonical_issue_id, ++ :clone_with_notes, ++ :confidential, ++ :create_merge_request, ++ :add_contacts, ++ :remove_contacts, ++ :assignee_ids, ++ :milestone_id, ++ :time_estimate, ++ :spend_time, ++ :discussion_locked, ++ :merge, ++ :rebase, ++ :wip_event, ++ :target_branch, ++ :reviewer_ids, ++ :health_status, ++ :promote_to_epic, ++ :weight, ++ :emoji_award, ++ :todo_event, ++ :subscription_event, ++ :state_event, ++ :title, ++ :tag_message, ++ :tag_name ++ ) ++ end ++ + private + + def system_note_viewable_by?(user) diff --git a/config/patches/gitlab-rails/security-patch-14.5.patch b/config/patches/gitlab-rails/security-patch-14.5.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3NlY3VyaXR5LXBhdGNoLTE0LjUucGF0Y2g= --- /dev/null +++ b/config/patches/gitlab-rails/security-patch-14.5.patch @@ -0,0 +1,55 @@ +diff --git a/app/models/note.rb b/app/models/note.rb +index cb285028203..e8342017bfe 100644 +--- a/app/models/note.rb ++++ b/app/models/note.rb +@@ -46,7 +46,7 @@ class Note < ApplicationRecord + attr_accessor :user_visible_reference_count + + # Attribute used to store the attributes that have been changed by quick actions. +- attr_accessor :commands_changes ++ attr_writer :commands_changes + + # Attribute used to determine whether keep_around_commits will be skipped for diff notes. + attr_accessor :skip_keep_around_commits +@@ -603,6 +603,41 @@ def user_mention_identifier + }) + end + ++ def commands_changes ++ @commands_changes&.slice( ++ :due_date, ++ :label_ids, ++ :remove_label_ids, ++ :add_label_ids, ++ :canonical_issue_id, ++ :clone_with_notes, ++ :confidential, ++ :create_merge_request, ++ :add_contacts, ++ :remove_contacts, ++ :assignee_ids, ++ :milestone_id, ++ :time_estimate, ++ :spend_time, ++ :discussion_locked, ++ :merge, ++ :rebase, ++ :wip_event, ++ :target_branch, ++ :reviewer_ids, ++ :health_status, ++ :promote_to_epic, ++ :weight, ++ :emoji_award, ++ :todo_event, ++ :subscription_event, ++ :state_event, ++ :title, ++ :tag_message, ++ :tag_name ++ ) ++ end ++ + private + + def system_note_viewable_by?(user) diff --git a/config/patches/gitlab-rails/token-prefix-patch-13.12.patch b/config/patches/gitlab-rails/token-prefix-patch-13.12.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3Rva2VuLXByZWZpeC1wYXRjaC0xMy4xMi5wYXRjaA== --- /dev/null +++ b/config/patches/gitlab-rails/token-prefix-patch-13.12.patch @@ -0,0 +1,201 @@ +diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +index 50a2613bb10..e957d09fbc6 100644 +--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb ++++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +@@ -5,16 +5,18 @@ class Encrypted < Base + def find_token_authenticatable(token, unscoped = false) + return if token.blank? + +- if required? +- find_by_encrypted_token(token, unscoped) +- elsif optional? +- find_by_encrypted_token(token, unscoped) || +- find_by_plaintext_token(token, unscoped) +- elsif migrating? +- find_by_plaintext_token(token, unscoped) +- else +- raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } +- end ++ instance = if required? ++ find_by_encrypted_token(token, unscoped) ++ elsif optional? ++ find_by_encrypted_token(token, unscoped) || ++ find_by_plaintext_token(token, unscoped) ++ elsif migrating? ++ find_by_plaintext_token(token, unscoped) ++ else ++ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } ++ end ++ ++ instance if instance && matches_prefix?(instance, token) + end + + def ensure_token(instance) +@@ -41,9 +43,7 @@ def ensure_token(instance) + def get_token(instance) + return insecure_strategy.get_token(instance) if migrating? + +- encrypted_token = instance.read_attribute(encrypted_field) +- token = EncryptionHelper.decrypt_token(encrypted_token) +- token || (insecure_strategy.get_token(instance) if optional?) ++ get_encrypted_token(instance) + end + + def set_token(instance, token) +@@ -69,6 +69,12 @@ def optional? + + protected + ++ def get_encrypted_token(instance) ++ encrypted_token = instance.read_attribute(encrypted_field) ++ token = EncryptionHelper.decrypt_token(encrypted_token) ++ token || (insecure_strategy.get_token(instance) if optional?) ++ end ++ + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) +@@ -95,14 +101,22 @@ def insecure_strategy + .new(klass, token_field, options) + end + ++ def matches_prefix?(instance, token) ++ prefix = options[:prefix] ++ prefix = prefix.call(instance) if prefix.is_a?(Proc) ++ prefix = '' unless prefix.is_a?(String) ++ ++ token.start_with?(prefix) ++ end ++ + def token_set?(instance) +- raw_token = instance.read_attribute(encrypted_field) ++ token = get_encrypted_token(instance) + + unless required? +- raw_token ||= insecure_strategy.get_token(instance) ++ token ||= insecure_strategy.get_token(instance) + end + +- raw_token.present? ++ token.present? && matches_prefix?(instance, token) + end + + def encrypted_field +diff --git a/app/models/group.rb b/app/models/group.rb +index 8114d75f682..1ceb326358a 100644 +--- a/app/models/group.rb ++++ b/app/models/group.rb +@@ -17,9 +17,15 @@ class Group < Namespace + include GroupAPICompatibility + include EachBatch + include HasTimelogsReport ++ extend ::Gitlab::Utils::Override + + ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10 + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent + has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent + alias_method :members, :group_members +@@ -98,7 +104,9 @@ class Group < Namespace + message: Gitlab::Regex.group_name_regex_message }, + if: :name_changed? + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + after_create :post_create_hook + after_destroy :post_destroy_hook +@@ -640,6 +648,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end +diff --git a/app/models/project.rb b/app/models/project.rb +index 0e2c63e625c..ad65902b2aa 100644 +--- a/app/models/project.rb ++++ b/app/models/project.rb +@@ -70,6 +70,11 @@ class Project < ApplicationRecord + + GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + cache_markdown_field :description, pipeline: :description + + default_value_for :packages_enabled, true +@@ -91,7 +96,9 @@ class Project < ApplicationRecord + default_value_for :autoclose_referenced_issues, true + default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + +@@ -1795,6 +1802,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def pages_deployed? + pages_metadatum&.deployed? + end +diff --git a/config/feature_flags/development/groups_runners_token_prefix.yml b/config/feature_flags/development/groups_runners_token_prefix.yml +new file mode 100644 +index 00000000000..87b87266673 +--- /dev/null ++++ b/config/feature_flags/development/groups_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: groups_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true +diff --git a/config/feature_flags/development/projects_runners_token_prefix.yml b/config/feature_flags/development/projects_runners_token_prefix.yml +new file mode 100644 +index 00000000000..5dd21d115f6 +--- /dev/null ++++ b/config/feature_flags/development/projects_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: projects_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true diff --git a/config/patches/gitlab-rails/token-prefix-patch-14.0.patch b/config/patches/gitlab-rails/token-prefix-patch-14.0.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3Rva2VuLXByZWZpeC1wYXRjaC0xNC4wLnBhdGNo --- /dev/null +++ b/config/patches/gitlab-rails/token-prefix-patch-14.0.patch @@ -0,0 +1,198 @@ +diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +index 50a2613bb10..e957d09fbc6 100644 +--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb ++++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +@@ -5,16 +5,18 @@ class Encrypted < Base + def find_token_authenticatable(token, unscoped = false) + return if token.blank? + +- if required? +- find_by_encrypted_token(token, unscoped) +- elsif optional? +- find_by_encrypted_token(token, unscoped) || +- find_by_plaintext_token(token, unscoped) +- elsif migrating? +- find_by_plaintext_token(token, unscoped) +- else +- raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } +- end ++ instance = if required? ++ find_by_encrypted_token(token, unscoped) ++ elsif optional? ++ find_by_encrypted_token(token, unscoped) || ++ find_by_plaintext_token(token, unscoped) ++ elsif migrating? ++ find_by_plaintext_token(token, unscoped) ++ else ++ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } ++ end ++ ++ instance if instance && matches_prefix?(instance, token) + end + + def ensure_token(instance) +@@ -41,9 +43,7 @@ def ensure_token(instance) + def get_token(instance) + return insecure_strategy.get_token(instance) if migrating? + +- encrypted_token = instance.read_attribute(encrypted_field) +- token = EncryptionHelper.decrypt_token(encrypted_token) +- token || (insecure_strategy.get_token(instance) if optional?) ++ get_encrypted_token(instance) + end + + def set_token(instance, token) +@@ -69,6 +69,12 @@ def optional? + + protected + ++ def get_encrypted_token(instance) ++ encrypted_token = instance.read_attribute(encrypted_field) ++ token = EncryptionHelper.decrypt_token(encrypted_token) ++ token || (insecure_strategy.get_token(instance) if optional?) ++ end ++ + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) +@@ -95,14 +101,22 @@ def insecure_strategy + .new(klass, token_field, options) + end + ++ def matches_prefix?(instance, token) ++ prefix = options[:prefix] ++ prefix = prefix.call(instance) if prefix.is_a?(Proc) ++ prefix = '' unless prefix.is_a?(String) ++ ++ token.start_with?(prefix) ++ end ++ + def token_set?(instance) +- raw_token = instance.read_attribute(encrypted_field) ++ token = get_encrypted_token(instance) + + unless required? +- raw_token ||= insecure_strategy.get_token(instance) ++ token ||= insecure_strategy.get_token(instance) + end + +- raw_token.present? ++ token.present? && matches_prefix?(instance, token) + end + + def encrypted_field +diff --git a/app/models/group.rb b/app/models/group.rb +index e645572d841..c2130a36401 100644 +--- a/app/models/group.rb ++++ b/app/models/group.rb +@@ -17,6 +17,12 @@ class Group < Namespace + include GroupAPICompatibility + include EachBatch + include BulkMemberAccessLoad ++ extend ::Gitlab::Utils::Override ++ ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' + + has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent + has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent +@@ -98,7 +104,9 @@ class Group < Namespace + message: Gitlab::Regex.group_name_regex_message }, + if: :name_changed? + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + after_create :post_create_hook + after_destroy :post_destroy_hook +@@ -630,6 +638,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end +diff --git a/app/models/project.rb b/app/models/project.rb +index 41fc9a2bcdf..f0a03e1f10c 100644 +--- a/app/models/project.rb ++++ b/app/models/project.rb +@@ -68,6 +68,11 @@ class Project < ApplicationRecord + + GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + cache_markdown_field :description, pipeline: :description + + default_value_for :packages_enabled, true +@@ -89,7 +94,9 @@ class Project < ApplicationRecord + default_value_for :autoclose_referenced_issues, true + default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + +@@ -1792,6 +1799,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def pages_deployed? + pages_metadatum&.deployed? + end +diff --git a/config/feature_flags/development/groups_runners_token_prefix.yml b/config/feature_flags/development/groups_runners_token_prefix.yml +new file mode 100644 +index 00000000000..87b87266673 +--- /dev/null ++++ b/config/feature_flags/development/groups_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: groups_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true +diff --git a/config/feature_flags/development/projects_runners_token_prefix.yml b/config/feature_flags/development/projects_runners_token_prefix.yml +new file mode 100644 +index 00000000000..5dd21d115f6 +--- /dev/null ++++ b/config/feature_flags/development/projects_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: projects_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true diff --git a/config/patches/gitlab-rails/token-prefix-patch-14.1.patch b/config/patches/gitlab-rails/token-prefix-patch-14.1.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3Rva2VuLXByZWZpeC1wYXRjaC0xNC4xLnBhdGNo --- /dev/null +++ b/config/patches/gitlab-rails/token-prefix-patch-14.1.patch @@ -0,0 +1,198 @@ +diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +index 50a2613bb10..e957d09fbc6 100644 +--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb ++++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +@@ -5,16 +5,18 @@ class Encrypted < Base + def find_token_authenticatable(token, unscoped = false) + return if token.blank? + +- if required? +- find_by_encrypted_token(token, unscoped) +- elsif optional? +- find_by_encrypted_token(token, unscoped) || +- find_by_plaintext_token(token, unscoped) +- elsif migrating? +- find_by_plaintext_token(token, unscoped) +- else +- raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } +- end ++ instance = if required? ++ find_by_encrypted_token(token, unscoped) ++ elsif optional? ++ find_by_encrypted_token(token, unscoped) || ++ find_by_plaintext_token(token, unscoped) ++ elsif migrating? ++ find_by_plaintext_token(token, unscoped) ++ else ++ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } ++ end ++ ++ instance if instance && matches_prefix?(instance, token) + end + + def ensure_token(instance) +@@ -41,9 +43,7 @@ def ensure_token(instance) + def get_token(instance) + return insecure_strategy.get_token(instance) if migrating? + +- encrypted_token = instance.read_attribute(encrypted_field) +- token = EncryptionHelper.decrypt_token(encrypted_token) +- token || (insecure_strategy.get_token(instance) if optional?) ++ get_encrypted_token(instance) + end + + def set_token(instance, token) +@@ -69,6 +69,12 @@ def optional? + + protected + ++ def get_encrypted_token(instance) ++ encrypted_token = instance.read_attribute(encrypted_field) ++ token = EncryptionHelper.decrypt_token(encrypted_token) ++ token || (insecure_strategy.get_token(instance) if optional?) ++ end ++ + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) +@@ -95,14 +101,22 @@ def insecure_strategy + .new(klass, token_field, options) + end + ++ def matches_prefix?(instance, token) ++ prefix = options[:prefix] ++ prefix = prefix.call(instance) if prefix.is_a?(Proc) ++ prefix = '' unless prefix.is_a?(String) ++ ++ token.start_with?(prefix) ++ end ++ + def token_set?(instance) +- raw_token = instance.read_attribute(encrypted_field) ++ token = get_encrypted_token(instance) + + unless required? +- raw_token ||= insecure_strategy.get_token(instance) ++ token ||= insecure_strategy.get_token(instance) + end + +- raw_token.present? ++ token.present? && matches_prefix?(instance, token) + end + + def encrypted_field +diff --git a/app/models/group.rb b/app/models/group.rb +index 1e7308499a0..5d4185d2355 100644 +--- a/app/models/group.rb ++++ b/app/models/group.rb +@@ -17,6 +17,12 @@ class Group < Namespace + include GroupAPICompatibility + include EachBatch + include BulkMemberAccessLoad ++ extend ::Gitlab::Utils::Override ++ ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' + + has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent + has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent +@@ -98,7 +104,9 @@ class Group < Namespace + message: Gitlab::Regex.group_name_regex_message }, + if: :name_changed? + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + after_create :post_create_hook + after_destroy :post_destroy_hook +@@ -629,6 +637,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end +diff --git a/app/models/project.rb b/app/models/project.rb +index c5522737b87..c71eb3fe0da 100644 +--- a/app/models/project.rb ++++ b/app/models/project.rb +@@ -68,6 +68,11 @@ class Project < ApplicationRecord + + GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + cache_markdown_field :description, pipeline: :description + + default_value_for :packages_enabled, true +@@ -89,7 +94,9 @@ class Project < ApplicationRecord + default_value_for :autoclose_referenced_issues, true + default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + +@@ -1781,6 +1788,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def pages_deployed? + pages_metadatum&.deployed? + end +diff --git a/config/feature_flags/development/groups_runners_token_prefix.yml b/config/feature_flags/development/groups_runners_token_prefix.yml +new file mode 100644 +index 00000000000..87b87266673 +--- /dev/null ++++ b/config/feature_flags/development/groups_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: groups_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true +diff --git a/config/feature_flags/development/projects_runners_token_prefix.yml b/config/feature_flags/development/projects_runners_token_prefix.yml +new file mode 100644 +index 00000000000..5dd21d115f6 +--- /dev/null ++++ b/config/feature_flags/development/projects_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: projects_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true diff --git a/config/patches/gitlab-rails/token-prefix-patch-14.2.patch b/config/patches/gitlab-rails/token-prefix-patch-14.2.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3Rva2VuLXByZWZpeC1wYXRjaC0xNC4yLnBhdGNo --- /dev/null +++ b/config/patches/gitlab-rails/token-prefix-patch-14.2.patch @@ -0,0 +1,198 @@ +diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +index 50a2613bb10..e957d09fbc6 100644 +--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb ++++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +@@ -5,16 +5,18 @@ class Encrypted < Base + def find_token_authenticatable(token, unscoped = false) + return if token.blank? + +- if required? +- find_by_encrypted_token(token, unscoped) +- elsif optional? +- find_by_encrypted_token(token, unscoped) || +- find_by_plaintext_token(token, unscoped) +- elsif migrating? +- find_by_plaintext_token(token, unscoped) +- else +- raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } +- end ++ instance = if required? ++ find_by_encrypted_token(token, unscoped) ++ elsif optional? ++ find_by_encrypted_token(token, unscoped) || ++ find_by_plaintext_token(token, unscoped) ++ elsif migrating? ++ find_by_plaintext_token(token, unscoped) ++ else ++ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } ++ end ++ ++ instance if instance && matches_prefix?(instance, token) + end + + def ensure_token(instance) +@@ -41,9 +43,7 @@ def ensure_token(instance) + def get_token(instance) + return insecure_strategy.get_token(instance) if migrating? + +- encrypted_token = instance.read_attribute(encrypted_field) +- token = EncryptionHelper.decrypt_token(encrypted_token) +- token || (insecure_strategy.get_token(instance) if optional?) ++ get_encrypted_token(instance) + end + + def set_token(instance, token) +@@ -69,6 +69,12 @@ def optional? + + protected + ++ def get_encrypted_token(instance) ++ encrypted_token = instance.read_attribute(encrypted_field) ++ token = EncryptionHelper.decrypt_token(encrypted_token) ++ token || (insecure_strategy.get_token(instance) if optional?) ++ end ++ + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) +@@ -95,14 +101,22 @@ def insecure_strategy + .new(klass, token_field, options) + end + ++ def matches_prefix?(instance, token) ++ prefix = options[:prefix] ++ prefix = prefix.call(instance) if prefix.is_a?(Proc) ++ prefix = '' unless prefix.is_a?(String) ++ ++ token.start_with?(prefix) ++ end ++ + def token_set?(instance) +- raw_token = instance.read_attribute(encrypted_field) ++ token = get_encrypted_token(instance) + + unless required? +- raw_token ||= insecure_strategy.get_token(instance) ++ token ||= insecure_strategy.get_token(instance) + end + +- raw_token.present? ++ token.present? && matches_prefix?(instance, token) + end + + def encrypted_field +diff --git a/app/models/group.rb b/app/models/group.rb +index f6b45a755e4..e1d55514cff 100644 +--- a/app/models/group.rb ++++ b/app/models/group.rb +@@ -17,6 +17,12 @@ class Group < Namespace + include GroupAPICompatibility + include EachBatch + include BulkMemberAccessLoad ++ extend ::Gitlab::Utils::Override ++ ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' + + has_many :all_group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent + has_many :group_members, -> { where(requested_at: nil).where.not(members: { access_level: Gitlab::Access::MINIMAL_ACCESS }) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent +@@ -98,7 +104,9 @@ class Group < Namespace + message: Gitlab::Regex.group_name_regex_message }, + if: :name_changed? + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + after_create :post_create_hook + after_destroy :post_destroy_hook +@@ -630,6 +638,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end +diff --git a/app/models/project.rb b/app/models/project.rb +index 67fc53c5ff3..0addf0d8724 100644 +--- a/app/models/project.rb ++++ b/app/models/project.rb +@@ -73,6 +73,11 @@ class Project < ApplicationRecord + + GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + cache_markdown_field :description, pipeline: :description + + default_value_for :packages_enabled, true +@@ -93,7 +98,9 @@ class Project < ApplicationRecord + default_value_for :autoclose_referenced_issues, true + default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + +@@ -1796,6 +1803,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def pages_deployed? + pages_metadatum&.deployed? + end +diff --git a/config/feature_flags/development/groups_runners_token_prefix.yml b/config/feature_flags/development/groups_runners_token_prefix.yml +new file mode 100644 +index 00000000000..87b87266673 +--- /dev/null ++++ b/config/feature_flags/development/groups_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: groups_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true +diff --git a/config/feature_flags/development/projects_runners_token_prefix.yml b/config/feature_flags/development/projects_runners_token_prefix.yml +new file mode 100644 +index 00000000000..5dd21d115f6 +--- /dev/null ++++ b/config/feature_flags/development/projects_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: projects_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true diff --git a/config/patches/gitlab-rails/token-prefix-patch-14.3.patch b/config/patches/gitlab-rails/token-prefix-patch-14.3.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3Rva2VuLXByZWZpeC1wYXRjaC0xNC4zLnBhdGNo --- /dev/null +++ b/config/patches/gitlab-rails/token-prefix-patch-14.3.patch @@ -0,0 +1,199 @@ +diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +index 50a2613bb10..e957d09fbc6 100644 +--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb ++++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +@@ -5,16 +5,18 @@ class Encrypted < Base + def find_token_authenticatable(token, unscoped = false) + return if token.blank? + +- if required? +- find_by_encrypted_token(token, unscoped) +- elsif optional? +- find_by_encrypted_token(token, unscoped) || +- find_by_plaintext_token(token, unscoped) +- elsif migrating? +- find_by_plaintext_token(token, unscoped) +- else +- raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } +- end ++ instance = if required? ++ find_by_encrypted_token(token, unscoped) ++ elsif optional? ++ find_by_encrypted_token(token, unscoped) || ++ find_by_plaintext_token(token, unscoped) ++ elsif migrating? ++ find_by_plaintext_token(token, unscoped) ++ else ++ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } ++ end ++ ++ instance if instance && matches_prefix?(instance, token) + end + + def ensure_token(instance) +@@ -41,9 +43,7 @@ def ensure_token(instance) + def get_token(instance) + return insecure_strategy.get_token(instance) if migrating? + +- encrypted_token = instance.read_attribute(encrypted_field) +- token = EncryptionHelper.decrypt_token(encrypted_token) +- token || (insecure_strategy.get_token(instance) if optional?) ++ get_encrypted_token(instance) + end + + def set_token(instance, token) +@@ -69,6 +69,12 @@ def optional? + + protected + ++ def get_encrypted_token(instance) ++ encrypted_token = instance.read_attribute(encrypted_field) ++ token = EncryptionHelper.decrypt_token(encrypted_token) ++ token || (insecure_strategy.get_token(instance) if optional?) ++ end ++ + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) +@@ -95,14 +101,22 @@ def insecure_strategy + .new(klass, token_field, options) + end + ++ def matches_prefix?(instance, token) ++ prefix = options[:prefix] ++ prefix = prefix.call(instance) if prefix.is_a?(Proc) ++ prefix = '' unless prefix.is_a?(String) ++ ++ token.start_with?(prefix) ++ end ++ + def token_set?(instance) +- raw_token = instance.read_attribute(encrypted_field) ++ token = get_encrypted_token(instance) + + unless required? +- raw_token ||= insecure_strategy.get_token(instance) ++ token ||= insecure_strategy.get_token(instance) + end + +- raw_token.present? ++ token.present? && matches_prefix?(instance, token) + end + + def encrypted_field +diff --git a/app/models/group.rb b/app/models/group.rb +index a667a908707..57a33b11221 100644 +--- a/app/models/group.rb ++++ b/app/models/group.rb +@@ -18,6 +18,13 @@ class Group < Namespace + include EachBatch + include BulkMemberAccessLoad + ++ extend ::Gitlab::Utils::Override ++ ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + def self.sti_name + 'Group' + end +@@ -105,7 +112,9 @@ def self.sti_name + message: Gitlab::Regex.group_name_regex_message }, + if: :name_changed? + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + after_create :post_create_hook + after_destroy :post_destroy_hook +@@ -646,6 +655,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end +diff --git a/app/models/project.rb b/app/models/project.rb +index b0dafeccc92..0b82cef584d 100644 +--- a/app/models/project.rb ++++ b/app/models/project.rb +@@ -74,6 +74,11 @@ class Project < ApplicationRecord + + GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + cache_markdown_field :description, pipeline: :description + + default_value_for :packages_enabled, true +@@ -94,7 +99,9 @@ class Project < ApplicationRecord + default_value_for :autoclose_referenced_issues, true + default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + +@@ -1854,6 +1861,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def pages_deployed? + pages_metadatum&.deployed? + end +diff --git a/config/feature_flags/development/groups_runners_token_prefix.yml b/config/feature_flags/development/groups_runners_token_prefix.yml +new file mode 100644 +index 00000000000..87b87266673 +--- /dev/null ++++ b/config/feature_flags/development/groups_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: groups_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true +diff --git a/config/feature_flags/development/projects_runners_token_prefix.yml b/config/feature_flags/development/projects_runners_token_prefix.yml +new file mode 100644 +index 00000000000..5dd21d115f6 +--- /dev/null ++++ b/config/feature_flags/development/projects_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: projects_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true diff --git a/config/patches/gitlab-rails/token-prefix-patch-14.4.patch b/config/patches/gitlab-rails/token-prefix-patch-14.4.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3Rva2VuLXByZWZpeC1wYXRjaC0xNC40LnBhdGNo --- /dev/null +++ b/config/patches/gitlab-rails/token-prefix-patch-14.4.patch @@ -0,0 +1,199 @@ +diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +index 50a2613bb10..e957d09fbc6 100644 +--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb ++++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +@@ -5,16 +5,18 @@ class Encrypted < Base + def find_token_authenticatable(token, unscoped = false) + return if token.blank? + +- if required? +- find_by_encrypted_token(token, unscoped) +- elsif optional? +- find_by_encrypted_token(token, unscoped) || +- find_by_plaintext_token(token, unscoped) +- elsif migrating? +- find_by_plaintext_token(token, unscoped) +- else +- raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } +- end ++ instance = if required? ++ find_by_encrypted_token(token, unscoped) ++ elsif optional? ++ find_by_encrypted_token(token, unscoped) || ++ find_by_plaintext_token(token, unscoped) ++ elsif migrating? ++ find_by_plaintext_token(token, unscoped) ++ else ++ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } ++ end ++ ++ instance if instance && matches_prefix?(instance, token) + end + + def ensure_token(instance) +@@ -41,9 +43,7 @@ def ensure_token(instance) + def get_token(instance) + return insecure_strategy.get_token(instance) if migrating? + +- encrypted_token = instance.read_attribute(encrypted_field) +- token = EncryptionHelper.decrypt_token(encrypted_token) +- token || (insecure_strategy.get_token(instance) if optional?) ++ get_encrypted_token(instance) + end + + def set_token(instance, token) +@@ -69,6 +69,12 @@ def optional? + + protected + ++ def get_encrypted_token(instance) ++ encrypted_token = instance.read_attribute(encrypted_field) ++ token = EncryptionHelper.decrypt_token(encrypted_token) ++ token || (insecure_strategy.get_token(instance) if optional?) ++ end ++ + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) +@@ -95,14 +101,22 @@ def insecure_strategy + .new(klass, token_field, options) + end + ++ def matches_prefix?(instance, token) ++ prefix = options[:prefix] ++ prefix = prefix.call(instance) if prefix.is_a?(Proc) ++ prefix = '' unless prefix.is_a?(String) ++ ++ token.start_with?(prefix) ++ end ++ + def token_set?(instance) +- raw_token = instance.read_attribute(encrypted_field) ++ token = get_encrypted_token(instance) + + unless required? +- raw_token ||= insecure_strategy.get_token(instance) ++ token ||= insecure_strategy.get_token(instance) + end + +- raw_token.present? ++ token.present? && matches_prefix?(instance, token) + end + + def encrypted_field +diff --git a/app/models/group.rb b/app/models/group.rb +index c5e119451e3..97bc07ff274 100644 +--- a/app/models/group.rb ++++ b/app/models/group.rb +@@ -18,6 +18,13 @@ class Group < Namespace + include EachBatch + include BulkMemberAccessLoad + ++ extend ::Gitlab::Utils::Override ++ ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + def self.sti_name + 'Group' + end +@@ -105,7 +112,9 @@ def self.sti_name + message: Gitlab::Regex.group_name_regex_message }, + if: :name_changed? + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + after_create :post_create_hook + after_destroy :post_destroy_hook +@@ -652,6 +661,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end +diff --git a/app/models/project.rb b/app/models/project.rb +index 6a5cf00aba1..7a927c559f3 100644 +--- a/app/models/project.rb ++++ b/app/models/project.rb +@@ -74,6 +74,11 @@ class Project < ApplicationRecord + + GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + cache_markdown_field :description, pipeline: :description + + default_value_for :packages_enabled, true +@@ -94,7 +99,9 @@ class Project < ApplicationRecord + default_value_for :autoclose_referenced_issues, true + default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + +@@ -1846,6 +1853,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def pages_deployed? + pages_metadatum&.deployed? + end +diff --git a/config/feature_flags/development/groups_runners_token_prefix.yml b/config/feature_flags/development/groups_runners_token_prefix.yml +new file mode 100644 +index 00000000000..87b87266673 +--- /dev/null ++++ b/config/feature_flags/development/groups_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: groups_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true +diff --git a/config/feature_flags/development/projects_runners_token_prefix.yml b/config/feature_flags/development/projects_runners_token_prefix.yml +new file mode 100644 +index 00000000000..5dd21d115f6 +--- /dev/null ++++ b/config/feature_flags/development/projects_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: projects_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true diff --git a/config/patches/gitlab-rails/token-prefix-patch-14.5.patch b/config/patches/gitlab-rails/token-prefix-patch-14.5.patch new file mode 100644 index 0000000000000000000000000000000000000000..02a4fe8c79ede43e225477ca1404ed0dfab23746_Y29uZmlnL3BhdGNoZXMvZ2l0bGFiLXJhaWxzL3Rva2VuLXByZWZpeC1wYXRjaC0xNC41LnBhdGNo --- /dev/null +++ b/config/patches/gitlab-rails/token-prefix-patch-14.5.patch @@ -0,0 +1,199 @@ +diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +index 50a2613bb10..e957d09fbc6 100644 +--- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb ++++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb +@@ -5,16 +5,18 @@ class Encrypted < Base + def find_token_authenticatable(token, unscoped = false) + return if token.blank? + +- if required? +- find_by_encrypted_token(token, unscoped) +- elsif optional? +- find_by_encrypted_token(token, unscoped) || +- find_by_plaintext_token(token, unscoped) +- elsif migrating? +- find_by_plaintext_token(token, unscoped) +- else +- raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } +- end ++ instance = if required? ++ find_by_encrypted_token(token, unscoped) ++ elsif optional? ++ find_by_encrypted_token(token, unscoped) || ++ find_by_plaintext_token(token, unscoped) ++ elsif migrating? ++ find_by_plaintext_token(token, unscoped) ++ else ++ raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } ++ end ++ ++ instance if instance && matches_prefix?(instance, token) + end + + def ensure_token(instance) +@@ -41,9 +43,7 @@ def ensure_token(instance) + def get_token(instance) + return insecure_strategy.get_token(instance) if migrating? + +- encrypted_token = instance.read_attribute(encrypted_field) +- token = EncryptionHelper.decrypt_token(encrypted_token) +- token || (insecure_strategy.get_token(instance) if optional?) ++ get_encrypted_token(instance) + end + + def set_token(instance, token) +@@ -69,6 +69,12 @@ def optional? + + protected + ++ def get_encrypted_token(instance) ++ encrypted_token = instance.read_attribute(encrypted_field) ++ token = EncryptionHelper.decrypt_token(encrypted_token) ++ token || (insecure_strategy.get_token(instance) if optional?) ++ end ++ + def encrypted_strategy + value = options[:encrypted] + value = value.call if value.is_a?(Proc) +@@ -95,14 +101,22 @@ def insecure_strategy + .new(klass, token_field, options) + end + ++ def matches_prefix?(instance, token) ++ prefix = options[:prefix] ++ prefix = prefix.call(instance) if prefix.is_a?(Proc) ++ prefix = '' unless prefix.is_a?(String) ++ ++ token.start_with?(prefix) ++ end ++ + def token_set?(instance) +- raw_token = instance.read_attribute(encrypted_field) ++ token = get_encrypted_token(instance) + + unless required? +- raw_token ||= insecure_strategy.get_token(instance) ++ token ||= insecure_strategy.get_token(instance) + end + +- raw_token.present? ++ token.present? && matches_prefix?(instance, token) + end + + def encrypted_field +diff --git a/app/models/group.rb b/app/models/group.rb +index 2dd20300ad2..49d6c832ca7 100644 +--- a/app/models/group.rb ++++ b/app/models/group.rb +@@ -18,6 +18,13 @@ class Group < Namespace + include EachBatch + include BulkMemberAccessLoad + ++ extend ::Gitlab::Utils::Override ++ ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + def self.sti_name + 'Group' + end +@@ -108,7 +115,9 @@ def self.sti_name + message: Gitlab::Regex.group_name_regex_message }, + if: :name_changed? + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + after_create :post_create_hook + after_destroy :post_destroy_hook +@@ -652,6 +661,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def project_creation_level + super || ::Gitlab::CurrentSettings.default_project_creation + end +diff --git a/app/models/project.rb b/app/models/project.rb +index 45999da7839..dd0da082ad2 100644 +--- a/app/models/project.rb ++++ b/app/models/project.rb +@@ -73,6 +73,11 @@ class Project < ApplicationRecord + + GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze + ++ # Prefix for runners_token which can be used to invalidate existing tokens. ++ # The value chosen here is GR (for Gitlab Runner) combined with the rotation ++ # date (20220225) decimal to hex encoded. ++ RUNNERS_TOKEN_PREFIX = 'GR1348941' ++ + cache_markdown_field :description, pipeline: :description + + default_value_for :packages_enabled, true +@@ -93,7 +98,9 @@ class Project < ApplicationRecord + default_value_for :autoclose_referenced_issues, true + default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } + +- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } ++ add_authentication_token_field :runners_token, ++ encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, ++ prefix: ->(instance) { instance.runners_token_prefix } + + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + +@@ -1863,6 +1870,15 @@ def runners_token + ensure_runners_token! + end + ++ def runners_token_prefix ++ Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' ++ end ++ ++ override :format_runners_token ++ def format_runners_token(token) ++ "#{runners_token_prefix}#{token}" ++ end ++ + def pages_deployed? + pages_metadatum&.deployed? + end +diff --git a/config/feature_flags/development/groups_runners_token_prefix.yml b/config/feature_flags/development/groups_runners_token_prefix.yml +new file mode 100644 +index 00000000000..87b87266673 +--- /dev/null ++++ b/config/feature_flags/development/groups_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: groups_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true +diff --git a/config/feature_flags/development/projects_runners_token_prefix.yml b/config/feature_flags/development/projects_runners_token_prefix.yml +new file mode 100644 +index 00000000000..5dd21d115f6 +--- /dev/null ++++ b/config/feature_flags/development/projects_runners_token_prefix.yml +@@ -0,0 +1,8 @@ ++--- ++name: projects_runners_token_prefix ++introduced_by_url: ++rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353805 ++milestone: '14.9' ++type: development ++group: group::database ++default_enabled: true