Commit 695389c6 authored by Cameron Swords's avatar Cameron Swords Committed by Sean McGivern

Parse analyzer information from Secure reports

parent b9bd0a86
...@@ -25,6 +25,8 @@ module Gitlab ...@@ -25,6 +25,8 @@ module Gitlab
create_scanner create_scanner
create_scan create_scan
create_analyzer
set_report_version
collate_remediations.each { |vulnerability| create_vulnerability(vulnerability) } collate_remediations.each { |vulnerability| create_vulnerability(vulnerability) }
report_data report_data
...@@ -67,6 +69,10 @@ module Gitlab ...@@ -67,6 +69,10 @@ module Gitlab
@scan_data ||= report_data.dig('scan') @scan_data ||= report_data.dig('scan')
end end
def analyzer_data
@analyzer_data ||= report_data.dig('scan', 'analyzer')
end
# map remediations to relevant vulnerabilities # map remediations to relevant vulnerabilities
def collate_remediations def collate_remediations
return report_data["vulnerabilities"] || [] unless report_data["remediations"] return report_data["vulnerabilities"] || [] unless report_data["remediations"]
...@@ -166,6 +172,25 @@ module Gitlab ...@@ -166,6 +172,25 @@ module Gitlab
report.scan = ::Gitlab::Ci::Reports::Security::Scan.new(scan_data) report.scan = ::Gitlab::Ci::Reports::Security::Scan.new(scan_data)
end end
def set_report_version
report.version = report_version
end
def create_analyzer
return unless analyzer_data.is_a?(Hash)
params = {
id: analyzer_data.dig('id'),
name: analyzer_data.dig('name'),
version: analyzer_data.dig('version'),
vendor: analyzer_data.dig('vendor', 'name')
}
return unless params.values.all?
report.analyzer = ::Gitlab::Ci::Reports::Security::Analyzer.new(**params)
end
def create_scanner(scanner_data = top_level_scanner) def create_scanner(scanner_data = top_level_scanner)
return unless scanner_data.is_a?(Hash) return unless scanner_data.is_a?(Hash)
...@@ -173,7 +198,8 @@ module Gitlab ...@@ -173,7 +198,8 @@ module Gitlab
::Gitlab::Ci::Reports::Security::Scanner.new( ::Gitlab::Ci::Reports::Security::Scanner.new(
external_id: scanner_data['id'], external_id: scanner_data['id'],
name: scanner_data['name'], name: scanner_data['name'],
vendor: scanner_data.dig('vendor', 'name'))) vendor: scanner_data.dig('vendor', 'name'),
version: scanner_data.dig('version')))
end end
def create_identifiers(identifiers) def create_identifiers(identifiers)
......
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
class Analyzer
attr_reader :id, :name, :version, :vendor
def initialize(id:, name:, version:, vendor:)
@id = id
@name = name
@version = version
@vendor = vendor
end
end
end
end
end
end
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
module Security module Security
class Report class Report
attr_reader :created_at, :type, :pipeline, :findings, :scanners, :identifiers attr_reader :created_at, :type, :pipeline, :findings, :scanners, :identifiers
attr_accessor :scan, :scanned_resources, :errors attr_accessor :scan, :scanned_resources, :errors, :analyzer, :version
delegate :project_id, to: :pipeline delegate :project_id, to: :pipeline
......
...@@ -15,14 +15,15 @@ module Gitlab ...@@ -15,14 +15,15 @@ module Gitlab
"semgrep" => 2 "semgrep" => 2
}.freeze }.freeze
attr_accessor :external_id, :name, :vendor attr_accessor :external_id, :name, :vendor, :version
alias_method :key, :external_id alias_method :key, :external_id
def initialize(external_id:, name:, vendor:) def initialize(external_id:, name:, vendor:, version:)
@external_id = external_id @external_id = external_id
@name = name @name = name
@vendor = vendor @vendor = vendor
@version = version
end end
def to_hash def to_hash
......
...@@ -5,6 +5,7 @@ FactoryBot.define do ...@@ -5,6 +5,7 @@ FactoryBot.define do
external_id { 'find_sec_bugs' } external_id { 'find_sec_bugs' }
name { 'Find Security Bugs' } name { 'Find Security Bugs' }
vendor { 'Security Scanner Vendor' } vendor { 'Security Scanner Vendor' }
version { '1.0.0' }
skip_create skip_create
......
...@@ -133,6 +133,15 @@ ...@@ -133,6 +133,15 @@
], ],
"dependency_files": [], "dependency_files": [],
"scan": { "scan": {
"analyzer": {
"id": "common-analyzer",
"name": "Common Analyzer",
"url": "https://site.com/analyzer/common",
"version": "2.0.1",
"vendor": {
"name": "Common"
}
},
"scanner": { "scanner": {
"id": "gemnasium", "id": "gemnasium",
"name": "Gemnasium", "name": "Gemnasium",
...@@ -146,5 +155,6 @@ ...@@ -146,5 +155,6 @@
"start_time": "placeholder-value", "start_time": "placeholder-value",
"end_time": "placeholder-value", "end_time": "placeholder-value",
"status": "success" "status": "success"
} },
"version": "14.0.2"
} }
...@@ -195,6 +195,22 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -195,6 +195,22 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
end end
describe 'top-level scanner' do
it 'is the primary scanner' do
expect(report.primary_scanner.external_id).to eq('gemnasium')
expect(report.primary_scanner.name).to eq('Gemnasium')
expect(report.primary_scanner.vendor).to eq('GitLab')
expect(report.primary_scanner.version).to eq('2.18.0')
end
it 'returns nil report has no scanner' do
empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
described_class.parse!({}.to_json, empty_report)
expect(empty_report.primary_scanner).to be_nil
end
end
describe 'parsing scanners' do describe 'parsing scanners' do
subject(:scanner) { report.findings.first.scanner } subject(:scanner) { report.findings.first.scanner }
...@@ -225,6 +241,35 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -225,6 +241,35 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
end end
describe 'parsing schema version' do
it 'parses the version' do
expect(report.version).to eq('14.0.2')
end
it 'returns nil when there is no version' do
empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
described_class.parse!({}.to_json, empty_report)
expect(empty_report.version).to be_nil
end
end
describe 'parsing analyzer' do
it 'associates analyzer with report' do
expect(report.analyzer.id).to eq('common-analyzer')
expect(report.analyzer.name).to eq('Common Analyzer')
expect(report.analyzer.version).to eq('2.0.1')
expect(report.analyzer.vendor).to eq('Common')
end
it 'returns nil when analyzer data is not available' do
empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
described_class.parse!({}.to_json, empty_report)
expect(empty_report.analyzer).to be_nil
end
end
describe '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)
......
...@@ -10,7 +10,8 @@ RSpec.describe Gitlab::Ci::Reports::Security::Scanner do ...@@ -10,7 +10,8 @@ RSpec.describe Gitlab::Ci::Reports::Security::Scanner do
{ {
external_id: 'brakeman', external_id: 'brakeman',
name: 'Brakeman', name: 'Brakeman',
vendor: 'GitLab' vendor: 'GitLab',
version: '1.0.1'
} }
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