Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
56fcaba4
Commit
56fcaba4
authored
Apr 15, 2021
by
James Johnson
Committed by
Dmitry Gruzd
Apr 15, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improve Vulnerability Tracking: Use Signatures [RUN ALL RSPEC] [RUN AS-IF-FOSS]
parent
d61c5521
Changes
34
Show whitespace changes
Inline
Side-by-side
Showing
34 changed files
with
2275 additions
and
1688 deletions
+2275
-1688
app/models/concerns/vulnerability_finding_helpers.rb
app/models/concerns/vulnerability_finding_helpers.rb
+7
-0
app/models/concerns/vulnerability_finding_signature_helpers.rb
...odels/concerns/vulnerability_finding_signature_helpers.rb
+7
-0
ee/app/finders/security/findings_finder.rb
ee/app/finders/security/findings_finder.rb
+5
-1
ee/app/finders/security/pipeline_vulnerabilities_finder.rb
ee/app/finders/security/pipeline_vulnerabilities_finder.rb
+27
-6
ee/app/models/concerns/ee/vulnerability_finding_helpers.rb
ee/app/models/concerns/ee/vulnerability_finding_helpers.rb
+39
-0
ee/app/models/concerns/ee/vulnerability_finding_signature_helpers.rb
...ls/concerns/ee/vulnerability_finding_signature_helpers.rb
+30
-0
ee/app/models/license.rb
ee/app/models/license.rb
+1
-0
ee/app/models/vulnerabilities/finding.rb
ee/app/models/vulnerabilities/finding.rb
+11
-6
ee/app/models/vulnerabilities/finding_signature.rb
ee/app/models/vulnerabilities/finding_signature.rb
+12
-0
ee/app/services/security/store_report_service.rb
ee/app/services/security/store_report_service.rb
+76
-6
ee/config/feature_flags/development/vulnerability_finding_signatures.yml
...re_flags/development/vulnerability_finding_signatures.yml
+8
-0
ee/lib/gitlab/ci/parsers/security/common.rb
ee/lib/gitlab/ci/parsers/security/common.rb
+34
-19
ee/lib/gitlab/ci/reports/security/finding.rb
ee/lib/gitlab/ci/reports/security/finding.rb
+21
-6
ee/lib/gitlab/ci/reports/security/finding_signature.rb
ee/lib/gitlab/ci/reports/security/finding_signature.rb
+18
-1
ee/lib/gitlab/ci/reports/security/locations/container_scanning.rb
...itlab/ci/reports/security/locations/container_scanning.rb
+2
-2
ee/lib/gitlab/ci/reports/security/locations/coverage_fuzzing.rb
.../gitlab/ci/reports/security/locations/coverage_fuzzing.rb
+0
-2
ee/lib/gitlab/ci/reports/security/locations/dast.rb
ee/lib/gitlab/ci/reports/security/locations/dast.rb
+0
-2
ee/lib/gitlab/ci/reports/security/locations/dependency_scanning.rb
...tlab/ci/reports/security/locations/dependency_scanning.rb
+0
-2
ee/lib/gitlab/ci/reports/security/locations/sast.rb
ee/lib/gitlab/ci/reports/security/locations/sast.rb
+0
-2
ee/lib/gitlab/ci/reports/security/locations/secret_detection.rb
.../gitlab/ci/reports/security/locations/secret_detection.rb
+0
-2
ee/lib/gitlab/ci/reports/security/vulnerability_reports_comparer.rb
...lab/ci/reports/security/vulnerability_reports_comparer.rb
+27
-9
ee/spec/factories/ci/reports/security/findings.rb
ee/spec/factories/ci/reports/security/findings.rb
+1
-0
ee/spec/factories/vulnerabilities/feedback.rb
ee/spec/factories/vulnerabilities/feedback.rb
+1
-0
ee/spec/finders/security/pipeline_vulnerabilities_finder_spec.rb
.../finders/security/pipeline_vulnerabilities_finder_spec.rb
+102
-45
ee/spec/lib/gitlab/ci/parsers/security/common_spec.rb
ee/spec/lib/gitlab/ci/parsers/security/common_spec.rb
+192
-159
ee/spec/lib/gitlab/ci/reports/security/finding_signature_spec.rb
.../lib/gitlab/ci/reports/security/finding_signature_spec.rb
+14
-8
ee/spec/lib/gitlab/ci/reports/security/finding_spec.rb
ee/spec/lib/gitlab/ci/reports/security/finding_spec.rb
+122
-59
ee/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb
...i/reports/security/vulnerability_reports_comparer_spec.rb
+85
-80
ee/spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb
.../concerns/vulnerability_finding_signature_helpers_spec.rb
+32
-0
ee/spec/models/vulnerabilities/finding_spec.rb
ee/spec/models/vulnerabilities/finding_spec.rb
+853
-739
ee/spec/requests/api/vulnerability_findings_spec.rb
ee/spec/requests/api/vulnerability_findings_spec.rb
+2
-1
ee/spec/serializers/vulnerabilities/feedback_entity_spec.rb
ee/spec/serializers/vulnerabilities/feedback_entity_spec.rb
+1
-1
ee/spec/services/ci/compare_security_reports_service_spec.rb
ee/spec/services/ci/compare_security_reports_service_spec.rb
+212
-205
ee/spec/services/security/store_report_service_spec.rb
ee/spec/services/security/store_report_service_spec.rb
+333
-325
No files found.
app/models/concerns/vulnerability_finding_helpers.rb
0 → 100644
View file @
56fcaba4
# frozen_string_literal: true
module
VulnerabilityFindingHelpers
extend
ActiveSupport
::
Concern
end
VulnerabilityFindingHelpers
.
prepend_if_ee
(
'EE::VulnerabilityFindingHelpers'
)
app/models/concerns/vulnerability_finding_signature_helpers.rb
0 → 100644
View file @
56fcaba4
# frozen_string_literal: true
module
VulnerabilityFindingSignatureHelpers
extend
ActiveSupport
::
Concern
end
VulnerabilityFindingSignatureHelpers
.
prepend_if_ee
(
'EE::VulnerabilityFindingSignatureHelpers'
)
ee/app/finders/security/findings_finder.rb
View file @
56fcaba4
...
...
@@ -47,10 +47,13 @@ module Security
report_finding
=
report_finding_for
(
security_finding
)
return
Vulnerabilities
::
Finding
.
new
unless
report_finding
finding_data
=
report_finding
.
to_hash
.
except
(
:compare_key
,
:identifiers
,
:location
,
:scanner
,
:links
)
finding_data
=
report_finding
.
to_hash
.
except
(
:compare_key
,
:identifiers
,
:location
,
:scanner
,
:links
,
:signatures
)
identifiers
=
report_finding
.
identifiers
.
map
do
|
identifier
|
Vulnerabilities
::
Identifier
.
new
(
identifier
.
to_hash
)
end
signatures
=
report_finding
.
signatures
.
map
do
|
signature
|
Vulnerabilities
::
FindingSignature
.
new
(
signature
.
to_hash
)
end
Vulnerabilities
::
Finding
.
new
(
finding_data
).
tap
do
|
finding
|
finding
.
location_fingerprint
=
report_finding
.
location
.
fingerprint
...
...
@@ -59,6 +62,7 @@ module Security
finding
.
sha
=
pipeline
.
sha
finding
.
scanner
=
security_finding
.
scanner
finding
.
identifiers
=
identifiers
finding
.
signatures
=
signatures
end
end
...
...
ee/app/finders/security/pipeline_vulnerabilities_finder.rb
View file @
56fcaba4
...
...
@@ -75,7 +75,7 @@ module Security
def
normalize_report_findings
(
report_findings
,
vulnerabilities
)
report_findings
.
map
do
|
report_finding
|
finding_hash
=
report_finding
.
to_hash
.
except
(
:compare_key
,
:identifiers
,
:location
,
:scanner
,
:links
)
.
except
(
:compare_key
,
:identifiers
,
:location
,
:scanner
,
:links
,
:signatures
)
finding
=
Vulnerabilities
::
Finding
.
new
(
finding_hash
)
# assigning Vulnerabilities to Findings to enable the computed state
...
...
@@ -90,6 +90,9 @@ module Security
finding
.
identifiers
=
report_finding
.
identifiers
.
map
do
|
identifier
|
Vulnerabilities
::
Identifier
.
new
(
identifier
.
to_hash
)
end
finding
.
signatures
=
report_finding
.
signatures
.
map
do
|
signature
|
Vulnerabilities
::
FindingSignature
.
new
(
signature
.
to_hash
)
end
finding
end
...
...
@@ -111,18 +114,36 @@ module Security
end
def
dismissal_feedback?
(
finding
)
dismissal_feedback_by_fingerprint
[
finding
.
project_fingerprint
]
if
::
Feature
.
enabled?
(
:vulnerability_finding_signatures
,
pipeline
.
project
)
&&
!
finding
.
signatures
.
empty?
dismissal_feedback_by_finding_signatures
(
finding
)
else
dismissal_feedback_by_project_fingerprint
(
finding
)
end
end
def
dismissal_feedback_by_fingerprint
strong_memoize
(
:
dismissal_feedback_by_fingerprint
)
do
def
all_dismissal_feedbacks
strong_memoize
(
:
all_dismissal_feedbacks
)
do
pipeline
.
project
.
vulnerability_feedback
.
for_dismissal
.
group_by
(
&
:project_fingerprint
)
end
end
def
dismissal_feedback_by_finding_signatures
(
finding
)
potential_uuids
=
Set
.
new
([
*
finding
.
signature_uuids
,
finding
.
uuid
].
compact
)
all_dismissal_feedbacks
.
any?
{
|
dismissal
|
potential_uuids
.
include?
(
dismissal
.
finding_uuid
)
}
end
def
dismissal_feedback_by_fingerprint
strong_memoize
(
:dismissal_feedback_by_fingerprint
)
do
all_dismissal_feedbacks
.
group_by
(
&
:project_fingerprint
)
end
end
def
dismissal_feedback_by_project_fingerprint
(
finding
)
dismissal_feedback_by_fingerprint
[
finding
.
project_fingerprint
]
end
def
confidence_levels
Array
(
params
.
fetch
(
:confidence
,
Vulnerabilities
::
Finding
.
confidences
.
keys
))
end
...
...
ee/app/models/concerns/ee/vulnerability_finding_helpers.rb
0 → 100644
View file @
56fcaba4
# frozen_string_literal: true
module
EE
module
VulnerabilityFindingHelpers
extend
ActiveSupport
::
Concern
def
matches_signatures
(
other_signatures
,
other_uuid
)
other_signature_types
=
other_signatures
.
index_by
(
&
:algorithm_type
)
# highest first
match_result
=
nil
signatures
.
sort_by
(
&
:priority
).
reverse_each
do
|
signature
|
matching_other_signature
=
other_signature_types
[
signature
.
algorithm_type
]
next
if
matching_other_signature
.
nil?
match_result
=
matching_other_signature
==
signature
break
end
if
match_result
.
nil?
[
uuid
,
*
signature_uuids
].
include?
(
other_uuid
)
else
match_result
end
end
def
signature_uuids
signatures
.
map
do
|
signature
|
hex_sha
=
signature
.
signature_hex
::
Security
::
VulnerabilityUUID
.
generate
(
report_type:
report_type
,
location_fingerprint:
hex_sha
,
primary_identifier_fingerprint:
primary_identifier
&
.
fingerprint
,
project_id:
project_id
)
end
end
end
end
ee/app/models/concerns/ee/vulnerability_finding_signature_helpers.rb
0 → 100644
View file @
56fcaba4
# frozen_string_literal: true
module
EE
module
VulnerabilityFindingSignatureHelpers
extend
ActiveSupport
::
Concern
# If the location object describes a physical location within a file
# (filename + line numbers), the 'location' algorithm_type should be used
#
# If the location object describes arbitrary data, then the 'hash'
# algorithm_type should be used.
PRIORITIES
=
{
scope_offset:
3
,
location:
2
,
hash:
1
}.
with_indifferent_access
.
freeze
class_methods
do
def
priority
(
algorithm_type
)
raise
ArgumentError
.
new
(
"No priority for
#{
algorithm_type
.
inspect
}
"
)
unless
PRIORITIES
.
key?
(
algorithm_type
)
PRIORITIES
[
algorithm_type
]
end
end
def
priority
self
.
class
.
priority
(
algorithm_type
)
end
end
end
ee/app/models/license.rb
View file @
56fcaba4
...
...
@@ -180,6 +180,7 @@ class License < ApplicationRecord
subepics
threat_monitoring
vulnerability_auto_fix
vulnerability_finding_signatures
]
EEU_FEATURES
.
freeze
...
...
ee/app/models/vulnerabilities/finding.rb
View file @
56fcaba4
...
...
@@ -5,6 +5,7 @@ module Vulnerabilities
include
ShaAttribute
include
::
Gitlab
::
Utils
::
StrongMemoize
include
Presentable
include
::
VulnerabilityFindingHelpers
# https://gitlab.com/groups/gitlab-org/-/epics/3148
# https://gitlab.com/gitlab-org/gitlab/-/issues/214563#note_370782508 is why the table names are not renamed
...
...
@@ -332,12 +333,16 @@ module Vulnerabilities
end
end
alias_method
:==
,
:eql?
# eql? is necessary in some cases like array intersection
alias_method
:==
,
:eql?
def
eql?
(
other
)
other
.
report_type
==
report_type
&&
other
.
location_fingerprint
==
location_fingerprint
&&
other
.
first_fingerprint
==
first_fingerprint
return
false
unless
other
.
report_type
==
report_type
&&
other
.
primary_identifier_fingerprint
==
primary_identifier_fingerprint
if
::
Feature
.
enabled?
(
:vulnerability_finding_signatures
,
project
)
matches_signatures
(
other
.
signatures
,
other
.
uuid
)
else
other
.
location_fingerprint
==
location_fingerprint
end
end
# Array.difference (-) method uses hash and eql? methods to do comparison
...
...
@@ -348,7 +353,7 @@ module Vulnerabilities
# when Finding is persisted and identifiers are not preloaded.
return
super
if
persisted?
&&
!
identifiers
.
loaded?
report_type
.
hash
^
location_fingerprint
.
hash
^
first
_fingerprint
.
hash
report_type
.
hash
^
location_fingerprint
.
hash
^
primary_identifier
_fingerprint
.
hash
end
def
severity_value
...
...
@@ -380,7 +385,7 @@ module Vulnerabilities
protected
def
first
_fingerprint
def
primary_identifier
_fingerprint
identifiers
.
first
&
.
fingerprint
end
...
...
ee/app/models/vulnerabilities/finding_signature.rb
View file @
56fcaba4
...
...
@@ -5,11 +5,23 @@ module Vulnerabilities
self
.
table_name
=
'vulnerability_finding_signatures'
include
BulkInsertSafe
include
VulnerabilityFindingSignatureHelpers
belongs_to
:finding
,
foreign_key:
'finding_id'
,
inverse_of: :signatures
,
class_name:
'Vulnerabilities::Finding'
enum
algorithm_type:
{
hash:
1
,
location:
2
,
scope_offset:
3
},
_prefix: :algorithm
validates
:finding
,
presence:
true
def
signature_hex
signature_sha
.
unpack1
(
"H*"
)
end
def
eql?
(
other
)
other
.
algorithm_type
==
algorithm_type
&&
other
.
signature_sha
==
signature_sha
end
alias_method
:==
,
:eql?
end
end
ee/app/services/security/store_report_service.rb
View file @
56fcaba4
...
...
@@ -61,17 +61,21 @@ module Security
return
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
,
:signatures
)
entity_params
=
Gitlab
::
Json
.
parse
(
vulnerability_params
&
.
dig
(
:raw_metadata
)).
slice
(
'description'
,
'message'
,
'solution'
,
'cve'
,
'location'
)
# Vulnerabilities::Finding (`vulnerability_occurrences`)
vulnerability_finding
=
vulnerability_findings_by_uuid
[
finding
.
uuid
]
||
create_new
_vulnerability_finding
(
finding
,
vulnerability_params
.
merge
(
entity_params
))
find_or_create
_vulnerability_finding
(
finding
,
vulnerability_params
.
merge
(
entity_params
))
update_vulnerability_scanner
(
finding
)
unless
Feature
.
enabled?
(
:optimize_sql_query_for_security_report
,
project
)
update_vulnerability_finding
(
vulnerability_finding
,
vulnerability_params
)
reset_remediations_for
(
vulnerability_finding
,
finding
)
if
::
Feature
.
enabled?
(
:vulnerability_finding_signatures
,
project
)
update_feedbacks
(
vulnerability_finding
,
vulnerability_params
[
:uuid
])
update_finding_signatures
(
finding
,
vulnerability_finding
)
end
# The maximum number of identifiers is not used in validation
# we just want to ignore the rest if a finding has more than that.
...
...
@@ -86,8 +90,16 @@ module Security
create_vulnerability
(
vulnerability_finding
,
pipeline
)
end
def
find_or_create_vulnerability_finding
(
finding
,
create_params
)
if
::
Feature
.
enabled?
(
:vulnerability_finding_signatures
,
project
)
find_or_create_vulnerability_finding_with_signatures
(
finding
,
create_params
)
else
find_or_create_vulnerability_finding_with_location
(
finding
,
create_params
)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def
create_new_vulnerability_finding
(
finding
,
create_params
)
def
find_or_create_vulnerability_finding_with_location
(
finding
,
create_params
)
find_params
=
{
scanner:
scanners_objects
[
finding
.
scanner
.
key
],
primary_identifier:
identifiers_objects
[
finding
.
primary_identifier
.
key
],
...
...
@@ -120,6 +132,56 @@ module Security
end
end
def
get_matched_findings
(
finding
,
normalized_signatures
,
find_params
)
project
.
vulnerability_findings
.
where
(
**
find_params
).
filter
do
|
vf
|
vf
.
matches_signatures
(
normalized_signatures
,
finding
.
uuid
)
end
end
def
find_or_create_vulnerability_finding_with_signatures
(
finding
,
create_params
)
find_params
=
{
# this isn't taking prioritization into account (happens in the filter
# block below), but it *does* limit the number of findings we have to sift through
location_fingerprint:
[
finding
.
location
.
fingerprint
,
*
finding
.
signatures
.
map
(
&
:signature_hex
)],
scanner:
scanners_objects
[
finding
.
scanner
.
key
],
primary_identifier:
identifiers_objects
[
finding
.
primary_identifier
.
key
]
}
normalized_signatures
=
finding
.
signatures
.
map
do
|
signature
|
::
Vulnerabilities
::
FindingSignature
.
new
(
signature
.
to_hash
)
end
matched_findings
=
get_matched_findings
(
finding
,
normalized_signatures
,
find_params
)
begin
vulnerability_finding
=
matched_findings
.
first
if
vulnerability_finding
.
nil?
find_params
[
:uuid
]
=
finding
.
uuid
vulnerability_finding
=
project
.
vulnerability_findings
.
create_with
(
create_params
)
.
find_or_initialize_by
(
find_params
)
end
vulnerability_finding
.
uuid
=
finding
.
uuid
vulnerability_finding
.
location_fingerprint
=
if
finding
.
signatures
.
empty?
finding
.
location
.
fingerprint
else
finding
.
signatures
.
max_by
(
&
:priority
).
signature_hex
end
vulnerability_finding
.
location
=
create_params
.
dig
(
:location
)
vulnerability_finding
.
save!
vulnerability_finding
rescue
ActiveRecord
::
RecordNotUnique
get_matched_findings
(
finding
,
normalized_signatures
,
find_params
).
first
rescue
ActiveRecord
::
RecordInvalid
=>
e
Gitlab
::
ErrorTracking
.
track_and_raise_exception
(
e
,
create_params:
create_params
&
.
dig
(
:raw_metadata
))
end
end
def
update_vulnerability_scanner
(
finding
)
scanner
=
scanners_objects
[
finding
.
scanner
.
key
]
scanner
.
update!
(
finding
.
scanner
.
to_hash
)
...
...
@@ -223,6 +285,14 @@ module Security
end
# rubocop: enable CodeReuse/ActiveRecord
def
update_feedbacks
(
vulnerability_finding
,
new_uuid
)
vulnerability_finding
.
load_feedback
.
each
do
|
feedback
|
feedback
.
finding_uuid
=
new_uuid
feedback
.
vulnerability_data
=
vulnerability_finding
.
raw_metadata
feedback
.
save!
end
end
def
update_finding_signatures
(
finding
,
vulnerability_finding
)
to_update
=
{}
to_create
=
[]
...
...
@@ -240,12 +310,12 @@ module Security
next
if
poro_signature
.
nil?
poro_signatures
.
delete
(
signature
.
algorithm_type
)
to_update
[
signature
.
id
]
=
poro_signature
.
to_h
to_update
[
signature
.
id
]
=
poro_signature
.
to_h
ash
end
# any remaining poro signatures left are new
poro_signatures
.
values
.
each
do
|
poro_signature
|
attributes
=
poro_signature
.
to_h
.
merge
(
finding_id:
vulnerability_finding
.
id
)
attributes
=
poro_signature
.
to_h
ash
.
merge
(
finding_id:
vulnerability_finding
.
id
)
to_create
<<
::
Vulnerabilities
::
FindingSignature
.
new
(
attributes:
attributes
,
created_at:
Time
.
zone
.
now
,
updated_at:
Time
.
zone
.
now
)
end
...
...
ee/config/feature_flags/development/vulnerability_finding_signatures.yml
0 → 100644
View file @
56fcaba4
---
name
:
vulnerability_finding_signatures
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54608
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/322044
milestone
:
'
13.11'
type
:
development
group
:
group::vulnerability research
default_enabled
:
false
ee/lib/gitlab/ci/parsers/security/common.rb
View file @
56fcaba4
...
...
@@ -7,13 +7,14 @@ module Gitlab
class
Common
SecurityReportParserError
=
Class
.
new
(
Gitlab
::
Ci
::
Parsers
::
ParserError
)
def
self
.
parse!
(
json_data
,
report
)
new
(
json_data
,
report
).
parse!
def
self
.
parse!
(
json_data
,
report
,
vulnerability_finding_signatures_enabled
=
false
)
new
(
json_data
,
report
,
vulnerability_finding_signatures_enabled
).
parse!
end
def
initialize
(
json_data
,
report
)
def
initialize
(
json_data
,
report
,
vulnerability_finding_signatures_enabled
=
false
)
@json_data
=
json_data
@report
=
report
@vulnerability_finding_signatures_enabled
=
vulnerability_finding_signatures_enabled
end
def
parse!
...
...
@@ -80,11 +81,20 @@ module Gitlab
links
=
create_links
(
data
[
'links'
])
location
=
create_location
(
data
[
'location'
]
||
{})
remediations
=
create_remediations
(
data
[
'remediations'
])
signatures
=
create_signatures
(
tracking_data
(
data
))
signatures
=
create_signatures
(
location
,
tracking_data
(
data
))
if
@vulnerability_finding_signatures_enabled
&&
!
signatures
.
empty?
# NOT the signature_sha - the compare key is hashed
# to create the project_fingerprint
highest_priority_signature
=
signatures
.
max_by
(
&
:priority
)
uuid
=
calculate_uuid_v5
(
identifiers
.
first
,
highest_priority_signature
.
signature_hex
)
else
uuid
=
calculate_uuid_v5
(
identifiers
.
first
,
location
&
.
fingerprint
)
end
report
.
add_finding
(
::
Gitlab
::
Ci
::
Reports
::
Security
::
Finding
.
new
(
uuid:
calculate_uuid_v5
(
identifiers
.
first
,
location
)
,
uuid:
uuid
,
report_type:
report
.
type
,
name:
finding_name
(
data
,
identifiers
,
location
),
compare_key:
data
[
'cve'
]
||
''
,
...
...
@@ -99,14 +109,17 @@ module Gitlab
raw_metadata:
data
.
to_json
,
metadata_version:
report_version
,
details:
data
[
'details'
]
||
{},
signatures:
signatures
))
signatures:
signatures
,
project_id:
report
.
project_id
,
vulnerability_finding_signatures_enabled:
@vulnerability_finding_signatures_enabled
))
end
def
create_signatures
(
data
)
return
[]
if
data
.
nil?
||
data
[
'items'
].
nil?
def
create_signatures
(
location
,
tracking
)
tracking
||=
{
'items'
=>
[]
}
signature_algorithms
=
Hash
.
new
{
|
hash
,
key
|
hash
[
key
]
=
[]
}
data
[
'items'
].
each
do
|
item
|
tracking
[
'items'
].
each
do
|
item
|
next
unless
item
.
key?
(
'signatures'
)
item
[
'signatures'
].
each
do
|
signature
|
...
...
@@ -117,14 +130,16 @@ module Gitlab
signature_algorithms
.
map
do
|
algorithm
,
values
|
value
=
values
.
join
(
'|'
)
begin
signature
=
::
Gitlab
::
Ci
::
Reports
::
Security
::
FindingSignature
.
new
(
algorithm_type:
algorithm
,
signature_value:
value
)
signature
.
valid?
?
signature
:
nil
rescue
ArgumentError
=>
e
Gitlab
::
ErrorTracking
.
track_and_raise_for_dev_exception
(
e
)
if
signature
.
valid?
signature
else
e
=
SecurityReportParserError
.
new
(
"Vulnerability tracking signature is not valid:
#{
signature
}
"
)
Gitlab
::
ErrorTracking
.
track_exception
(
e
)
nil
end
end
.
compact
...
...
@@ -201,11 +216,11 @@ module Gitlab
"
#{
identifier
.
name
}
in
#{
location
&
.
fingerprint_path
}
"
end
def
calculate_uuid_v5
(
primary_identifier
,
location
)
def
calculate_uuid_v5
(
primary_identifier
,
location
_fingerprint
)
uuid_v5_name_components
=
{
report_type:
report
.
type
,
primary_identifier_fingerprint:
primary_identifier
&
.
fingerprint
,
location_fingerprint:
location
&
.
fingerprint
,
location_fingerprint:
location
_
fingerprint
,
project_id:
report
.
project_id
}
...
...
ee/lib/gitlab/ci/reports/security/finding.rb
View file @
56fcaba4
...
...
@@ -5,6 +5,8 @@ module Gitlab
module
Reports
module
Security
class
Finding
include
::
VulnerabilityFindingHelpers
UNSAFE_SEVERITIES
=
%w[unknown high critical]
.
freeze
attr_reader
:compare_key
...
...
@@ -25,10 +27,11 @@ module Gitlab
attr_reader
:remediations
attr_reader
:details
attr_reader
:signatures
attr_reader
:project_id
delegate
:file_path
,
:start_line
,
:end_line
,
to: :location
def
initialize
(
compare_key
:,
identifiers
:,
links:
[],
remediations:
[],
location
:,
metadata_version
:,
name
:,
raw_metadata
:,
report_type
:,
scanner
:,
scan
:,
uuid
:,
confidence:
nil
,
severity:
nil
,
details:
{},
signatures:
[])
# rubocop:disable Metrics/ParameterLists
def
initialize
(
compare_key
:,
identifiers
:,
links:
[],
remediations:
[],
location
:,
metadata_version
:,
name
:,
raw_metadata
:,
report_type
:,
scanner
:,
scan
:,
uuid
:,
confidence:
nil
,
severity:
nil
,
details:
{},
signatures:
[]
,
project_id:
nil
,
vulnerability_finding_signatures_enabled:
false
)
# rubocop:disable Metrics/ParameterLists
@compare_key
=
compare_key
@confidence
=
confidence
@identifiers
=
identifiers
...
...
@@ -45,6 +48,8 @@ module Gitlab
@remediations
=
remediations
@details
=
details
@signatures
=
signatures
@project_id
=
project_id
@vulnerability_finding_signatures_enabled
=
vulnerability_finding_signatures_enabled
@project_fingerprint
=
generate_project_fingerprint
end
...
...
@@ -66,6 +71,7 @@ module Gitlab
severity
uuid
details
signatures
]
.
each_with_object
({})
do
|
key
,
hash
|
hash
[
key
]
=
public_send
(
key
)
# rubocop:disable GitlabSecurity/PublicSend
end
...
...
@@ -85,13 +91,22 @@ module Gitlab
end
def
eql?
(
other
)
report_type
==
other
.
report_type
&&
location
.
fingerprint
==
other
.
location
.
fingerprint
&&
primary_fingerprint
==
other
.
primary_fingerprint
return
false
unless
report_type
==
other
.
report_type
&&
primary_identifier_fingerprint
==
other
.
primary_identifier_fingerprint
if
@vulnerability_finding_signatures_enabled
matches_signatures
(
other
.
signatures
,
other
.
uuid
)
else
location
.
fingerprint
==
other
.
location
.
fingerprint
end
end
def
hash
report_type
.
hash
^
location
.
fingerprint
.
hash
^
primary_fingerprint
.
hash
if
@vulnerability_finding_signatures_enabled
&&
!
signatures
.
empty?
highest_signature
=
signatures
.
max_by
(
&
:priority
)
report_type
.
hash
^
highest_signature
.
signature_hex
.
hash
^
primary_identifier_fingerprint
.
hash
else
report_type
.
hash
^
location
.
fingerprint
.
hash
^
primary_identifier_fingerprint
.
hash
end
end
def
valid?
...
...
@@ -104,7 +119,7 @@ module Gitlab
end
end
def
primary_fingerprint
def
primary_
identifier_
fingerprint
primary_identifier
&
.
fingerprint
end
...
...
ee/lib/gitlab/ci/reports/security/finding_signature.rb
View file @
56fcaba4
...
...
@@ -5,6 +5,8 @@ module Gitlab
module
Reports
module
Security
class
FindingSignature
include
VulnerabilityFindingSignatureHelpers
attr_accessor
:algorithm_type
,
:signature_value
def
initialize
(
params
=
{})
...
...
@@ -12,11 +14,19 @@ module Gitlab
@signature_value
=
params
.
dig
(
:signature_value
)
end
def
priority
::
Vulnerabilities
::
FindingSignature
.
priority
(
algorithm_type
)
end
def
signature_sha
Digest
::
SHA1
.
digest
(
signature_value
)
end
def
to_h
def
signature_hex
signature_sha
.
unpack1
(
"H*"
)
end
def
to_hash
{
algorithm_type:
algorithm_type
,
signature_sha:
signature_sha
...
...
@@ -26,6 +36,13 @@ module Gitlab
def
valid?
::
Vulnerabilities
::
FindingSignature
.
algorithm_types
.
key?
(
algorithm_type
)
end
def
eql?
(
other
)
other
.
algorithm_type
==
algorithm_type
&&
other
.
signature_sha
==
signature_sha
end
alias_method
:==
,
:eql?
end
end
end
...
...
ee/lib/gitlab/ci/reports/security/locations/container_scanning.rb
View file @
56fcaba4
...
...
@@ -18,12 +18,12 @@ module Gitlab
@package_version
=
package_version
end
private
def
fingerprint_data
"
#{
docker_image_name_without_tag
}
:
#{
package_name
}
"
end
private
def
docker_image_name_without_tag
base_name
,
version
=
image
.
split
(
':'
)
...
...
ee/lib/gitlab/ci/reports/security/locations/coverage_fuzzing.rb
View file @
56fcaba4
...
...
@@ -16,8 +16,6 @@ module Gitlab
@crash_state
=
crash_state
end
private
def
fingerprint_data
"
#{
crash_type
}
:
#{
crash_state
}
"
end
...
...
ee/lib/gitlab/ci/reports/security/locations/dast.rb
View file @
56fcaba4
...
...
@@ -20,8 +20,6 @@ module Gitlab
alias_method
:fingerprint_path
,
:path
private
def
fingerprint_data
"
#{
path
}
:
#{
method_name
}
:
#{
param
}
"
end
...
...
ee/lib/gitlab/ci/reports/security/locations/dependency_scanning.rb
View file @
56fcaba4
...
...
@@ -18,8 +18,6 @@ module Gitlab
@package_version
=
package_version
end
private
def
fingerprint_data
"
#{
file_path
}
:
#{
package_name
}
"
end
...
...
ee/lib/gitlab/ci/reports/security/locations/sast.rb
View file @
56fcaba4
...
...
@@ -22,8 +22,6 @@ module Gitlab
@start_line
=
start_line
end
private
def
fingerprint_data
"
#{
file_path
}
:
#{
start_line
}
:
#{
end_line
}
"
end
...
...
ee/lib/gitlab/ci/reports/security/locations/secret_detection.rb
View file @
56fcaba4
...
...
@@ -22,8 +22,6 @@ module Gitlab
@start_line
=
start_line
end
private
def
fingerprint_data
"
#{
file_path
}
:
#{
start_line
}
:
#{
end_line
}
"
end
...
...
ee/lib/gitlab/ci/reports/security/vulnerability_reports_comparer.rb
View file @
56fcaba4
...
...
@@ -7,13 +7,17 @@ module Gitlab
class
VulnerabilityReportsComparer
include
Gitlab
::
Utils
::
StrongMemoize
attr_reader
:base_report
,
:head_report
attr_reader
:base_report
,
:head_report
,
:added
,
:fixed
ACCEPTABLE_REPORT_AGE
=
1
.
week
def
initialize
(
base_report
,
head_report
)
@base_report
=
base_report
@head_report
=
head_report
@added
=
[]
@fixed
=
[]
calculate_changes
end
def
base_report_created_at
...
...
@@ -30,16 +34,30 @@ module Gitlab
ACCEPTABLE_REPORT_AGE
.
ago
>
@base_report
.
created_at
end
def
added
strong_memoize
(
:added
)
do
head_report
.
findings
-
base_report
.
findings
end
def
calculate_changes
base_findings
=
base_report
.
findings
head_findings
=
head_report
.
findings
head_findings_hash
=
head_findings
.
index_by
(
&
:object_id
)
# This is slow - O(N^2). If we didn't need to worry about one high
# priority fingerprint that doesn't match overruling a lower
# priority fingerprint that does match, we'd be able to do some
# set operations here
base_findings
.
each
do
|
base_finding
|
still_exists
=
false
head_findings
.
each
do
|
head_finding
|
next
unless
base_finding
.
eql?
(
head_finding
)
still_exists
=
true
head_findings_hash
.
delete
(
head_finding
.
object_id
)
break
end
def
fixed
strong_memoize
(
:fixed
)
do
base_report
.
findings
-
head_report
.
findings
@fixed
<<
base_finding
unless
still_exists
end
@added
=
head_findings_hash
.
values
end
end
end
...
...
ee/spec/factories/ci/reports/security/findings.rb
View file @
56fcaba4
...
...
@@ -39,6 +39,7 @@ FactoryBot.define do
project_id:
n
)
end
vulnerability_finding_signatures_enabled
{
false
}
skip_create
...
...
ee/spec/factories/vulnerabilities/feedback.rb
View file @
56fcaba4
...
...
@@ -17,6 +17,7 @@ FactoryBot.define do
category
{
'sast'
}
project_fingerprint
{
generate
(
:project_fingerprint
)
}
vulnerability_data
{
{
category:
'sast'
}
}
finding_uuid
{
Gitlab
::
UUID
.
v5
(
SecureRandom
.
hex
)
}
trait
:dismissal
do
feedback_type
{
'dismissal'
}
...
...
ee/spec/finders/security/pipeline_vulnerabilities_finder_spec.rb
View file @
56fcaba4
...
...
@@ -83,12 +83,12 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
# use the same number of queries, regardless of the number of findings
# contained in the pipeline report.
sast_findings
=
pipeline
.
security_reports
.
reports
[
'sast
'
].
findings
container_scanning_findings
=
pipeline
.
security_reports
.
reports
[
'container_scanning
'
].
findings
dep_findings
=
pipeline
.
security_reports
.
reports
[
'dependency_scanning'
].
findings
# this test is invalid if we don't have more
sast
findings than dep findings
expect
(
sast
_findings
.
count
).
to
be
>
dep_findings
.
count
# this test is invalid if we don't have more
container_scanning
findings than dep findings
expect
(
container_scanning
_findings
.
count
).
to
be
>
dep_findings
.
count
(
sast
_findings
+
dep_findings
).
each
do
|
report_finding
|
(
container_scanning
_findings
+
dep_findings
).
each
do
|
report_finding
|
# create a finding and a vulnerability for each report finding
# (the vulnerability is created with the :confirmed trait)
create
(
:vulnerabilities_finding
,
...
...
@@ -103,7 +103,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
# should be the same number of queries between different report types
expect
do
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[
sast
]
}).
execute
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[
container_scanning
]
}).
execute
end
.
to
issue_same_number_of_queries_as
{
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[dependency_scanning]
}).
execute
}
...
...
@@ -117,11 +117,11 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
orig_security_reports
=
pipeline
.
security_reports
new_finding
=
create
(
:ci_reports_security_finding
)
expect
do
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[
sast
]
}).
execute
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[
container_scanning
]
}).
execute
end
.
to
issue_same_number_of_queries_as
{
orig_security_reports
.
reports
[
'
sast
'
].
add_finding
(
new_finding
)
orig_security_reports
.
reports
[
'
container_scanning
'
].
add_finding
(
new_finding
)
allow
(
pipeline
).
to
receive
(
:security_reports
).
and_return
(
orig_security_reports
)
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[
sast
]
}).
execute
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[
container_scanning
]
}).
execute
}
end
end
...
...
@@ -130,9 +130,11 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
context
'when sast'
do
let
(
:params
)
{
{
report_type:
%w[sast]
}
}
let
(
:sast_report_fingerprints
)
{
pipeline
.
security_reports
.
reports
[
'sast'
].
findings
.
map
(
&
:location
).
map
(
&
:fingerprint
)
}
let
(
:sast_report_uuids
)
{
pipeline
.
security_reports
.
reports
[
'sast'
].
findings
.
map
(
&
:uuid
)
}
it
'includes only sast'
do
expect
(
subject
.
findings
.
map
(
&
:location_fingerprint
)).
to
match_array
(
sast_report_fingerprints
)
expect
(
subject
.
findings
.
map
(
&
:uuid
)).
to
match_array
(
sast_report_uuids
)
expect
(
subject
.
findings
.
count
).
to
eq
(
sast_count
)
end
end
...
...
@@ -172,6 +174,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
let
(
:ds_finding
)
{
pipeline
.
security_reports
.
reports
[
"dependency_scanning"
].
findings
.
first
}
let
(
:sast_finding
)
{
pipeline
.
security_reports
.
reports
[
"sast"
].
findings
.
first
}
context
'when vulnerability_finding_signatures feature flag is disabled'
do
let!
(
:feedback
)
do
[
create
(
...
...
@@ -181,7 +184,8 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
project:
project
,
pipeline:
pipeline
,
project_fingerprint:
ds_finding
.
project_fingerprint
,
vulnerability_data:
ds_finding
.
raw_metadata
vulnerability_data:
ds_finding
.
raw_metadata
,
finding_uuid:
ds_finding
.
uuid
),
create
(
:vulnerability_feedback
,
...
...
@@ -190,11 +194,16 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
project:
project
,
pipeline:
pipeline
,
project_fingerprint:
sast_finding
.
project_fingerprint
,
vulnerability_data:
sast_finding
.
raw_metadata
vulnerability_data:
sast_finding
.
raw_metadata
,
finding_uuid:
sast_finding
.
uuid
)
]
end
before
do
stub_feature_flags
(
vulnerability_finding_signatures:
false
)
end
context
'when unscoped'
do
subject
{
described_class
.
new
(
pipeline:
pipeline
).
execute
}
...
...
@@ -222,6 +231,54 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
end
end
context
'when vulnerability_finding_signatures feature flag is enabled'
do
let!
(
:feedback
)
do
[
create
(
:vulnerability_feedback
,
:dismissal
,
:sast
,
project:
project
,
pipeline:
pipeline
,
project_fingerprint:
sast_finding
.
project_fingerprint
,
vulnerability_data:
sast_finding
.
raw_metadata
,
finding_uuid:
sast_finding
.
uuid
)
]
end
before
do
stub_feature_flags
(
vulnerability_finding_signatures:
true
)
end
context
'when unscoped'
do
subject
{
described_class
.
new
(
pipeline:
pipeline
).
execute
}
it
'returns non-dismissed vulnerabilities'
do
expect
(
subject
.
findings
.
count
).
to
eq
(
cs_count
+
dast_count
+
ds_count
+
sast_count
-
feedback
.
count
)
expect
(
subject
.
findings
.
map
(
&
:project_fingerprint
)).
not_to
include
(
*
feedback
.
map
(
&
:project_fingerprint
))
end
end
context
'when `dismissed`'
do
subject
{
described_class
.
new
(
pipeline:
pipeline
,
params:
{
report_type:
%w[sast]
,
scope:
'dismissed'
}
).
execute
}
it
'returns non-dismissed vulnerabilities'
do
expect
(
subject
.
findings
.
count
).
to
eq
(
sast_count
-
1
)
expect
(
subject
.
findings
.
map
(
&
:project_fingerprint
)).
not_to
include
(
sast_finding
.
project_fingerprint
)
end
end
context
'when `all`'
do
let
(
:params
)
{
{
report_type:
%w[sast dast container_scanning dependency_scanning]
,
scope:
'all'
}
}
it
'returns all vulnerabilities'
do
expect
(
subject
.
findings
.
count
).
to
eq
(
cs_count
+
dast_count
+
ds_count
+
sast_count
)
end
end
end
end
context
'by severity'
do
context
'when unscoped'
do
subject
{
described_class
.
new
(
pipeline:
pipeline
).
execute
}
...
...
ee/spec/lib/gitlab/ci/parsers/security/common_spec.rb
View file @
56fcaba4
...
...
@@ -4,30 +4,22 @@ require 'spec_helper'
RSpec
.
describe
Gitlab
::
Ci
::
Parsers
::
Security
::
Common
do
describe
'#parse!'
do
where
(
vulnerability_finding_signatures_enabled:
[
true
,
false
])
with_them
do
let_it_be
(
:pipeline
)
{
create
(
:ci_pipeline
)
}
let
(
:artifact
)
{
build
(
:ee_ci_job_artifact
,
:common_security_report
)
}
let
(
:report
)
{
Gitlab
::
Ci
::
Reports
::
Security
::
Report
.
new
(
artifact
.
file_type
,
pipeline
,
2
.
weeks
.
ago
)
}
let
(
:location
)
{
::
Gitlab
::
Ci
::
Reports
::
Security
::
Locations
::
DependencyScanning
.
new
(
file_path:
'yarn/yarn.lock'
,
package_version:
'v2'
,
package_name:
'saml2'
)
}
let
(
:tracking_data
)
do
{
'type'
=>
'source'
,
'items'
=>
[
'signatures'
=>
[
{
'algorithm'
=>
'hash'
,
'value'
=>
'hash_value'
},
{
'algorithm'
=>
'location'
,
'value'
=>
'location_value'
},
{
'algorithm'
=>
'scope_offset'
,
'value'
=>
'scope_offset_value'
}
]
]
}
end
let
(
:tracking_data
)
{
nil
}
before
do
allow_next_instance_of
(
described_class
)
do
|
parser
|
allow
(
parser
).
to
receive
(
:create_location
).
and_return
(
location
)
allow
(
parser
).
to
receive
(
:tracking_data
).
and_return
(
tracking_data
)
end
artifact
.
each_blob
{
|
blob
|
described_class
.
parse!
(
blob
,
report
)
}
artifact
.
each_blob
{
|
blob
|
described_class
.
parse!
(
blob
,
report
,
vulnerability_finding_signatures_enabled
)
}
end
describe
'parsing finding.name'
do
...
...
@@ -200,8 +192,21 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end
end
describe
'parsing signature'
do
context
'with valid signature information'
do
describe
'parsing tracking'
do
let
(
:tracking_data
)
do
{
'type'
=>
'source'
,
'items'
=>
[
'signatures'
=>
[
{
'algorithm'
=>
'hash'
,
'value'
=>
'hash_value'
},
{
'algorithm'
=>
'location'
,
'value'
=>
'location_value'
},
{
'algorithm'
=>
'scope_offset'
,
'value'
=>
'scope_offset_value'
}
]
]
}
end
context
'with valid tracking information'
do
it
'creates signatures for each algorithm'
do
finding
=
report
.
findings
.
first
expect
(
finding
.
signatures
.
size
).
to
eq
(
3
)
...
...
@@ -209,7 +214,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end
end
context
'with invalid signature
information'
do
context
'with invalid tracking
information'
do
let
(
:tracking_data
)
do
{
'type'
=>
'source'
,
...
...
@@ -229,6 +234,34 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
expect
(
finding
.
signatures
.
map
(
&
:algorithm_type
).
to_set
).
to
eq
(
Set
[
'hash'
,
'location'
])
end
end
context
'with valid tracking information'
do
it
'creates signatures for each signature algorithm'
do
finding
=
report
.
findings
.
first
expect
(
finding
.
signatures
.
size
).
to
eq
(
3
)
expect
(
finding
.
signatures
.
map
(
&
:algorithm_type
)).
to
eq
(
%w[hash location scope_offset]
)
signatures
=
finding
.
signatures
.
index_by
(
&
:algorithm_type
)
expected_values
=
tracking_data
[
'items'
][
0
][
'signatures'
].
index_by
{
|
x
|
x
[
'algorithm'
]
}
expect
(
signatures
[
'hash'
].
signature_value
).
to
eq
(
expected_values
[
'hash'
][
'value'
])
expect
(
signatures
[
'location'
].
signature_value
).
to
eq
(
expected_values
[
'location'
][
'value'
])
expect
(
signatures
[
'scope_offset'
].
signature_value
).
to
eq
(
expected_values
[
'scope_offset'
][
'value'
])
end
it
'sets the uuid according to the higest priority signature'
do
finding
=
report
.
findings
.
first
highest_signature
=
finding
.
signatures
.
max_by
(
&
:priority
)
identifiers
=
if
vulnerability_finding_signatures_enabled
"
#{
finding
.
report_type
}
-
#{
finding
.
primary_identifier
.
fingerprint
}
-
#{
highest_signature
.
signature_hex
}
-
#{
report
.
project_id
}
"
else
"
#{
finding
.
report_type
}
-
#{
finding
.
primary_identifier
.
fingerprint
}
-
#{
finding
.
location
.
fingerprint
}
-
#{
report
.
project_id
}
"
end
expect
(
finding
.
uuid
).
to
eq
(
Gitlab
::
UUID
.
v5
(
identifiers
))
end
end
end
end
end
end
ee/spec/lib/gitlab/ci/reports/security/finding_signature_spec.rb
View file @
56fcaba4
...
...
@@ -18,15 +18,12 @@ RSpec.describe Gitlab::Ci::Reports::Security::FindingSignature do
expect
(
subject
.
algorithm_type
).
to
eq
(
params
[
:algorithm_type
])
expect
(
subject
.
signature_value
).
to
eq
(
params
[
:signature_value
])
end
describe
'#valid?'
do
it
'returns true'
do
expect
(
subject
.
valid?
).
to
eq
(
true
)
end
end
describe
'#to_h'
do
it
'returns a hash representation of the signature'
do
expect
(
subject
.
to_h
).
to
eq
(
algorithm_type:
params
[
:algorithm_type
],
signature_sha:
Digest
::
SHA1
.
digest
(
params
[
:signature_value
])
)
end
end
...
...
@@ -50,4 +47,13 @@ RSpec.describe Gitlab::Ci::Reports::Security::FindingSignature do
end
end
end
describe
'#to_hash'
do
it
'returns a hash representation of the signature'
do
expect
(
subject
.
to_hash
).
to
eq
(
algorithm_type:
params
[
:algorithm_type
],
signature_sha:
Digest
::
SHA1
.
digest
(
params
[
:signature_value
])
)
end
end
end
ee/spec/lib/gitlab/ci/reports/security/finding_spec.rb
View file @
56fcaba4
...
...
@@ -137,7 +137,8 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
scan:
occurrence
.
scan
,
severity:
occurrence
.
severity
,
uuid:
occurrence
.
uuid
,
details:
occurrence
.
details
details:
occurrence
.
details
,
signatures:
[]
})
end
end
...
...
@@ -197,9 +198,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
end
describe
'#eql?'
do
where
(
vulnerability_finding_signatures_enabled:
[
true
,
false
])
with_them
do
let
(
:identifier
)
{
build
(
:ci_reports_security_identifier
)
}
let
(
:location
)
{
build
(
:ci_reports_security_locations_sast
)
}
let
(
:finding
)
{
build
(
:ci_reports_security_finding
,
severity:
'low'
,
report_type: :sast
,
identifiers:
[
identifier
],
location:
location
)
}
let
(
:finding
)
{
build
(
:ci_reports_security_finding
,
severity:
'low'
,
report_type: :sast
,
identifiers:
[
identifier
],
location:
location
,
vulnerability_finding_signatures_enabled:
vulnerability_finding_signatures_enabled
)
}
let
(
:report_type
)
{
:secret_detection
}
let
(
:identifier_external_id
)
{
'foo'
}
...
...
@@ -211,9 +214,12 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
severity:
'low'
,
report_type:
report_type
,
identifiers:
[
other_identifier
],
location:
other_location
)
location:
other_location
,
vulnerability_finding_signatures_enabled:
vulnerability_finding_signatures_enabled
)
end
let
(
:signature
)
{
::
Gitlab
::
Ci
::
Reports
::
Security
::
FindingSignature
.
new
(
algorithm_type:
'location'
,
signature_value:
'value1'
)
}
subject
{
finding
.
eql?
(
other_finding
)
}
context
'when the primary_identifier is nil'
do
...
...
@@ -230,25 +236,30 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
context
'when the other finding has same primary identifier fingerprint'
do
let
(
:identifier_external_id
)
{
identifier
.
external_id
}
context
'when the other finding has same location fingerprint'
do
context
'when the other finding has same location signature'
do
before
do
finding
.
signatures
<<
signature
other_finding
.
signatures
<<
signature
end
let
(
:location_start_line
)
{
location
.
start_line
}
it
{
is_expected
.
to
be
(
true
)
}
end
context
'when the other finding does not have same location fingerprint
'
do
context
'when the other finding does not have same location signature
'
do
it
{
is_expected
.
to
be
(
false
)
}
end
end
context
'when the other finding does not have same primary identifier fingerprint'
do
context
'when the other finding has same location fingerprint
'
do
context
'when the other finding has same location signature
'
do
let
(
:location_start_line
)
{
location
.
start_line
}
it
{
is_expected
.
to
be
(
false
)
}
end
context
'when the other finding does not have same location fingerprint
'
do
context
'when the other finding does not have same location signature
'
do
it
{
is_expected
.
to
be
(
false
)
}
end
end
...
...
@@ -258,30 +269,31 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
context
'when the other finding has same primary identifier fingerprint'
do
let
(
:identifier_external_id
)
{
identifier
.
external_id
}
context
'when the other finding has same location fingerprint
'
do
context
'when the other finding has same location signature
'
do
let
(
:location_start_line
)
{
location
.
start_line
}
it
{
is_expected
.
to
be
(
false
)
}
end
context
'when the other finding does not have same location fingerprint
'
do
context
'when the other finding does not have same location signature
'
do
it
{
is_expected
.
to
be
(
false
)
}
end
end
context
'when the other finding does not have same primary identifier fingerprint'
do
context
'when the other finding has same location fingerprint
'
do
context
'when the other finding has same location signature
'
do
let
(
:location_start_line
)
{
location
.
start_line
}
it
{
is_expected
.
to
be
(
false
)
}
end
context
'when the other finding does not have same location fingerprint
'
do
context
'when the other finding does not have same location signature
'
do
it
{
is_expected
.
to
be
(
false
)
}
end
end
end
end
end
describe
'#valid?'
do
let
(
:scanner
)
{
build
(
:ci_reports_security_scanner
)
}
...
...
@@ -345,4 +357,55 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
it
{
is_expected
.
to
match_array
(
expected_keys
)
}
end
describe
'#hash'
do
let
(
:scanner
)
{
build
(
:ci_reports_security_scanner
)
}
let
(
:identifiers
)
{
[
build
(
:ci_reports_security_identifier
)]
}
let
(
:location
)
{
build
(
:ci_reports_security_locations_sast
)
}
let
(
:uuid
)
{
SecureRandom
.
uuid
}
context
'with vulnerability_finding_signatures enabled'
do
let
(
:finding
)
do
build
(
:ci_reports_security_finding
,
scanner:
scanner
,
identifiers:
identifiers
,
location:
location
,
uuid:
uuid
,
compare_key:
''
,
vulnerability_finding_signatures_enabled:
true
)
end
let
(
:low_priority_signature
)
{
::
Gitlab
::
Ci
::
Reports
::
Security
::
FindingSignature
.
new
(
algorithm_type:
'location'
,
signature_value:
'value1'
)
}
let
(
:high_priority_signature
)
{
::
Gitlab
::
Ci
::
Reports
::
Security
::
FindingSignature
.
new
(
algorithm_type:
'scope_offset'
,
signature_value:
'value2'
)
}
it
'returns the expected hash with no signatures'
do
expect
(
finding
.
signatures
.
length
).
to
eq
(
0
)
expect
(
finding
.
hash
).
to
eq
(
finding
.
report_type
.
hash
^
finding
.
location
.
fingerprint
.
hash
^
finding
.
primary_identifier_fingerprint
.
hash
)
end
it
'returns the expected hash with signatures'
do
finding
.
signatures
<<
low_priority_signature
finding
.
signatures
<<
high_priority_signature
expect
(
finding
.
signatures
.
length
).
to
eq
(
2
)
expect
(
finding
.
hash
).
to
eq
(
finding
.
report_type
.
hash
^
high_priority_signature
.
signature_hex
.
hash
^
finding
.
primary_identifier_fingerprint
.
hash
)
end
end
context
'without vulnerability_finding_signatures enabled'
do
let
(
:finding
)
do
build
(
:ci_reports_security_finding
,
scanner:
scanner
,
identifiers:
identifiers
,
location:
location
,
uuid:
uuid
,
compare_key:
''
,
vulnerability_finding_signatures_enabled:
false
)
end
it
'returns the expected hash'
do
expect
(
finding
.
hash
).
to
eq
(
finding
.
report_type
.
hash
^
finding
.
location
.
fingerprint
.
hash
^
finding
.
primary_identifier_fingerprint
.
hash
)
end
end
end
end
ee/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb
View file @
56fcaba4
...
...
@@ -8,16 +8,20 @@ RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
let
(
:base_vulnerability
)
{
build
(
:vulnerabilities_finding
,
report_type: :sast
,
identifiers:
[
identifier
],
location_fingerprint:
'123'
,
confidence:
::
Enums
::
Vulnerability
.
confidence_levels
[
:high
],
severity:
::
Enums
::
Vulnerability
.
severity_levels
[
:critical
])
}
let
(
:base_report
)
{
build
(
:ci_reports_security_aggregated_reports
,
findings:
[
base_vulnerability
])}
let
(
:head_vulnerability
)
{
build
(
:vulnerabilities_finding
,
report_type: :sast
,
identifiers:
[
identifier
],
location_fingerprint:
'123'
,
confidence:
::
Enums
::
Vulnerability
.
confidence_levels
[
:high
],
severity:
::
Enums
::
Vulnerability
.
severity_levels
[
:critical
]
)
}
let
(
:head_vulnerability
)
{
build
(
:vulnerabilities_finding
,
report_type: :sast
,
identifiers:
[
identifier
],
location_fingerprint:
base_vulnerability
.
location_fingerprint
,
confidence:
::
Enums
::
Vulnerability
.
confidence_levels
[
:high
],
severity:
::
Enums
::
Vulnerability
.
severity_levels
[
:critical
],
uuid:
base_vulnerability
.
uuid
)
}
let
(
:head_report
)
{
build
(
:ci_reports_security_aggregated_reports
,
findings:
[
head_vulnerability
])}
subject
{
described_class
.
new
(
base_report
,
head_report
)
}
where
(
vulnerability_finding_signatures_enabled:
[
true
,
false
])
with_them
do
before
do
allow
(
base_vulnerability
).
to
receive
(
:location
).
and_return
({})
allow
(
head_vulnerability
).
to
receive
(
:location
).
and_return
({})
stub_feature_flags
(
vulnerability_finding_signatures:
vulnerability_finding_signatures_enabled
)
end
subject
{
described_class
.
new
(
base_report
,
head_report
)
}
describe
'#base_report_out_of_date'
do
context
'no base report'
do
let
(
:base_report
)
{
build
(
:ci_reports_security_aggregated_reports
,
reports:
[],
findings:
[])}
...
...
@@ -79,7 +83,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
describe
'#fixed'
do
let
(
:vuln
)
{
build
(
:vulnerabilities_finding
,
report_type: :sast
,
identifiers:
[
identifier
],
location_fingerprint:
'888'
)
}
let
(
:medium_vuln
)
{
build
(
:vulnerabilities_finding
,
report_type: :sast
,
identifiers:
[
identifier
],
location_fingerprint:
'888'
,
confidence:
::
Enums
::
Vulnerability
.
confidence_levels
[
:high
],
severity:
Enums
::
Vulnerability
.
severity_levels
[
:medium
]
)
}
let
(
:medium_vuln
)
{
build
(
:vulnerabilities_finding
,
report_type: :sast
,
identifiers:
[
identifier
],
location_fingerprint:
'888'
,
confidence:
::
Enums
::
Vulnerability
.
confidence_levels
[
:high
],
severity:
Enums
::
Vulnerability
.
severity_levels
[
:medium
],
uuid:
vuln
.
uuid
)
}
context
'with fixed vulnerability'
do
let
(
:base_report
)
{
build
(
:ci_reports_security_aggregated_reports
,
findings:
[
base_vulnerability
,
vuln
])}
...
...
@@ -132,4 +136,5 @@ RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
expect
(
comparer
.
added
).
to
eq
([])
end
end
end
end
ee/spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb
0 → 100644
View file @
56fcaba4
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
VulnerabilityFindingSignatureHelpers
do
let
(
:cls
)
do
Class
.
new
do
include
VulnerabilityFindingSignatureHelpers
attr_accessor
:algorithm_type
def
initialize
(
algorithm_type
)
@algorithm_type
=
algorithm_type
end
end
end
describe
'#priority'
do
it
'returns numeric values of the priority string'
do
expect
(
cls
.
new
(
'scope_offset'
).
priority
).
to
eq
(
3
)
expect
(
cls
.
new
(
'location'
).
priority
).
to
eq
(
2
)
expect
(
cls
.
new
(
'hash'
).
priority
).
to
eq
(
1
)
end
end
describe
'#self.priority'
do
it
'returns the numeric value of the provided string'
do
expect
(
cls
.
priority
(
'scope_offset'
)).
to
eq
(
3
)
expect
(
cls
.
priority
(
'location'
)).
to
eq
(
2
)
expect
(
cls
.
priority
(
'hash'
)).
to
eq
(
1
)
end
end
end
ee/spec/models/vulnerabilities/finding_spec.rb
View file @
56fcaba4
...
...
@@ -7,6 +7,12 @@ RSpec.describe Vulnerabilities::Finding do
it
{
is_expected
.
to
define_enum_for
(
:report_type
)
}
it
{
is_expected
.
to
define_enum_for
(
:severity
)
}
where
(
vulnerability_finding_signatures_enabled:
[
true
,
false
])
with_them
do
before
do
stub_feature_flags
(
vulnerability_finding_signatures:
vulnerability_finding_signatures_enabled
)
end
describe
'associations'
do
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
belong_to
(
:primary_identifier
).
class_name
(
'Vulnerabilities::Identifier'
)
}
...
...
@@ -63,7 +69,7 @@ RSpec.describe Vulnerabilities::Finding do
context
'database uniqueness'
do
let
(
:finding
)
{
create
(
:vulnerabilities_finding
)
}
let
(
:new_finding
)
{
finding
.
dup
.
tap
{
|
o
|
o
.
uuid
=
SecureRandom
.
uuid
}
}
let
(
:new_finding
)
{
finding
.
dup
.
tap
{
|
o
|
o
.
cve
=
SecureRandom
.
uuid
}
}
it
"when all index attributes are identical"
do
expect
{
new_finding
.
save!
}.
to
raise_error
(
ActiveRecord
::
RecordNotUnique
)
...
...
@@ -81,7 +87,7 @@ RSpec.describe Vulnerabilities::Finding do
with_them
do
it
"is valid"
do
expect
{
new_finding
.
update!
({
key
=>
create
(
factory_name
)
})
}.
not_to
raise_error
expect
{
new_finding
.
update!
({
key
=>
create
(
factory_name
),
'uuid'
=>
SecureRandom
.
uuid
})
}.
not_to
raise_error
end
end
end
...
...
@@ -673,7 +679,6 @@ RSpec.describe Vulnerabilities::Finding do
let
(
:confirmed_finding
)
{
create
(
:vulnerabilities_finding
,
:confirmed
)
}
let
(
:resolved_finding
)
{
create
(
:vulnerabilities_finding
,
:resolved
)
}
let
(
:dismissed_finding
)
{
create
(
:vulnerabilities_finding
,
:dismissed
)
}
let
(
:detected_finding
)
{
create
(
:vulnerabilities_finding
,
:detected
)
}
let
(
:finding_with_issue
)
{
create
(
:vulnerabilities_finding
,
:with_issue_feedback
)
}
it
'returns the expected state for a unresolved finding'
do
...
...
@@ -692,10 +697,6 @@ RSpec.describe Vulnerabilities::Finding do
expect
(
dismissed_finding
.
state
).
to
eq
'dismissed'
end
it
'returns the expected state for a detected finding'
do
expect
(
detected_finding
.
state
).
to
eq
'detected'
end
context
'when a vulnerability present for a dismissed finding'
do
before
do
create
(
:vulnerability
,
project:
dismissed_finding
.
project
,
findings:
[
dismissed_finding
])
...
...
@@ -1010,4 +1011,117 @@ RSpec.describe Vulnerabilities::Finding do
end
end
end
describe
'#eql?'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:report_type
)
{
:sast
}
let
(
:identifier_fingerprint
)
{
'fooo'
}
let
(
:identifier
)
{
build
(
:vulnerabilities_identifier
,
fingerprint:
identifier_fingerprint
)
}
let
(
:location_fingerprint1
)
{
'fingerprint1'
}
let
(
:location_fingerprint2
)
{
'fingerprint2'
}
let
(
:finding1
)
do
build
(
:vulnerabilities_finding
,
report_type
,
project:
project
,
primary_identifier:
identifier
,
location_fingerprint:
location_fingerprint1
)
end
let
(
:finding2
)
do
build
(
:vulnerabilities_finding
,
report_type
,
project:
project
,
primary_identifier:
identifier
,
location_fingerprint:
location_fingerprint2
)
end
it
'matches the finding based on enabled tracking methods (if feature flag enabled)'
do
signature1
=
create
(
:vulnerabilities_finding_signature
,
finding:
finding1
)
signature2
=
create
(
:vulnerabilities_finding_signature
,
finding:
finding2
,
signature_sha:
signature1
.
signature_sha
)
# verify that the signatures do exist and that they match
expect
(
finding1
.
signatures
.
size
).
to
eq
(
1
)
expect
(
finding2
.
signatures
.
size
).
to
eq
(
1
)
expect
(
signature1
.
eql?
(
signature2
)).
to
be
(
true
)
# now verify that the correct matching method was used for eql?
expect
(
finding1
.
eql?
(
finding2
)).
to
be
(
vulnerability_finding_signatures_enabled
)
end
context
'short circuits on the highest priority signature match'
do
using
RSpec
::
Parameterized
::
TableSyntax
let
(
:same_hash
)
{
false
}
let
(
:same_location
)
{
false
}
let
(
:create_scope_offset
)
{
false
}
let
(
:same_scope_offset
)
{
false
}
let
(
:create_signatures
)
do
signature1_hash
=
create
(
:vulnerabilities_finding_signature
,
algorithm_type:
'hash'
,
finding:
finding1
)
sha
=
same_hash
?
signature1_hash
.
signature_sha
:
::
Digest
::
SHA1
.
digest
(
SecureRandom
.
hex
(
50
))
create
(
:vulnerabilities_finding_signature
,
algorithm_type:
'hash'
,
finding:
finding2
,
signature_sha:
sha
)
signature1_location
=
create
(
:vulnerabilities_finding_signature
,
algorithm_type:
'location'
,
finding:
finding1
)
sha
=
same_location
?
signature1_location
.
signature_sha
:
::
Digest
::
SHA1
.
digest
(
SecureRandom
.
hex
(
50
))
create
(
:vulnerabilities_finding_signature
,
algorithm_type:
'location'
,
finding:
finding2
,
signature_sha:
sha
)
signature1_scope_offset
=
create
(
:vulnerabilities_finding_signature
,
algorithm_type:
'scope_offset'
,
finding:
finding1
)
if
create_scope_offset
sha
=
same_scope_offset
?
signature1_scope_offset
.
signature_sha
:
::
Digest
::
SHA1
.
digest
(
SecureRandom
.
hex
(
50
))
create
(
:vulnerabilities_finding_signature
,
algorithm_type:
'scope_offset'
,
finding:
finding2
,
signature_sha:
sha
)
end
end
where
(
:same_hash
,
:same_location
,
:create_scope_offset
,
:same_scope_offset
,
:should_match
)
do
true
|
true
|
true
|
true
|
true
# everything matches
false
|
false
|
true
|
false
|
false
# nothing matches
true
|
true
|
true
|
false
|
false
# highest priority matches alg/priority but not on value
false
|
false
|
true
|
true
|
true
# highest priority matches alg/priority and value
false
|
true
|
false
|
false
|
true
# highest priority is location, matches alg/priority and value
end
with_them
do
it
'matches correctly'
do
next
unless
vulnerability_finding_signatures_enabled
create_signatures
expect
(
finding1
.
eql?
(
finding2
)).
to
be
(
should_match
)
end
end
end
end
end
end
ee/spec/requests/api/vulnerability_findings_spec.rb
View file @
56fcaba4
...
...
@@ -33,7 +33,8 @@ RSpec.describe API::VulnerabilityFindings do
project:
project
,
pipeline:
pipeline
,
project_fingerprint:
sast_report
.
findings
.
first
.
project_fingerprint
,
vulnerability_data:
sast_report
.
findings
.
first
.
raw_metadata
vulnerability_data:
sast_report
.
findings
.
first
.
raw_metadata
,
finding_uuid:
sast_report
.
findings
.
first
.
uuid
)
end
...
...
ee/spec/serializers/vulnerabilities/feedback_entity_spec.rb
View file @
56fcaba4
...
...
@@ -164,7 +164,7 @@ RSpec.describe Vulnerabilities::FeedbackEntity do
end
context
'when finding_uuid is not present'
do
let
(
:feedback
)
{
build_stubbed
(
:vulnerability_feedback
,
:issue
,
project:
project
)
}
let
(
:feedback
)
{
build_stubbed
(
:vulnerability_feedback
,
:issue
,
project:
project
,
finding_uuid:
nil
)
}
it
'has a nil finding_uuid'
do
expect
(
subject
[
:finding_uuid
]).
to
be_nil
...
...
ee/spec/services/ci/compare_security_reports_service_spec.rb
View file @
56fcaba4
...
...
@@ -11,6 +11,12 @@ RSpec.describe Ci::CompareSecurityReportsService do
collection
.
map
{
|
t
|
t
[
'identifiers'
].
first
[
'external_id'
]
}
end
where
(
vulnerability_finding_signatures_enabled:
[
true
,
false
])
with_them
do
before
do
stub_feature_flags
(
vulnerability_finding_signatures:
vulnerability_finding_signatures_enabled
)
end
describe
'#execute DS'
do
before
do
stub_licensed_features
(
dependency_scanning:
true
)
...
...
@@ -273,4 +279,5 @@ RSpec.describe Ci::CompareSecurityReportsService do
end
end
end
end
end
ee/spec/services/security/store_report_service_spec.rb
View file @
56fcaba4
...
...
@@ -3,6 +3,8 @@
require
'spec_helper'
RSpec
.
describe
Security
::
StoreReportService
,
'#execute'
do
using
RSpec
::
Parameterized
::
TableSyntax
let_it_be
(
:user
)
{
create
(
:user
)
}
let
(
:artifact
)
{
create
(
:ee_ci_job_artifact
,
trait
)
}
...
...
@@ -11,13 +13,16 @@ RSpec.describe Security::StoreReportService, '#execute' do
let
(
:pipeline
)
{
artifact
.
job
.
pipeline
}
let
(
:report
)
{
pipeline
.
security_reports
.
get_report
(
report_type
.
to_s
,
artifact
)
}
subject
{
described_class
.
new
(
pipeline
,
report
).
execute
}
where
(
vulnerability_finding_signatures_enabled:
[
true
,
false
])
with_them
do
before
do
stub_feature_flags
(
vulnerability_finding_signatures:
vulnerability_finding_signatures_enabled
)
stub_licensed_features
(
sast:
true
,
dependency_scanning:
true
,
container_scanning:
true
,
security_dashboard:
true
)
allow
(
Security
::
AutoFixWorker
).
to
receive
(
:perform_async
)
end
subject
{
described_class
.
new
(
pipeline
,
report
).
execute
}
context
'without existing data'
do
before
(
:all
)
do
checksum
=
'f00bc6261fa512f0960b7fc3bfcce7fb31997cf32b96fa647bed5668b2c77fee'
...
...
@@ -30,24 +35,18 @@ RSpec.describe Security::StoreReportService, '#execute' do
end
context
'for different security reports'
do
using
RSpec
::
Parameterized
::
TableSyntax
where
(
:case_name
,
:trait
,
:scanners
,
:identifiers
,
:findings
,
:finding_identifiers
,
:finding_pipelines
,
:remediations
,
:signatures
,
:optimize_sql_query_for_security_report_ff
)
do
'with SAST report'
|
:sast
|
1
|
6
|
5
|
7
|
5
|
0
|
2
|
false
'with exceeding identifiers'
|
:with_exceeding_identifiers
|
1
|
20
|
1
|
20
|
1
|
0
|
0
|
false
'with Dependency Scanning report'
|
:dependency_scanning_remediation
|
1
|
3
|
2
|
3
|
2
|
1
|
0
|
false
'with Container Scanning report'
|
:container_scanning
|
1
|
8
|
8
|
8
|
8
|
0
|
0
|
false
'with SAST report'
|
:sast
|
1
|
6
|
5
|
7
|
5
|
0
|
2
|
true
'with exceeding identifiers'
|
:with_exceeding_identifiers
|
1
|
20
|
1
|
20
|
1
|
0
|
0
|
true
'with Dependency Scanning report'
|
:dependency_scanning_remediation
|
1
|
3
|
2
|
3
|
2
|
1
|
0
|
true
'with Container Scanning report'
|
:container_scanning
|
1
|
8
|
8
|
8
|
8
|
0
|
0
|
true
end
with_them
do
before
do
stub_feature_flags
(
optimize_sql_query_for_security_report:
optimize_sql_query_for_security_report_ff
)
end
where
(
:case_name
,
:trait
,
:scanners
,
:identifiers
,
:findings
,
:finding_identifiers
,
:finding_pipelines
,
:remediations
,
:signatures
)
do
'with SAST report'
|
:sast
|
1
|
6
|
5
|
7
|
5
|
0
|
2
'with exceeding identifiers'
|
:with_exceeding_identifiers
|
1
|
20
|
1
|
20
|
1
|
0
|
1
'with Dependency Scanning report'
|
:dependency_scanning_remediation
|
1
|
3
|
2
|
3
|
2
|
1
|
2
'with Container Scanning report'
|
:container_scanning
|
1
|
8
|
8
|
8
|
8
|
0
|
8
end
it
'inserts all scanners'
do
expect
{
subject
}.
to
change
{
Vulnerabilities
::
Scanner
.
count
}.
by
(
scanners
)
end
...
...
@@ -164,11 +163,6 @@ RSpec.describe Security::StoreReportService, '#execute' do
let
(
:new_pipeline
)
{
create
(
:ci_pipeline
,
project:
project
)
}
let
(
:new_report
)
{
new_pipeline
.
security_reports
.
get_report
(
report_type
.
to_s
,
artifact
)
}
let
(
:existing_signature
)
{
create
(
:vulnerabilities_finding_signature
,
finding:
finding
)
}
let
(
:unsupported_signature
)
do
create
(
:vulnerabilities_finding_signature
,
finding:
finding
,
algorithm_type:
::
Vulnerabilities
::
FindingSignature
.
algorithm_types
[
:location
])
end
let
(
:trait
)
{
:sast
}
...
...
@@ -182,7 +176,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
end
let!
(
:finding
)
do
create
(
:vulnerabilities_finding
,
created_finding
=
create
(
:vulnerabilities_finding
,
pipelines:
[
pipeline
],
identifiers:
[
identifier
],
primary_identifier:
identifier
,
...
...
@@ -190,6 +184,15 @@ RSpec.describe Security::StoreReportService, '#execute' do
project:
project
,
uuid:
"e5388f40-18f5-566d-95c6-d64c6f46a00a"
,
location_fingerprint:
finding_location_fingerprint
)
existing_finding
=
report
.
findings
.
find
{
|
f
|
f
.
location
.
fingerprint
==
created_finding
.
location_fingerprint
}
create
(
:vulnerabilities_finding_signature
,
finding:
created_finding
,
algorithm_type:
existing_finding
.
signatures
.
first
.
algorithm_type
,
signature_sha:
existing_finding
.
signatures
.
first
.
signature_sha
)
created_finding
end
let!
(
:vulnerability
)
{
create
(
:vulnerability
,
findings:
[
finding
],
project:
project
)
}
...
...
@@ -227,6 +230,14 @@ RSpec.describe Security::StoreReportService, '#execute' do
end
it
'updates UUIDv4 to UUIDv5'
do
finding
.
uuid
=
'00000000-1111-2222-3333-444444444444'
finding
.
save!
# this report_finding should be used to update the finding's uuid
report_finding
=
new_report
.
findings
.
find
{
|
f
|
f
.
location
.
fingerprint
==
'0e7d0291d912f56880e39d4fbd80d99dd5d327ba'
}
allow
(
report_finding
).
to
receive
(
:uuid
).
and_return
(
desired_uuid
)
report_finding
.
signatures
.
pop
subject
expect
(
finding
.
reload
.
uuid
).
to
eq
(
desired_uuid
)
...
...
@@ -259,28 +270,24 @@ RSpec.describe Security::StoreReportService, '#execute' do
end
it
'updates signatures to match new values'
do
existing_signature
unsupported_signature
next
unless
vulnerability_finding_signatures_enabled
expect
(
finding
.
signatures
.
count
).
to
eq
(
2
)
signature_algs
=
finding
.
signatures
.
map
(
&
:algorithm_type
).
sort
expect
(
signature_algs
).
to
eq
(
%w[hash location]
)
expect
(
finding
.
signatures
.
count
).
to
eq
(
1
)
expect
(
finding
.
signatures
.
first
.
algorithm_type
).
to
eq
(
'hash'
)
existing_signature
=
finding
.
signatures
.
first
subject
finding
.
reload
existing_signature
.
reload
# check that unsupported algorithm is not deleted
expect
(
finding
.
signatures
.
count
).
to
eq
(
3
)
signature_algs
=
finding
.
signatures
.
sort
.
map
(
&
:algorithm_type
)
expect
(
signature_algs
).
to
eq
(
%w[hash location scope_offset]
)
expect
(
finding
.
signatures
.
count
).
to
eq
(
2
)
signature_algs
=
finding
.
signatures
.
sort_by
(
&
:priority
).
map
(
&
:algorithm_type
)
expect
(
signature_algs
).
to
eq
(
%w[hash scope_offset]
)
# check that the existing hash signature was updated/reused
expect
(
existing_signature
.
id
).
to
eq
(
finding
.
signatures
.
min
.
id
)
# check that the unsupported signature was not deleted
expect
(
::
Vulnerabilities
::
FindingSignature
.
exists?
(
unsupported_signature
.
id
)).
to
eq
(
true
)
expect
(
existing_signature
.
id
).
to
eq
(
finding
.
signatures
.
find
(
&
:algorithm_hash?
).
id
)
end
it
'updates existing vulnerability with new data'
do
...
...
@@ -299,7 +306,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
context
'when the existing resolved vulnerability is discovered again on the latest report'
do
before
do
vulnerability
.
update!
(
resolved_on_default_branch:
true
)
vulnerability
.
update_column
(
:resolved_on_default_branch
,
true
)
end
it
'marks the vulnerability as not resolved on default branch'
do
...
...
@@ -331,7 +338,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
end
context
'vulnerability issue link'
do
context
'when there is no asso
ciated issue feedback with finding'
do
context
'when there is no assoi
ciated issue feedback with finding'
do
it
'does not insert issue links from the new pipeline'
do
expect
{
subject
}.
to
change
{
Vulnerabilities
::
IssueLink
.
count
}.
by
(
0
)
end
...
...
@@ -414,7 +421,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
context
'when auto fix feature is disabled'
do
before
do
project
.
security_setting
.
update!
(
auto_fix_dependency_scanning:
false
)
project
.
security_setting
.
update_column
(
:auto_fix_dependency_scanning
,
false
)
end
it
'does not start auto fix worker'
do
...
...
@@ -464,4 +471,5 @@ RSpec.describe Security::StoreReportService, '#execute' do
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment