Commit 37fcc64f authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch '7586-add_locations_poros_for_vulnerabilities' into 'master'

Add locations POROs for vulnerabilities

See merge request gitlab-org/gitlab-ee!10508
parents 18edd650 9e3e43be
......@@ -50,11 +50,11 @@ module Security
find_params = {
scanner: scanners_objects[occurrence.scanner.key],
primary_identifier: identifiers_objects[occurrence.primary_identifier.key],
location_fingerprint: occurrence.location_fingerprint
location_fingerprint: occurrence.location.fingerprint
}
create_params = occurrence.to_hash
.except(:compare_key, :identifiers, :scanner) # rubocop: disable CodeReuse/ActiveRecord
.except(:compare_key, :identifiers, :location, :scanner) # rubocop: disable CodeReuse/ActiveRecord
begin
project.vulnerabilities
......
......@@ -50,7 +50,7 @@ module Gitlab
report_type: report.type,
name: data['message'],
compare_key: data['cve'],
location_fingerprint: generate_location_fingerprint(data['location']),
location: create_location(data['location']),
severity: parse_level(data['severity']),
confidence: parse_level(data['confidence']),
scanner: scanner,
......@@ -96,7 +96,7 @@ module Gitlab
input.blank? ? 'undefined' : input.downcase
end
def generate_location_fingerprint(location)
def create_location(location_data)
raise NotImplementedError
end
end
......
......@@ -131,8 +131,12 @@ module Gitlab
nil
end
def generate_location_fingerprint(location)
Digest::SHA1.hexdigest("#{location['operating_system']}:#{location.dig('dependency', 'package', 'name')}")
def create_location(location_data)
::Gitlab::Ci::Reports::Security::Locations::ContainerScanning.new(
image: location_data['image'],
operating_system: location_data['operating_system'],
package_name: location_data.dig('dependency', 'package', 'name'),
package_version: location_data.dig('dependency', 'version'))
end
end
end
......
......@@ -45,8 +45,12 @@ module Gitlab
end
end
def generate_location_fingerprint(location)
Digest::SHA1.hexdigest("#{location['path']}:#{location['method']}:#{location['param']}")
def create_location(location_data)
::Gitlab::Ci::Reports::Security::Locations::Dast.new(
hostname: location_data['hostname'],
method_name: location_data['method'],
param: location_data['param'],
path: location_data['path'])
end
end
end
......
......@@ -11,8 +11,11 @@ module Gitlab
private
def generate_location_fingerprint(location)
Digest::SHA1.hexdigest("#{location['file']}:#{location.dig('dependency', 'package', 'name')}")
def create_location(location_data)
::Gitlab::Ci::Reports::Security::Locations::DependencyScanning.new(
file_path: location_data['file'],
package_name: location_data.dig('dependency', 'package', 'name'),
package_version: location_data.dig('dependency', 'version'))
end
end
end
......
......@@ -11,8 +11,13 @@ module Gitlab
private
def generate_location_fingerprint(location)
Digest::SHA1.hexdigest("#{location['file']}:#{location['start_line']}:#{location['end_line']}")
def create_location(location_data)
::Gitlab::Ci::Reports::Security::Locations::Sast.new(
file_path: location_data['file'],
start_line: location_data['start_line'],
end_line: location_data['end_line'],
class_name: location_data['class'],
method_name: location_data['method'])
end
end
end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
module Locations
class Base
include ::Gitlab::Utils::StrongMemoize
def ==(other)
other.fingerprint == fingerprint
end
def fingerprint
strong_memoize(:fingerprint) do
Digest::SHA1.hexdigest(fingerprint_data)
end
end
private
def fingerprint_data
raise NotImplemented
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
module Locations
class ContainerScanning < Base
attr_reader :image
attr_reader :operating_system
attr_reader :package_name
attr_reader :package_version
def initialize(image:, operating_system:, package_name: nil, package_version: nil)
@image = image
@operating_system = operating_system
@package_name = package_name
@package_version = package_version
end
private
def fingerprint_data
"#{operating_system}:#{package_name}"
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
module Locations
class Dast < Base
attr_reader :hostname
attr_reader :method_name
attr_reader :param
attr_reader :path
def initialize(hostname:, method_name:, path:, param: nil)
@hostname = hostname
@method_name = method_name
@param = param
@path = path
end
private
def fingerprint_data
"#{path}:#{method_name}:#{param}"
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
module Locations
class DependencyScanning < Base
attr_reader :file_path
attr_reader :package_name
attr_reader :package_version
def initialize(file_path:, package_name:, package_version: nil)
@file_path = file_path
@package_name = package_name
@package_version = package_version
end
private
def fingerprint_data
"#{file_path}:#{package_name}"
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
module Locations
class Sast < Base
attr_reader :class_name
attr_reader :end_line
attr_reader :file_path
attr_reader :method_name
attr_reader :start_line
def initialize(file_path:, start_line:, end_line: nil, class_name: nil, method_name: nil)
@class_name = class_name
@end_line = end_line
@file_path = file_path
@method_name = method_name
@start_line = start_line
end
private
def fingerprint_data
"#{file_path}:#{start_line}:#{end_line}"
end
end
end
end
end
end
end
......@@ -8,7 +8,7 @@ module Gitlab
attr_reader :compare_key
attr_reader :confidence
attr_reader :identifiers
attr_reader :location_fingerprint
attr_reader :location
attr_reader :metadata_version
attr_reader :name
attr_reader :project_fingerprint
......@@ -18,11 +18,11 @@ module Gitlab
attr_reader :severity
attr_reader :uuid
def initialize(compare_key:, identifiers:, location_fingerprint:, metadata_version:, name:, raw_metadata:, report_type:, scanner:, uuid:, confidence: nil, severity: nil) # rubocop:disable Metrics/ParameterLists
def initialize(compare_key:, identifiers:, location:, metadata_version:, name:, raw_metadata:, report_type:, scanner:, uuid:, confidence: nil, severity: nil) # rubocop:disable Metrics/ParameterLists
@compare_key = compare_key
@confidence = confidence
@identifiers = identifiers
@location_fingerprint = location_fingerprint
@location = location
@metadata_version = metadata_version
@name = name
@raw_metadata = raw_metadata
......@@ -39,7 +39,7 @@ module Gitlab
compare_key
confidence
identifiers
location_fingerprint
location
metadata_version
name
project_fingerprint
......@@ -59,7 +59,7 @@ module Gitlab
def ==(other)
other.report_type == report_type &&
other.location_fingerprint == location_fingerprint &&
other.location == location &&
other.primary_identifier == primary_identifier
end
......
# frozen_string_literal: true
FactoryBot.define do
factory :ci_reports_security_locations_container_scanning, class: ::Gitlab::Ci::Reports::Security::Locations::ContainerScanning do
image 'registry.gitlab.com/my/project:latest'
operating_system 'debian:9'
package_name 'glibc'
package_version '1.2.3'
skip_create
initialize_with do
::Gitlab::Ci::Reports::Security::Locations::ContainerScanning.new(attributes)
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :ci_reports_security_locations_dast, class: ::Gitlab::Ci::Reports::Security::Locations::Dast do
hostname 'my-app.com'
method_name 'GET'
param 'X-Content-Type-Options'
path '/some/path'
skip_create
initialize_with do
::Gitlab::Ci::Reports::Security::Locations::Dast.new(attributes)
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :ci_reports_security_locations_dependency_scanning, class: ::Gitlab::Ci::Reports::Security::Locations::DependencyScanning do
file_path 'app/pom.xml'
package_name 'io.netty/netty'
package_version '1.2.3'
skip_create
initialize_with do
::Gitlab::Ci::Reports::Security::Locations::DependencyScanning.new(attributes)
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :ci_reports_security_locations_sast, class: ::Gitlab::Ci::Reports::Security::Locations::Sast do
file_path 'maven/src/main/java/com/gitlab/security_products/tests/App.java'
start_line 29
end_line 31
class_name 'com.gitlab.security_products.tests.App'
method_name 'insecureCypher'
skip_create
initialize_with do
::Gitlab::Ci::Reports::Security::Locations::Sast.new(attributes)
end
end
end
......@@ -5,7 +5,7 @@ FactoryBot.define do
compare_key 'this_is_supposed_to_be_a_unique_value'
confidence :medium
identifiers { Array.new(1) { FactoryBot.build(:ci_reports_security_identifier) } }
location_fingerprint '4e5b6966dd100170b4b1ad599c7058cce91b57b4'
location factory: :ci_reports_security_locations_sast
metadata_version 'sast:1.0'
name 'Cipher with no integrity'
report_type :sast
......
......@@ -31,10 +31,16 @@ describe Gitlab::Ci::Parsers::Security::ContainerScanning do
expect(report.scanners.length).to eq(1)
end
it "generates expected location fingerprint" do
expected = Digest::SHA1.hexdigest('debian:9:glibc')
expect(report.occurrences.first.location_fingerprint).to eq(expected)
it 'generates expected location' do
location = report.occurrences.first.location
expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::ContainerScanning)
expect(location).to have_attributes(
image: 'registry.gitlab.com/groulot/container-scanning-test/master:5f21de6956aee99ddb68ae49498662d9872f50ff',
operating_system: 'debian:9',
package_name: 'glibc',
package_version: '2.24-11+deb9u3'
)
end
it "generates expected metadata_version" do
......
......@@ -23,12 +23,16 @@ describe Gitlab::Ci::Parsers::Security::Dast do
expect(report.scanners.length).to eq(1)
end
it 'generates expected location fingerprint' do
expected1 = Digest::SHA1.hexdigest(':GET:X-Content-Type-Options')
expected2 = Digest::SHA1.hexdigest('/:GET:X-Content-Type-Options')
expect(report.occurrences.first.location_fingerprint).to eq(expected1)
expect(report.occurrences.last.location_fingerprint).to eq(expected2)
it 'generates expected location' do
location = report.occurrences.first.location
expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::Dast)
expect(location).to have_attributes(
hostname: 'http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io',
method_name: 'GET',
param: 'X-Content-Type-Options',
path: ''
)
end
describe 'occurrence properties' do
......
......@@ -12,10 +12,10 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type) }
let(:parser) { described_class.new }
where(:report_format, :occurrence_count, :identifier_count, :scanner_count, :fingerprint, :version) do
:dependency_scanning | 4 | 7 | 2 | '2773f8cc955346ab1f756b94aa310db8e17c0944' | '1.3'
:dependency_scanning_deprecated | 4 | 7 | 2 | '2773f8cc955346ab1f756b94aa310db8e17c0944' | '1.3'
:dependency_scanning_remediation | 2 | 3 | 1 | '228998b5db51d86d3b091939e2f5873ada0a14a1' | '2.0'
where(:report_format, :occurrence_count, :identifier_count, :scanner_count, :file_path, :package_name, :package_version, :version) do
:dependency_scanning | 4 | 7 | 2 | 'app/pom.xml' | 'io.netty/netty' | '3.9.1.Final' | '1.3'
:dependency_scanning_deprecated | 4 | 7 | 2 | 'app/pom.xml' | 'io.netty/netty' | '3.9.1.Final' | '1.3'
:dependency_scanning_remediation | 2 | 3 | 1 | 'yarn.lock' | 'debug' | '1.0.5' | '2.0'
end
with_them do
......@@ -33,8 +33,15 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do
expect(report.scanners.length).to eq(scanner_count)
end
it "generates expected location fingerprint" do
expect(report.occurrences.first.location_fingerprint).to eq(fingerprint)
it 'generates expected location' do
location = report.occurrences.first.location
expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::DependencyScanning)
expect(location).to have_attributes(
file_path: file_path,
package_name: package_name,
package_version: package_version
)
end
it "generates expected metadata_version" do
......
......@@ -26,8 +26,17 @@ describe Gitlab::Ci::Parsers::Security::Sast do
expect(report.scanners.length).to eq(3)
end
it "generates expected location fingerprint" do
expect(report.occurrences.first.location_fingerprint).to eq('d869ba3f0b3347eb2749135a437dc07c8ae0f420')
it 'generates expected location' do
location = report.occurrences.first.location
expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::Sast)
expect(location).to have_attributes(
file_path: 'python/hardcoded/hardcoded-tmp.py',
start_line: 1,
end_line: 1,
class_name: nil,
method_name: nil
)
end
it "generates expected metadata_version" do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Locations::ContainerScanning do
let(:params) do
{
image: 'registry.gitlab.com/my/project:latest',
operating_system: 'debian:9',
package_name: 'glibc',
package_version: '1.2.3'
}
end
let(:mandatory_params) { %i[image operating_system] }
let(:expected_fingerprint) { Digest::SHA1.hexdigest('debian:9:glibc') }
it_behaves_like 'vulnerability location'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Locations::Dast do
let(:params) do
{
hostname: 'my-app.com',
method_name: 'GET',
param: 'X-Content-Type-Options',
path: '/some/path'
}
end
let(:mandatory_params) { %i[path method_name] }
let(:expected_fingerprint) { Digest::SHA1.hexdigest('/some/path:GET:X-Content-Type-Options') }
it_behaves_like 'vulnerability location'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Locations::DependencyScanning do
let(:params) do
{
file_path: 'app/pom.xml',
package_name: 'io.netty/netty',
package_version: '1.2.3'
}
end
let(:mandatory_params) { %i[file_path package_name] }
let(:expected_fingerprint) { Digest::SHA1.hexdigest('app/pom.xml:io.netty/netty') }
it_behaves_like 'vulnerability location'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Locations::Sast do
let(:params) do
{
file_path: 'src/main/App.java',
start_line: 29,
end_line: 31,
class_name: 'com.gitlab.security_products.tests.App',
method_name: 'insecureCypher'
}
end
let(:mandatory_params) { %i[file_path start_line] }
let(:expected_fingerprint) { Digest::SHA1.hexdigest('src/main/App.java:29:31') }
it_behaves_like 'vulnerability location'
end
......@@ -9,13 +9,14 @@ describe Gitlab::Ci::Reports::Security::Occurrence do
let(:primary_identifier) { create(:ci_reports_security_identifier) }
let(:other_identifier) { create(:ci_reports_security_identifier) }
let(:scanner) { create(:ci_reports_security_scanner) }
let(:location) { create(:ci_reports_security_locations_sast) }
let(:params) do
{
compare_key: 'this_is_supposed_to_be_a_unique_value',
confidence: :medium,
identifiers: [primary_identifier, other_identifier],
location_fingerprint: '4e5b6966dd100170b4b1ad599c7058cce91b57b4',
location: location,
metadata_version: 'sast:1.0',
name: 'Cipher with no integrity',
raw_metadata: 'I am a stringified json object',
......@@ -35,7 +36,7 @@ describe Gitlab::Ci::Reports::Security::Occurrence do
confidence: :medium,
project_fingerprint: '9a73f32d58d87d94e3dc61c4c1a94803f6014258',
identifiers: [primary_identifier, other_identifier],
location_fingerprint: '4e5b6966dd100170b4b1ad599c7058cce91b57b4',
location: location,
metadata_version: 'sast:1.0',
name: 'Cipher with no integrity',
raw_metadata: 'I am a stringified json object',
......@@ -47,7 +48,7 @@ describe Gitlab::Ci::Reports::Security::Occurrence do
end
end
%i[compare_key identifiers location_fingerprint metadata_version name raw_metadata report_type scanner uuid].each do |attribute|
%i[compare_key identifiers location metadata_version name raw_metadata report_type scanner uuid].each do |attribute|
context "when attribute #{attribute} is missing" do
before do
params.delete(attribute)
......@@ -70,7 +71,7 @@ describe Gitlab::Ci::Reports::Security::Occurrence do
compare_key: occurrence.compare_key,
confidence: occurrence.confidence,
identifiers: occurrence.identifiers,
location_fingerprint: occurrence.location_fingerprint,
location: occurrence.location,
metadata_version: occurrence.metadata_version,
name: occurrence.name,
project_fingerprint: occurrence.project_fingerprint,
......@@ -101,22 +102,19 @@ describe Gitlab::Ci::Reports::Security::Occurrence do
let(:identifier) { create(:ci_reports_security_identifier) }
let(:other_identifier) { create(:ci_reports_security_identifier, external_type: 'other_identifier') }
report_type = 'sast'
fingerprint = '4e5b6966dd100170b4b1ad599c7058cce91b57b4'
other_report_type = 'dependency_scanning'
other_fingerprint = '368d8604fb8c0g455d129274f5773aa2f31d4f7q'
where(:report_type_1, :location_fingerprint_1, :identifier_1, :report_type_2, :location_fingerprint_2, :identifier_2, :equal, :case_name) do
report_type | fingerprint | -> { identifier } | report_type | fingerprint | -> { identifier } | true | 'when report_type, location_fingerprint and primary identifier are equal'
report_type | fingerprint | -> { identifier } | other_report_type | fingerprint | -> { identifier } | false | 'when report_type is different'
report_type | fingerprint | -> { identifier } | report_type | other_fingerprint | -> { identifier } | false | 'when location_fingerprint is different'
report_type | fingerprint | -> { identifier } | report_type | fingerprint | -> { other_identifier } | false | 'when primary identifier is different'
let(:location) { create(:ci_reports_security_locations_sast) }
let(:other_location) { create(:ci_reports_security_locations_sast, file_path: 'other/file.rb') }
where(:report_type_1, :location_1, :identifier_1, :report_type_2, :location_2, :identifier_2, :equal, :case_name) do
'sast' | -> { location } | -> { identifier } | 'sast' | -> { location } | -> { identifier } | true | 'when report_type, location and primary identifier are equal'
'sast' | -> { location } | -> { identifier } | 'dependency_scanning' | -> { location } | -> { identifier } | false | 'when report_type is different'
'sast' | -> { location } | -> { identifier } | 'sast' | -> { other_location } | -> { identifier } | false | 'when location is different'
'sast' | -> { location } | -> { identifier } | 'sast' | -> { location } | -> { other_identifier } | false | 'when primary identifier is different'
end
with_them do
let(:occurrence_1) { create(:ci_reports_security_occurrence, report_type: report_type_1, location_fingerprint: location_fingerprint_1, identifiers: [identifier_1.call]) }
let(:occurrence_2) { create(:ci_reports_security_occurrence, report_type: report_type_2, location_fingerprint: location_fingerprint_2, identifiers: [identifier_2.call]) }
let(:occurrence_1) { create(:ci_reports_security_occurrence, report_type: report_type_1, location: location_1.call, identifiers: [identifier_1.call]) }
let(:occurrence_2) { create(:ci_reports_security_occurrence, report_type: report_type_2, location: location_2.call, identifiers: [identifier_2.call]) }
it "returns #{params[:equal]}" do
expect(occurrence_1 == occurrence_2).to eq(equal)
......
# frozen_string_literal: true
shared_examples 'vulnerability location' do
describe '#initialize' do
subject { described_class.new(**params) }
context 'when all params are given' do
it 'initializes an instance' do
expect { subject }.not_to raise_error
expect(subject).to have_attributes(**params)
end
end
where(:param) do
mandatory_params
end
with_them do
context "when param #{params[:param]} is missing" do
before do
params.delete(param)
end
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError)
end
end
end
end
describe '#fingerprint' do
subject { described_class.new(**params).fingerprint }
it "generates expected fingerprint" do
expect(subject).to eq(expected_fingerprint)
end
end
describe '#==' do
let(:location_1) { create(:ci_reports_security_locations_sast) }
let(:location_2) { create(:ci_reports_security_locations_sast) }
subject { location_1 == location_2 }
it "returns true when fingerprints are equal" do
allow(location_1).to receive(:fingerprint).and_return('fingerprint')
allow(location_2).to receive(:fingerprint).and_return('fingerprint')
expect(subject).to eq(true)
end
it "returns false when fingerprints are different" do
allow(location_1).to receive(:fingerprint).and_return('fingerprint')
allow(location_2).to receive(:fingerprint).and_return('another_fingerprint')
expect(subject).to eq(false)
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