Commit ccc96bb6 authored by Mehmet Emin INAC's avatar Mehmet Emin INAC Committed by Mayra Cabrera

Add `Group Name` & `Project Name` into vulnerability export csv

This will introduce new columns in CSV file for project and instance
level exports called `Group Name` and `Project Name` which might sound
redundant for project exports but simplifys the implementation.
parent 82ee4b7f
...@@ -121,18 +121,18 @@ The response will be `404 Not Found` if the vulnerability export is not finished ...@@ -121,18 +121,18 @@ The response will be `404 Not Found` if the vulnerability export is not finished
Example response: Example response:
```csv ```csv
Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE Group Name,Project Name,Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE
container_scanning,Clair,confirmed,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997 Gitlab.org,Defend,container_scanning,Clair,confirmed,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997
container_scanning,Clair,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269
container_scanning,Clair,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001
container_scanning,Clair,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228
container_scanning,Clair,confirmed,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052 Gitlab.org,Defend,container_scanning,Clair,confirmed,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052
container_scanning,Clair,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520
container_scanning,Clair,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869
dependency_scanning,Gemnasium,detected,Regular Expression Denial of Service in debug,,Regular Expression Denial of Service in debug,unknown,yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Regular Expression Denial of Service in debug,,Regular Expression Denial of Service in debug,unknown,yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a
dependency_scanning,Gemnasium,detected,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,unknown,yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98 Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,unknown,yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98
sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:47 Gitlab.org,Defend,sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:47
sast,Find Security Bugs,detected,Cipher with no integrity,,Cipher with no integrity,medium,e6449b89335daf53c0db4c0219bc1634:CIPHER_INTEGRITY:src/main/java/com/gitlab/security_products/tests/App.java:29 Gitlab.org,Defend,sast,Find Security Bugs,detected,Cipher with no integrity,,Cipher with no integrity,medium,e6449b89335daf53c0db4c0219bc1634:CIPHER_INTEGRITY:src/main/java/com/gitlab/security_products/tests/App.java:29
sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,e8ff1d01f74cd372f78da8f5247d3e73:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:41 Gitlab.org,Defend,sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,e8ff1d01f74cd372f78da8f5247d3e73:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:41
sast,Find Security Bugs,confirmed,ECB mode is insecure 2,,ECB mode is insecure,medium,ea0f905fc76f2739d5f10a1fd1e37a10:ECB_MODE:src/main/java/com/gitlab/security_products/tests/App.java:29 Gitlab.org,Defend,sast,Find Security Bugs,confirmed,ECB mode is insecure 2,,ECB mode is insecure,medium,ea0f905fc76f2739d5f10a1fd1e37a10:ECB_MODE:src/main/java/com/gitlab/security_products/tests/App.java:29
``` Gitlab.org,Defend,```
...@@ -258,6 +258,14 @@ module Vulnerabilities ...@@ -258,6 +258,14 @@ module Vulnerabilities
metadata.dig('evidence', 'summary') metadata.dig('evidence', 'summary')
end end
def message
metadata.dig('message')
end
def cve
metadata.dig('cve')
end
alias_method :==, :eql? # eql? is necessary in some cases like array intersection alias_method :==, :eql? # eql? is necessary in some cases like array intersection
def eql?(other) def eql?(other)
......
...@@ -29,6 +29,8 @@ class Vulnerability < ApplicationRecord ...@@ -29,6 +29,8 @@ class Vulnerability < ApplicationRecord
belongs_to :dismissed_by, class_name: 'User' belongs_to :dismissed_by, class_name: 'User'
belongs_to :confirmed_by, class_name: 'User' belongs_to :confirmed_by, class_name: 'User'
has_one :group, through: :project
has_many :findings, class_name: 'Vulnerabilities::Occurrence', inverse_of: :vulnerability has_many :findings, class_name: 'Vulnerabilities::Occurrence', inverse_of: :vulnerability
has_many :issue_links, class_name: 'Vulnerabilities::IssueLink', inverse_of: :vulnerability has_many :issue_links, class_name: 'Vulnerabilities::IssueLink', inverse_of: :vulnerability
has_many :related_issues, through: :issue_links, source: :issue do has_many :related_issues, through: :issue_links, source: :issue do
...@@ -65,8 +67,6 @@ class Vulnerability < ApplicationRecord ...@@ -65,8 +67,6 @@ class Vulnerability < ApplicationRecord
scope :with_states, -> (states) { where(state: states) } scope :with_states, -> (states) { where(state: states) }
scope :counts_by_severity, -> { group(:severity).count } scope :counts_by_severity, -> { group(:severity).count }
delegate :default_branch, to: :project, prefix: :project
def self.counts_by_day_and_severity(num_days_in_past, end_date = Date.current) def self.counts_by_day_and_severity(num_days_in_past, end_date = Date.current)
return [] unless Feature.enabled?(:vulnerability_history, default_enabled: true) return [] unless Feature.enabled?(:vulnerability_history, default_enabled: true)
...@@ -94,7 +94,11 @@ class Vulnerability < ApplicationRecord ...@@ -94,7 +94,11 @@ class Vulnerability < ApplicationRecord
findings.first findings.first
end end
delegate :scanner_name, :metadata, to: :finding, prefix: true, allow_nil: true delegate :scanner_name, :metadata, :message, :cve,
to: :finding, prefix: true, allow_nil: true
delegate :default_branch, :name, to: :project, prefix: true, allow_nil: true
delegate :name, to: :group, prefix: true, allow_nil: true
def resource_parent def resource_parent
project project
......
...@@ -3,6 +3,19 @@ ...@@ -3,6 +3,19 @@
module VulnerabilityExports module VulnerabilityExports
module Exporters module Exporters
class CsvService class CsvService
MAPPING = {
'Group Name' => 'group_name',
'Project Name' => 'project_name',
'Scanner Type' => 'report_type',
'Scanner Name' => 'finding_scanner_name',
'Status' => 'state',
'Vulnerability' => 'title',
'Details' => 'description',
'Additional Info' => 'finding_message',
'Severity' => 'severity',
'CVE' => 'finding_cve'
}.freeze
attr_reader :vulnerabilities attr_reader :vulnerabilities
def initialize(vulnerabilities) def initialize(vulnerabilities)
...@@ -16,20 +29,7 @@ module VulnerabilityExports ...@@ -16,20 +29,7 @@ module VulnerabilityExports
private private
def csv_builder def csv_builder
@csv_builder ||= CsvBuilder.new(vulnerabilities, header_to_value_hash) @csv_builder ||= CsvBuilder.new(vulnerabilities, MAPPING)
end
def header_to_value_hash
{
'Scanner Type' => 'report_type',
'Scanner Name' => 'finding_scanner_name',
'Status' => 'state',
'Vulnerability' => 'title',
'Details' => 'description',
'Additional Info' => -> (vulnerability) { vulnerability.finding_metadata&.fetch('message', nil) },
'Severity' => 'severity',
'CVE' => -> (vulnerability) { vulnerability.finding_metadata&.fetch('cve', nil) }
}
end end
end end
end end
......
---
title: Add `Group Name` and `Project Name` into vulnerability export CSV file
merge_request: 30755
author:
type: added
Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE Group Name,Project Name,Scanner Type,Scanner Name,Status,Vulnerability,Details,Additional Info,Severity,CVE
container_scanning,Clair,detected,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2017-16997 in glibc,,CVE-2017-16997 in glibc,critical,CVE-2017-16997
container_scanning,Clair,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2017-18269 in glibc,,CVE-2017-18269 in glibc,critical,CVE-2017-18269
container_scanning,Clair,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-1000001 in glibc,,CVE-2018-1000001 in glibc,high,CVE-2018-1000001
container_scanning,Clair,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2016-10228 in glibc,,CVE-2016-10228 in glibc,medium,CVE-2016-10228
container_scanning,Clair,detected,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2010-4052 in glibc,,CVE-2010-4052 in glibc,low,CVE-2010-4052
container_scanning,Clair,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-18520 in elfutils,,CVE-2018-18520 in elfutils,low,CVE-2018-18520
container_scanning,Clair,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869 Gitlab.org,Defend,container_scanning,Clair,detected,CVE-2018-16869 in nettle,,CVE-2018-16869 in nettle,unknown,CVE-2018-16869
dependency_scanning,Gemnasium,detected,Regular Expression Denial of Service in debug,,Regular Expression Denial of Service in debug,unknown,yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Regular Expression Denial of Service in debug,,Regular Expression Denial of Service in debug,unknown,yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a
dependency_scanning,Gemnasium,detected,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,unknown,yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98 Gitlab.org,Defend,dependency_scanning,Gemnasium,detected,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,,Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js,unknown,yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98
sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:47 Gitlab.org,Defend,sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:47
sast,Find Security Bugs,detected,Cipher with no integrity,,Cipher with no integrity,medium,e6449b89335daf53c0db4c0219bc1634:CIPHER_INTEGRITY:src/main/java/com/gitlab/security_products/tests/App.java:29 Gitlab.org,Defend,sast,Find Security Bugs,detected,Cipher with no integrity,,Cipher with no integrity,medium,e6449b89335daf53c0db4c0219bc1634:CIPHER_INTEGRITY:src/main/java/com/gitlab/security_products/tests/App.java:29
sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,e8ff1d01f74cd372f78da8f5247d3e73:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:41 Gitlab.org,Defend,sast,Find Security Bugs,detected,Predictable pseudorandom number generator,,Predictable pseudorandom number generator,medium,e8ff1d01f74cd372f78da8f5247d3e73:PREDICTABLE_RANDOM:src/main/java/com/gitlab/security_products/tests/App.java:41
sast,Find Security Bugs,detected,ECB mode is insecure,,ECB mode is insecure,medium,ea0f905fc76f2739d5f10a1fd1e37a10:ECB_MODE:src/main/java/com/gitlab/security_products/tests/App.java:29 Gitlab.org,Defend,sast,Find Security Bugs,detected,ECB mode is insecure,,ECB mode is insecure,medium,ea0f905fc76f2739d5f10a1fd1e37a10:ECB_MODE:src/main/java/com/gitlab/security_products/tests/App.java:29
...@@ -588,4 +588,22 @@ describe Vulnerabilities::Occurrence do ...@@ -588,4 +588,22 @@ describe Vulnerabilities::Occurrence do
expect(occurrence.evidence).to be_nil expect(occurrence.evidence).to be_nil
end end
end end
describe '#message' do
let(:occurrence) { build(:vulnerabilities_occurrence) }
let(:expected_message) { occurrence.metadata['message'] }
subject { occurrence.message }
it { is_expected.to eql(expected_message) }
end
describe '#cve' do
let(:occurrence) { build(:vulnerabilities_occurrence) }
let(:expected_cve) { occurrence.metadata['cve'] }
subject { occurrence.cve }
it { is_expected.to eql(expected_cve) }
end
end end
...@@ -39,6 +39,8 @@ describe Vulnerability do ...@@ -39,6 +39,8 @@ describe Vulnerability do
it { is_expected.to belong_to(:dismissed_by).class_name('User') } it { is_expected.to belong_to(:dismissed_by).class_name('User') }
it { is_expected.to belong_to(:confirmed_by).class_name('User') } it { is_expected.to belong_to(:confirmed_by).class_name('User') }
it { is_expected.to have_one(:group).through(:project) }
it { is_expected.to have_many(:findings).class_name('Vulnerabilities::Occurrence').dependent(false) } it { is_expected.to have_many(:findings).class_name('Vulnerabilities::Occurrence').dependent(false) }
it { is_expected.to have_many(:notes).dependent(:delete_all) } it { is_expected.to have_many(:notes).dependent(:delete_all) }
it { is_expected.to have_many(:user_mentions).class_name('VulnerabilityUserMention') } it { is_expected.to have_many(:user_mentions).class_name('VulnerabilityUserMention') }
...@@ -256,23 +258,14 @@ describe Vulnerability do ...@@ -256,23 +258,14 @@ describe Vulnerability do
end end
end end
describe '#finding_scanner_name' do describe 'delegations' do
let_it_be(:project) { create(:project, :with_vulnerability) } it { is_expected.to delegate_method(:scanner_name).to(:finding).with_prefix.allow_nil }
let_it_be(:vulnerability) { project.vulnerabilities.first } it { is_expected.to delegate_method(:metadata).to(:finding).with_prefix.allow_nil }
let_it_be(:finding) { create(:vulnerabilities_occurrence, vulnerability: vulnerability) } it { is_expected.to delegate_method(:message).to(:finding).with_prefix.allow_nil }
it { is_expected.to delegate_method(:cve).to(:finding).with_prefix.allow_nil }
subject(:finding_scanner_name) { vulnerability.finding_scanner_name } it { is_expected.to delegate_method(:default_branch).to(:project).with_prefix.allow_nil }
it { is_expected.to delegate_method(:name).to(:project).with_prefix.allow_nil }
it { is_expected.to eq(finding.scanner_name) } it { is_expected.to delegate_method(:name).to(:group).with_prefix.allow_nil }
end
describe '#project_default_branch' do
let_it_be(:project) { create(:project, :repository, :with_vulnerability) }
let_it_be(:vulnerability) { project.vulnerabilities.first }
subject { vulnerability.project_default_branch }
it { is_expected.to eq("master") }
end end
describe '#resolved_on_default_branch' do describe '#resolved_on_default_branch' do
......
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
require 'spec_helper' require 'spec_helper'
describe VulnerabilityExports::Exporters::CsvService do describe VulnerabilityExports::Exporters::CsvService do
let_it_be(:project) { create(:project, :public) }
let_it_be(:vulnerability) { create(:vulnerability, :with_findings, project: project) }
let(:export_csv_service) { described_class.new(Vulnerability.all) } let(:export_csv_service) { described_class.new(Vulnerability.all) }
subject(:csv) { CSV.parse(export_csv_service.generate, headers: true) } subject(:csv) { CSV.parse(export_csv_service.generate, headers: true) }
...@@ -23,20 +20,45 @@ describe VulnerabilityExports::Exporters::CsvService do ...@@ -23,20 +20,45 @@ describe VulnerabilityExports::Exporters::CsvService do
end end
it 'includes the columns required for import' do it 'includes the columns required for import' do
expect(csv.headers).to include('Scanner Type', 'Scanner Name', 'Status', 'Vulnerability', 'Details', expect(csv.headers).to contain_exactly('Group Name', 'Project Name', 'Scanner Type', 'Scanner Name', 'Status',
'Additional Info', 'Severity', 'CVE') 'Vulnerability', 'Details', 'Additional Info', 'Severity', 'CVE')
end end
it 'includes proper values for each column type' do context 'when a project belongs to a group' do
aggregate_failures do let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:vulnerability) { create(:vulnerability, :with_findings, project: project) }
it 'includes proper values for each column type', :aggregate_failures do
expect(csv[0]['Group Name']).to eq group.name
expect(csv[0]['Project Name']).to eq project.name
expect(csv[0]['Scanner Type']).to eq vulnerability.report_type
expect(csv[0]['Scanner Name']).to eq vulnerability.finding_scanner_name
expect(csv[0]['Status']).to eq vulnerability.state
expect(csv[0]['Vulnerability']).to eq vulnerability.title
expect(csv[0]['Details']).to eq vulnerability.description
expect(csv[0]['Additional Info']).to eq vulnerability.finding_message
expect(csv[0]['Severity']).to eq vulnerability.severity
expect(csv[0]['CVE']).to eq vulnerability.finding_cve
end
end
context 'when a project belongs to a user' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, namespace: user.namespace ) }
let_it_be(:vulnerability) { create(:vulnerability, :with_findings, project: project) }
it 'includes proper values for each column except group name' do
expect(csv[0]['Group Name']).to be_nil
expect(csv[0]['Project Name']).to eq project.name
expect(csv[0]['Scanner Type']).to eq vulnerability.report_type expect(csv[0]['Scanner Type']).to eq vulnerability.report_type
expect(csv[0]['Scanner Name']).to eq vulnerability.finding_scanner_name expect(csv[0]['Scanner Name']).to eq vulnerability.finding_scanner_name
expect(csv[0]['Status']).to eq vulnerability.state expect(csv[0]['Status']).to eq vulnerability.state
expect(csv[0]['Vulnerability']).to eq vulnerability.title expect(csv[0]['Vulnerability']).to eq vulnerability.title
expect(csv[0]['Details']).to eq vulnerability.description expect(csv[0]['Details']).to eq vulnerability.description
expect(csv[0]['Additional Info']).to eq vulnerability.finding_metadata['message'] expect(csv[0]['Additional Info']).to eq vulnerability.finding_message
expect(csv[0]['Severity']).to eq vulnerability.severity expect(csv[0]['Severity']).to eq vulnerability.severity
expect(csv[0]['CVE']).to eq vulnerability.finding_metadata['cve'] expect(csv[0]['CVE']).to eq vulnerability.finding_cve
end end
end 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