Commit dda5a172 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch 'move_uuid_calculation_out_of_store_report_service' into 'master'

Move uuid calculation out of store report service

See merge request gitlab-org/gitlab!49784
parents caa4847e 070b79a3
...@@ -111,7 +111,7 @@ module Security ...@@ -111,7 +111,7 @@ module Security
def all_security_findings def all_security_findings
pipeline.security_findings pipeline.security_findings
.with_build_and_artifacts .with_pipeline_entities
.with_scan .with_scan
.with_scanner .with_scanner
.deduplicated .deduplicated
......
...@@ -118,7 +118,7 @@ module EE ...@@ -118,7 +118,7 @@ module EE
strong_memoize(:security_report) do strong_memoize(:security_report) do
next unless file_type.in?(SECURITY_REPORT_FILE_TYPES) next unless file_type.in?(SECURITY_REPORT_FILE_TYPES)
report = ::Gitlab::Ci::Reports::Security::Report.new(file_type, nil, nil).tap do |report| report = ::Gitlab::Ci::Reports::Security::Report.new(file_type, job.pipeline, nil).tap do |report|
each_blob do |blob| each_blob do |blob|
::Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, report) ::Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, report)
end end
......
...@@ -38,7 +38,7 @@ module Security ...@@ -38,7 +38,7 @@ module Security
.where('vulnerability_feedback.project_fingerprint = security_findings.project_fingerprint')) .where('vulnerability_feedback.project_fingerprint = security_findings.project_fingerprint'))
end end
scope :ordered, -> { order(severity: :desc, confidence: :desc, id: :asc) } scope :ordered, -> { order(severity: :desc, confidence: :desc, id: :asc) }
scope :with_build_and_artifacts, -> { includes(build: :job_artifacts) } scope :with_pipeline_entities, -> { includes(build: [:job_artifacts, pipeline: :project]) }
scope :with_scan, -> { includes(:scan) } scope :with_scan, -> { includes(:scan) }
scope :with_scanner, -> { includes(:scanner) } scope :with_scanner, -> { includes(:scanner) }
scope :deduplicated, -> { where(deduplicated: true) } scope :deduplicated, -> { where(deduplicated: true) }
......
...@@ -50,7 +50,6 @@ module Security ...@@ -50,7 +50,6 @@ module Security
end end
vulnerability_params = finding.to_hash.except(:compare_key, :identifiers, :location, :scanner, :scan, :links) vulnerability_params = finding.to_hash.except(:compare_key, :identifiers, :location, :scanner, :scan, :links)
vulnerability_params[:uuid] = calculate_uuid_v5(finding)
vulnerability_finding = create_or_find_vulnerability_finding(finding, vulnerability_params) vulnerability_finding = create_or_find_vulnerability_finding(finding, vulnerability_params)
update_vulnerability_scanner(finding) update_vulnerability_scanner(finding)
...@@ -94,23 +93,6 @@ module Security ...@@ -94,23 +93,6 @@ module Security
end end
end end
def calculate_uuid_v5(vulnerability_finding)
uuid_v5_name_components = {
report_type: vulnerability_finding.report_type,
primary_identifier_fingerprint: vulnerability_finding.primary_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) def update_vulnerability_scanner(finding)
scanner = scanners_objects[finding.scanner.key] scanner = scanners_objects[finding.scanner.key]
scanner.update!(finding.scanner.to_hash) scanner.update!(finding.scanner.to_hash)
......
...@@ -61,7 +61,7 @@ module Gitlab ...@@ -61,7 +61,7 @@ module Gitlab
report.add_finding( report.add_finding(
::Gitlab::Ci::Reports::Security::Finding.new( ::Gitlab::Ci::Reports::Security::Finding.new(
uuid: SecureRandom.uuid, uuid: calculate_uuid_v5(report, location, identifiers.first),
report_type: report.type, report_type: report.type,
name: finding_name(data, identifiers, location), name: finding_name(data, identifiers, location),
compare_key: data['cve'] || '', compare_key: data['cve'] || '',
...@@ -160,6 +160,24 @@ module Gitlab ...@@ -160,6 +160,24 @@ module Gitlab
identifier = identifiers.find(&:cve?) || identifiers.find(&:cwe?) || identifiers.first identifier = identifiers.find(&:cve?) || identifiers.find(&:cwe?) || identifiers.first
"#{identifier.name} in #{location&.fingerprint_path}" "#{identifier.name} in #{location&.fingerprint_path}"
end end
def calculate_uuid_v5(report, location, primary_identifier)
uuid_v5_name_components = {
report_type: report.type,
primary_identifier_fingerprint: primary_identifier&.fingerprint,
location_fingerprint: location&.fingerprint,
project_id: report.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)
return
end
name = uuid_v5_name_components.values.join('-')
Gitlab::UUID.v5(name)
end
end end
end end
end end
......
...@@ -5,16 +5,10 @@ module Gitlab ...@@ -5,16 +5,10 @@ module Gitlab
module Reports module Reports
module Security module Security
class Report class Report
attr_reader :created_at attr_reader :created_at, :type, :pipeline, :findings, :scanners, :identifiers
attr_reader :type attr_accessor :scan, :scanned_resources, :error
attr_reader :pipeline
attr_reader :findings
attr_reader :scanners
attr_reader :identifiers
attr_accessor :scan delegate :project_id, to: :pipeline
attr_accessor :scanned_resources
attr_accessor :error
def initialize(type, pipeline, created_at) def initialize(type, pipeline, created_at)
@type = type @type = type
......
# 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
...@@ -91,7 +91,7 @@ RSpec.describe Security::FindingsFinder do ...@@ -91,7 +91,7 @@ RSpec.describe Security::FindingsFinder do
end end
it 'does not cause N+1 queries' do it 'does not cause N+1 queries' do
expect { finder_result }.not_to exceed_query_limit(7) expect { finder_result }.not_to exceed_query_limit(9)
end end
describe '#current_page' do describe '#current_page' do
......
...@@ -13,7 +13,13 @@ ...@@ -13,7 +13,13 @@
"name": "Gemnasium" "name": "Gemnasium"
}, },
"location": {}, "location": {},
"identifiers": [], "identifiers": [
{
"type": "GitLab",
"name": "Foo vulnerability",
"value": "foo"
}
],
"links": [ "links": [
{ {
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020" "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020"
...@@ -52,7 +58,13 @@ ...@@ -52,7 +58,13 @@
"name": "Gemnasium" "name": "Gemnasium"
}, },
"location": {}, "location": {},
"identifiers": [], "identifiers": [
{
"type": "GitLab",
"name": "Bar vulnerability",
"value": "bar"
}
],
"links": [ "links": [
{ {
"name": "CVE-1030", "name": "CVE-1030",
......
...@@ -13,12 +13,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -13,12 +13,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
before do before do
allow(parser).to receive(:create_location).and_return(location) allow(parser).to receive(:create_location).and_return(location)
artifact.each_blob do |blob|
parser.parse!(blob, report) artifact.each_blob { |blob| parser.parse!(blob, report) }
end
end end
context 'parsing finding.name' do describe 'parsing finding.name' do
let(:artifact) { build(:ee_ci_job_artifact, :common_security_report_with_blank_names) } let(:artifact) { build(:ee_ci_job_artifact, :common_security_report_with_blank_names) }
context 'when message is provided' do context 'when message is provided' do
...@@ -65,9 +64,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -65,9 +64,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
end end
context 'parsing finding.details' do describe 'parsing finding.details' do
let(:artifact) { build(:ee_ci_job_artifact, :common_security_report) }
context 'when details are provided' do context 'when details are provided' do
it 'sets details from the report' do it 'sets details from the report' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' } vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' }
...@@ -85,7 +82,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -85,7 +82,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
end end
context 'parsing remediations' do describe 'parsing remediations' do
let(:expected_remediation) { create(:ci_reports_security_remediation, diff: '') } let(:expected_remediation) { create(:ci_reports_security_remediation, diff: '') }
it 'finds remediation with same cve' do it 'finds remediation with same cve' do
...@@ -122,7 +119,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -122,7 +119,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
end end
context 'parsing scanners' do describe 'parsing scanners' do
subject(:scanner) { report.findings.first.scanner } subject(:scanner) { report.findings.first.scanner }
context 'when vendor is not missing in scanner' do context 'when vendor is not missing in scanner' do
...@@ -132,7 +129,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -132,7 +129,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
end end
context 'parsing scan' do describe 'parsing scan' do
it 'returns scan object for each finding' do it 'returns scan object for each finding' do
scans = report.findings.map(&:scan) scans = report.findings.map(&:scan)
...@@ -153,7 +150,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -153,7 +150,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
end end
context 'parsing links' do describe 'parsing links' do
it 'returns links object for each finding', :aggregate_failures do it 'returns links object for each finding', :aggregate_failures do
links = report.findings.flat_map(&:links) links = report.findings.flat_map(&:links)
...@@ -163,5 +160,18 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -163,5 +160,18 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
expect(links.first).to be_a(::Gitlab::Ci::Reports::Security::Link) expect(links.first).to be_a(::Gitlab::Ci::Reports::Security::Link)
end end
end end
describe 'setting the uuid' do
let(:finding_uuids) { report.findings.map(&:uuid) }
let(:uuid_1_components) { "dependency_scanning-4ff8184cd18485b6e85d5b101e341b12eacd1b3b-33dc9f32c77dde16d39c69d3f78f27ca3114a7c5-#{pipeline.project_id}" }
let(:uuid_2_components) { "dependency_scanning-d55f9e66e79882ae63af9fd55cc822ab75307e31-33dc9f32c77dde16d39c69d3f78f27ca3114a7c5-#{pipeline.project_id}" }
let(:uuid_1) { Gitlab::UUID.v5(uuid_1_components) }
let(:uuid_2) { Gitlab::UUID.v5(uuid_2_components) }
let(:expected_uuids) { [uuid_1, uuid_2, nil] }
it 'sets the UUIDv5 for findings', :aggregate_failures do
expect(finding_uuids).to match_array(expected_uuids)
end
end
end end
end end
...@@ -5,10 +5,12 @@ require 'spec_helper' ...@@ -5,10 +5,12 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::Security::Report do RSpec.describe Gitlab::Ci::Reports::Security::Report do
let_it_be(:pipeline) { create(:ci_pipeline) } let_it_be(:pipeline) { create(:ci_pipeline) }
let(:report) { described_class.new('sast', pipeline, created_at) }
let(:created_at) { 2.weeks.ago } let(:created_at) { 2.weeks.ago }
subject(:report) { described_class.new('sast', pipeline, created_at) }
it { expect(report.type).to eq('sast') } it { expect(report.type).to eq('sast') }
it { is_expected.to delegate_method(:project_id).to(:pipeline) }
describe '#add_scanner' do describe '#add_scanner' do
let(:scanner) { create(:ci_reports_security_scanner, external_id: 'find_sec_bugs') } let(:scanner) { create(:ci_reports_security_scanner, external_id: 'find_sec_bugs') }
......
# 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
...@@ -2,19 +2,6 @@ ...@@ -2,19 +2,6 @@
require 'spec_helper' require 'spec_helper'
UUID_REGEXP = Regexp.new("^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-" \
"([0-9a-f]{2})([0-9a-f]{2})-([0-9a-f]{12})$").freeze
RSpec::Matchers.define :be_uuid_v5 do
match do |string|
expect(string).to be_a(String)
uuid_components = string.downcase.scan(UUID_REGEXP).first
time_hi_and_version = uuid_components[2].to_i(16)
(time_hi_and_version >> 12) == 5
end
end
RSpec.describe Security::StoreReportService, '#execute' do RSpec.describe Security::StoreReportService, '#execute' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:artifact) { create(:ee_ci_job_artifact, trait) } let(:artifact) { create(:ee_ci_job_artifact, trait) }
...@@ -78,12 +65,6 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -78,12 +65,6 @@ RSpec.describe Security::StoreReportService, '#execute' do
it 'inserts all vulnerabilties' do it 'inserts all vulnerabilties' do
expect { subject }.to change { Vulnerability.count }.by(findings) expect { subject }.to change { Vulnerability.count }.by(findings)
end end
it 'calculates UUIDv5 for all findings' do
subject
uuids = Vulnerabilities::Finding.pluck(:uuid)
expect(uuids).to all(be_uuid_v5)
end
end end
context 'invalid data' do context 'invalid data' do
...@@ -149,10 +130,6 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -149,10 +130,6 @@ RSpec.describe Security::StoreReportService, '#execute' do
expect { subject }.to change { Vulnerabilities::Finding.count }.by(32) expect { subject }.to change { Vulnerabilities::Finding.count }.by(32)
end 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 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) expect { subject }.to change { Vulnerabilities::FindingPipeline.where(pipeline: new_pipeline).count }.by(33)
end end
......
# frozen_string_literal: true
module Gitlab
class UUID
NAMESPACE_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
class << self
def v5(name, namespace_id: default_namespace_id)
Digest::UUID.uuid_v5(namespace_id, name)
end
private
def default_namespace_id
@default_namespace_id ||= begin
namespace_uuid = NAMESPACE_IDS.fetch(Rails.env.to_sym)
# Digest::UUID is broken when using a UUID as a 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
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UUID do
let_it_be(:name) { "GitLab" }
describe '.v5' do
subject { described_class.v5(name) }
before do
# This is necessary to clear memoization for testing different environments
described_class.instance_variable_set(:@default_namespace_id, nil)
end
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
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment