diff --git a/ee/app/services/security/store_report_service.rb b/ee/app/services/security/store_report_service.rb index 2dc637cf086bf6891108bea149b0fe7ba157237f..80f09abd88bc44cf69457fcef4d48dc904e80b7f 100644 --- a/ee/app/services/security/store_report_service.rb +++ b/ee/app/services/security/store_report_service.rb @@ -72,10 +72,15 @@ def create_or_find_vulnerability_finding(finding, create_params) } begin - project + vulnerability_finding = project .vulnerability_findings .create_with(create_params) - .find_or_create_by!(find_params) + .find_or_initialize_by(find_params) + + vulnerability_finding.uuid = calculcate_uuid_v5(vulnerability_finding, find_params) + + vulnerability_finding.save! + vulnerability_finding rescue ActiveRecord::RecordNotUnique project.vulnerability_findings.find_by!(find_params) rescue ActiveRecord::RecordInvalid => e @@ -83,6 +88,23 @@ def create_or_find_vulnerability_finding(finding, create_params) end end + def calculcate_uuid_v5(vulnerability_finding, finding_params) + uuid_v5_name_components = { + report_type: vulnerability_finding.report_type, + primary_identifier_fingerprint: vulnerability_finding.primary_identifier&.fingerprint || finding_params.dig(:primary_identifier, :fingerprint), + location_fingerprint: vulnerability_finding.location_fingerprint, + project_id: project.id + } + + if uuid_v5_name_components.values.any?(&:nil?) + Gitlab::AppLogger.warn(message: "One or more UUID name components are nil", components: uuid_v5_name_components) + end + + name = uuid_v5_name_components.values.join('-') + + Gitlab::Vulnerabilities::CalculateFindingUUID.call(name) + end + def update_vulnerability_scanner(finding) scanner = scanners_objects[finding.scanner.key] scanner.update!(finding.scanner.to_hash) diff --git a/ee/lib/gitlab/vulnerabilities/calculate_finding_uuid.rb b/ee/lib/gitlab/vulnerabilities/calculate_finding_uuid.rb new file mode 100644 index 0000000000000000000000000000000000000000..a7a9fd8c40a77b2d87a72843ef64088b1c89a4c8 --- /dev/null +++ b/ee/lib/gitlab/vulnerabilities/calculate_finding_uuid.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + module Vulnerabilities + class CalculateFindingUUID + FINDING_NAMESPACES_IDS = { + development: "a143e9e2-41b3-47bc-9a19-081d089229f4", + test: "a143e9e2-41b3-47bc-9a19-081d089229f4", + staging: "a6930898-a1b2-4365-ab18-12aa474d9b26", + production: "58dc0f06-936c-43b3-93bb-71693f1b6570" + }.freeze + + NAMESPACE_REGEX = /(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})/.freeze + PACK_PATTERN = "NnnnnN".freeze + + def self.call(value) + Digest::UUID.uuid_v5(namespace_id, value) + end + + def self.namespace_id + namespace_uuid = FINDING_NAMESPACES_IDS.fetch(Rails.env.to_sym) + # Digest::UUID is broken when using an UUID in namespace_id + # https://github.com/rails/rails/issues/37681#issue-520718028 + namespace_uuid.scan(NAMESPACE_REGEX).flatten.map { |s| s.to_i(16) }.pack(PACK_PATTERN) + end + end + end +end diff --git a/ee/spec/lib/gitlab/vulnerabilities/calculate_finding_uuid_spec.rb b/ee/spec/lib/gitlab/vulnerabilities/calculate_finding_uuid_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..74894ba5ef383bb1d7fd17016584b376f7ec1e57 --- /dev/null +++ b/ee/spec/lib/gitlab/vulnerabilities/calculate_finding_uuid_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Vulnerabilities::CalculateFindingUUID do + let_it_be(:value) { "GitLab" } + + subject { described_class.call(value) } + + context 'in development' do + let_it_be(:development_proper_uuid) { "5b593e54-90f5-504b-8805-5394a4d14b94" } + + before do + allow(Rails).to receive(:env).and_return(:development) + end + + it { is_expected.to eq(development_proper_uuid) } + end + + context 'in test' do + let_it_be(:test_proper_uuid) { "5b593e54-90f5-504b-8805-5394a4d14b94" } + + it { is_expected.to eq(test_proper_uuid) } + end + + context 'in staging' do + let_it_be(:staging_proper_uuid) { "dd190b37-7754-5c7c-80a0-85621a5823ad" } + + before do + allow(Rails).to receive(:env).and_return(:staging) + end + + it { is_expected.to eq(staging_proper_uuid) } + end + + context 'in production' do + let_it_be(:production_proper_uuid) { "4961388b-9d8e-5da0-a499-3ef5da58daf0" } + + before do + allow(Rails).to receive(:env).and_return(:production) + end + + it { is_expected.to eq(production_proper_uuid) } + end +end diff --git a/ee/spec/services/security/store_report_service_spec.rb b/ee/spec/services/security/store_report_service_spec.rb index 0dd1a51fd434873b5c2fc53e8f90d31b33d71265..d31bce178515aef94f077c91f3a09b1dc9b9ce50 100644 --- a/ee/spec/services/security/store_report_service_spec.rb +++ b/ee/spec/services/security/store_report_service_spec.rb @@ -53,6 +53,10 @@ it 'inserts all vulnerabilties' do expect { subject }.to change { Vulnerability.count }.by(findings) end + + it 'calculates UUIDv5 for all findings' do + expect(Vulnerabilities::Finding.pluck(:uuid)).to all(be_a(String)) + end end context 'invalid data' do @@ -118,6 +122,10 @@ expect { subject }.to change { Vulnerabilities::Finding.count }.by(32) end + it 'calculates UUIDv5 for all findings' do + expect(Vulnerabilities::Finding.pluck(:uuid)).to all(be_a(String)) + end + it 'inserts all finding pipelines (join model) for this new pipeline' do expect { subject }.to change { Vulnerabilities::FindingPipeline.where(pipeline: new_pipeline).count }.by(33) end