Commit bd93f764 authored by Mehmet Emin INAC's avatar Mehmet Emin INAC

Change the migration to do not depend on repor files

parent e649fc11
...@@ -87,30 +87,21 @@ module EE ...@@ -87,30 +87,21 @@ module EE
has_one :route, as: :source has_one :route, as: :source
has_many :vulnerabilities has_many :vulnerabilities
has_many :vulnerability_findings
has_many :vulnerability_identifiers
has_many :vulnerability_scanners
def self.polymorphic_name def self.polymorphic_name
'Project' 'Project'
end end
def reports def resolved_vulnerabilities
return [] unless default_branch return Vulnerability.none unless latest_pipeline_id
@reports ||= artifacts.flat_map(&:reports) vulnerabilities.not_found_in_pipeline_id(latest_pipeline_id)
end end
private private
delegate :connection, to: :'self.class', private: true delegate :connection, to: :'self.class', private: true
def artifacts
return [] unless latest_pipeline_id
JobArtifact.for_pipeline(latest_pipeline_id).each { |artifact| artifact.project = self }
end
def latest_pipeline_id def latest_pipeline_id
strong_memoize(:latest_pipeline_id) { pipeline_with_reports&.fetch('id') } strong_memoize(:latest_pipeline_id) { pipeline_with_reports&.fetch('id') }
end end
...@@ -204,140 +195,17 @@ module EE ...@@ -204,140 +195,17 @@ module EE
end end
end end
class JobArtifact < ActiveRecord::Base
ARTIFACTS_SQL = <<~SQL
SELECT
"ci_job_artifacts".*
FROM "ci_job_artifacts"
INNER JOIN "ci_builds" ON "ci_job_artifacts"."job_id" = "ci_builds"."id"
AND "ci_builds"."commit_id" = %{commit_id}
AND "ci_builds"."type" = 'Ci::Build'
AND ("ci_builds"."retried" IS FALSE OR "ci_builds"."retried" IS NULL)
WHERE
"ci_job_artifacts"."file_type" IN (%{file_types})
SQL
FILE_FORMAT_ADAPTERS = {
gzip: ::Gitlab::Ci::Build::Artifacts::Adapters::GzipStream,
raw: ::Gitlab::Ci::Build::Artifacts::Adapters::RawStream
}.freeze
self.table_name = 'ci_job_artifacts'
enum file_format: {
raw: 1,
zip: 2,
gzip: 3
}, _suffix: true
enum file_location: {
legacy_path: 1,
hashed_path: 2
}
enum file_type: {
archive: 1,
metadata: 2,
trace: 3,
junit: 4,
sast: 5, ## EE-specific
dependency_scanning: 6, ## EE-specific
container_scanning: 7, ## EE-specific
dast: 8, ## EE-specific
codequality: 9, ## EE-specific
license_management: 10, ## EE-specific
license_scanning: 101, ## EE-specific till 13.0
performance: 11, ## EE-specific till 13.2
metrics: 12, ## EE-specific
metrics_referee: 13, ## runner referees
network_referee: 14, ## runner referees
lsif: 15, # LSIF data for code navigation
dotenv: 16,
cobertura: 17,
terraform: 18, # Transformed json
accessibility: 19,
cluster_applications: 20,
secret_detection: 21, ## EE-specific
requirements: 22, ## EE-specific
coverage_fuzzing: 23, ## EE-specific
browser_performance: 24, ## EE-specific
load_performance: 25 ## EE-specific
}
mount_uploader :file, JobArtifactUploader
attr_accessor :project
delegate :namespace, to: :project
def self.for_pipeline(pipeline_id)
find_by_sql(artifacts_sql_for(pipeline_id))
end
def self.artifacts_sql_for(pipeline_id)
format(ARTIFACTS_SQL, commit_id: pipeline_id, file_types: Project::FILE_TYPES.join(', '))
end
def reports
reports = []
each_blob do |blob|
report = ::Gitlab::Ci::Reports::Security::Report.new(file_type, nil, created_at)
parse_security_artifact_blob(report, blob)
reports << report
end
reports
end
def hashed_path?
super || file_location.nil?
end
private
def each_blob(&blk)
unless file_format_adapter_class
raise NotSupportedAdapterError, 'This file format requires a dedicated adapter'
end
file.open do |stream|
file_format_adapter_class.new(stream).each_blob(&blk)
end
end
def file_format_adapter_class
FILE_FORMAT_ADAPTERS[file_format.to_sym]
end
def parse_security_artifact_blob(security_report, blob)
report_clone = security_report.clone_as_blank
::Gitlab::Ci::Parsers.fabricate!(security_report.type).parse!(blob, report_clone)
security_report.merge!(report_clone)
end
end
class Route < ActiveRecord::Base; end class Route < ActiveRecord::Base; end
class Vulnerability < ActiveRecord::Base class Vulnerability < ActiveRecord::Base
include EachBatch include EachBatch
scope :id_not_in, -> (ids) { where.not(id: ids) } scope :not_found_in_pipeline_id, -> (pipeline_id) { where.not(id: found_in_pipeline(pipeline_id)) }
end scope :found_in_pipeline, -> (pipeline_id) do
class VulnerabilityFinding < ActiveRecord::Base joins(<<~SQL)
self.table_name = 'vulnerability_occurrences' INNER JOIN vulnerability_occurrences vo ON vo.vulnerability_id = vulnerabilities.id
INNER JOIN vulnerability_occurrence_pipelines vop ON vop.occurrence_id = vo.id AND vop.pipeline_id = #{pipeline_id}
attribute(:project_fingerprint, ::Gitlab::Database::ShaAttribute.new) SQL
attribute(:location_fingerprint, ::Gitlab::Database::ShaAttribute.new) end
belongs_to :scanner, class_name: 'VulnerabilityScanner'
belongs_to :primary_identifier, class_name: 'VulnerabilityIdentifier'
end
class VulnerabilityScanner < ActiveRecord::Base
scope :by_external_id, -> (external_ids) { where(external_id: external_ids) }
end
class VulnerabilityIdentifier < ActiveRecord::Base
attribute(:fingerprint, ::Gitlab::Database::ShaAttribute.new)
scope :by_fingerprint, -> (fingerprints) { where(fingerprint: fingerprints) }
end end
# This class depends on following classes # This class depends on following classes
...@@ -410,11 +278,8 @@ module EE ...@@ -410,11 +278,8 @@ module EE
attr_accessor :project_id attr_accessor :project_id
delegate :reports, to: :project, private: true
def update_vulnerabilities def update_vulnerabilities
@updated_count ||= project.vulnerabilities @updated_count ||= project.resolved_vulnerabilities
.id_not_in(existing_vulnerability_ids)
.update_all(resolved_on_default_branch: true) .update_all(resolved_on_default_branch: true)
end end
...@@ -422,20 +287,11 @@ module EE ...@@ -422,20 +287,11 @@ module EE
::Gitlab::BackgroundMigration::Logger.info( ::Gitlab::BackgroundMigration::Logger.info(
migrator: 'PopulateResolvedOnDefaultBranchColumnForProject', migrator: 'PopulateResolvedOnDefaultBranchColumnForProject',
message: 'Project migrated', message: 'Project migrated',
stats: stats, updated_count: @updated_count,
project_id: project_id project_id: project_id
) )
end end
def stats
{
all_count: findings.length,
valid_count: all_valid_findings.length,
existing_count: existing_vulnerability_ids.length,
updated_count: @updated_count
}
end
def log_error(error) def log_error(error)
::Gitlab::BackgroundMigration::Logger.error( ::Gitlab::BackgroundMigration::Logger.error(
migrator: 'PopulateResolvedOnDefaultBranchColumnForProject', migrator: 'PopulateResolvedOnDefaultBranchColumnForProject',
...@@ -447,52 +303,6 @@ module EE ...@@ -447,52 +303,6 @@ module EE
def project def project
@project ||= Project.find(project_id) @project ||= Project.find(project_id)
end end
def existing_vulnerability_ids
@existing_vulnerability_ids ||= all_valid_findings.map { |finding| find_saved_finding_for(finding)&.vulnerability_id }.compact
end
def all_valid_findings
@all_valid_findings ||= findings.select(&:scanner)
.select(&:primary_identifier)
.select(&:location)
end
def findings
@findings ||= reports.flat_map(&:findings)
end
def find_saved_finding_for(finding)
project.vulnerability_findings.find_by({
scanner: scanner_objects[finding.scanner.key],
primary_identifier: identifier_objects[finding.primary_identifier.key],
location_fingerprint: finding.location.fingerprint
})
end
def scanner_objects
@scanner_objects ||= project.vulnerability_scanners.by_external_id(all_scanner_external_ids).group_by(&:external_id)
end
def all_scanner_external_ids
all_scanners.map(&:external_id).uniq
end
def all_scanners
reports.map(&:scanners).flat_map(&:values)
end
def identifier_objects
@identifier_objects ||= project.vulnerability_identifiers.by_fingerprint(all_identifier_fingerprints).group_by(&:fingerprint)
end
def all_identifier_fingerprints
all_identifiers.map(&:fingerprint).uniq
end
def all_identifiers
reports.map(&:identifiers).flat_map(&:values)
end
end end
end end
end end
......
...@@ -9,7 +9,9 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol ...@@ -9,7 +9,9 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol
let(:pipelines) { table(:ci_pipelines) } let(:pipelines) { table(:ci_pipelines) }
let(:vulnerabilities) { table(:vulnerabilities) } let(:vulnerabilities) { table(:vulnerabilities) }
let(:findings) { table(:vulnerability_occurrences) } let(:findings) { table(:vulnerability_occurrences) }
let(:finding_pipelines) { table(:vulnerability_occurrence_pipelines) }
let(:builds) { table(:ci_builds) } let(:builds) { table(:ci_builds) }
let(:artifacts) { table(:ci_job_artifacts) }
let(:scanners) { table(:vulnerability_scanners) } let(:scanners) { table(:vulnerability_scanners) }
let(:vulnerability_identifiers) { table(:vulnerability_identifiers) } let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
...@@ -60,8 +62,6 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol ...@@ -60,8 +62,6 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol
let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a', status: 'success') } let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a', status: 'success') }
let(:utility_object) { described_class.new(project.id) } let(:utility_object) { described_class.new(project.id) }
let(:scanner) { scanners.create!(project_id: project.id, external_id: 'bandit', name: 'Bandit') } let(:scanner) { scanners.create!(project_id: project.id, external_id: 'bandit', name: 'Bandit') }
let(:artifact_model) { EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn::JobArtifact }
let(:artifact_fixture_path) { Rails.root.join('ee/spec/fixtures/security_reports/master/gl-sast-report.json') }
let(:sha_attribute) { Gitlab::Database::ShaAttribute.new } let(:sha_attribute) { Gitlab::Database::ShaAttribute.new }
let(:vulnerability_identifier) do let(:vulnerability_identifier) do
vulnerability_identifiers.create!( vulnerability_identifiers.create!(
...@@ -98,11 +98,9 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol ...@@ -98,11 +98,9 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol
before do before do
build = builds.create!(commit_id: pipeline.id, retried: false, type: 'Ci::Build') build = builds.create!(commit_id: pipeline.id, retried: false, type: 'Ci::Build')
artifact = artifact_model.new(project_id: project.id, job_id: build.id, file_type: 5, file_format: 1) artifacts.create!(project_id: project.id, job_id: build.id, file_type: 5, file_format: 1)
artifact.file = fixture_file_upload(artifact_fixture_path, 'application/json')
artifact.save!
findings.create!( finding = findings.create!(
project_id: project.id, project_id: project.id,
vulnerability_id: existing_vulnerability.id, vulnerability_id: existing_vulnerability.id,
severity: 5, severity: 5,
...@@ -117,6 +115,8 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol ...@@ -117,6 +115,8 @@ RSpec.describe ::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchCol
metadata_version: '1', metadata_version: '1',
raw_metadata: '') raw_metadata: '')
finding_pipelines.create!(occurrence_id: finding.id, pipeline_id: pipeline.id)
allow(::Gitlab::CurrentSettings).to receive(:default_branch_name).and_return(:master) allow(::Gitlab::CurrentSettings).to receive(:default_branch_name).and_return(:master)
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