Commit 02917968 authored by Tetiana Chupryna's avatar Tetiana Chupryna

Merge branch '348489-add-container-scanning-dependencies-to-dependency-list' into 'master'

Match container-scanning vulnerabilities in dependencies

See merge request gitlab-org/gitlab!76939
parents fae80992 b6b81928
...@@ -218,6 +218,10 @@ module Vulnerabilities ...@@ -218,6 +218,10 @@ module Vulnerabilities
location.dig('file') location.dig('file')
end end
def image
location.dig('image')
end
def links def links
return metadata.fetch('links', []) if Feature.disabled?(:vulnerability_finding_replace_metadata) || finding_links.load.empty? return metadata.fetch('links', []) if Feature.disabled?(:vulnerability_finding_replace_metadata) || finding_links.load.empty?
......
...@@ -5,6 +5,8 @@ module Gitlab ...@@ -5,6 +5,8 @@ module Gitlab
module Parsers module Parsers
module Security module Security
class DependencyList class DependencyList
CONTAINER_IMAGE_PATH_PREFIX = 'container-image:'
def initialize(project, sha, pipeline) def initialize(project, sha, pipeline)
@project = project @project = project
@formatter = Formatters::DependencyList.new(project, sha) @formatter = Formatters::DependencyList.new(project, sha)
...@@ -30,19 +32,24 @@ module Gitlab ...@@ -30,19 +32,24 @@ module Gitlab
end end
def parse_vulnerabilities(report_data, report) def parse_vulnerabilities(report_data, report)
vuln_findings = pipeline.vulnerability_findings.dependency_scanning vuln_findings = pipeline.vulnerability_findings.by_report_types(%i[container_scanning dependency_scanning])
vuln_findings.each do |finding| vuln_findings.each do |finding|
dependency = finding.location.dig("dependency") dependency = finding.location.dig("dependency")
next unless dependency next unless dependency
file = finding.file
vulnerability = finding.metadata.merge(vulnerability_id: finding.vulnerability_id) vulnerability = finding.metadata.merge(vulnerability_id: finding.vulnerability_id)
report.add_dependency(formatter.format(dependency, '', file, vulnerability)) report.add_dependency(formatter.format(dependency, '', dependency_path(finding), vulnerability))
end end
end end
def dependency_path(finding)
return finding.file if finding.dependency_scanning?
"#{CONTAINER_IMAGE_PATH_PREFIX}#{finding.image}"
end
def parse_licenses!(json_data, report) def parse_licenses!(json_data, report)
license_report = ::Gitlab::Ci::Reports::LicenseScanning::Report.parse_from(json_data) license_report = ::Gitlab::Ci::Reports::LicenseScanning::Report.parse_from(json_data)
license_report.licenses.each do |license| license_report.licenses.each do |license|
......
...@@ -519,6 +519,57 @@ FactoryBot.define do ...@@ -519,6 +519,57 @@ FactoryBot.define do
end end
end end
trait :with_container_scanning_metadata do
transient do
raw_severity { "Critical" }
id { "CVE-2021-44228" }
image { "package-registry/package:tag" }
package { "org.apache.logging.log4j:log4j-api" }
version { "2.14.1" }
end
after(:build) do |finding, evaluator|
finding.report_type = "container_scanning"
finding.name = "CVE-2021-44228 in org.apache.logging.log4j:log4j-api-2.14.1"
finding.message = "Apache Log4j2 <=2.14.1 JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints."
finding.metadata_version = "2.1"
finding.raw_metadata = {
"category": "container_scanning",
"name": "CVE-2021-44228 in org.apache.logging.log4j:log4j-api-2.14.1",
"message": "Apache Log4j2 <=2.14.1 JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints.",
"description": "Apache Log4j2 <=2.14.1 JNDI features used in configuration, log messages, and parameters do not protect against attacker controlled LDAP and other JNDI related endpoints.",
"severity": evaluator.raw_severity,
"scanner": {
"id": "trivy",
"name": "Trivy"
},
"location": {
"image": evaluator.image,
"dependency": {
"package": {
"name": evaluator.package
},
"operating_system": "Unknown",
"version": evaluator.version
}
},
"identifiers": [
{
"type": "cve",
"name": evaluator.id,
"value": "CVE-2021-44228",
"url": "http://packetstormsecurity.com/files/165225/Apache-Log4j2-2.14.1-Remote-Code-Execution.html"
}
],
"links": [
{
"url": "http://packetstormsecurity.com/files/165225/Apache-Log4j2-2.14.1-Remote-Code-Execution.html"
}
]
}.to_json
end
end
trait :with_cluster_image_scanning_scanning_metadata do trait :with_cluster_image_scanning_scanning_metadata do
after(:build) do |finding, _| after(:build) do |finding, _|
finding.report_type = "cluster_image_scanning" finding.report_type = "cluster_image_scanning"
......
...@@ -11,15 +11,15 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do ...@@ -11,15 +11,15 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do
let_it_be(:pipeline) { create :ee_ci_pipeline, :with_dependency_list_report } let_it_be(:pipeline) { create :ee_ci_pipeline, :with_dependency_list_report }
describe '#parse!' do describe '#parse!' do
context 'with dependency_list artifact' do let(:artifact) { pipeline.job_artifacts.last }
let(:artifact) { pipeline.job_artifacts.last }
before do before do
artifact.each_blob do |blob| artifact.each_blob do |blob|
parser.parse!(blob, report) parser.parse!(blob, report)
end
end end
end
context 'with dependency_list artifact' do
it 'parses all files' do it 'parses all files' do
blob_path = "/#{project.full_path}/-/blob/#{sha}/yarn/yarn.lock" blob_path = "/#{project.full_path}/-/blob/#{sha}/yarn/yarn.lock"
...@@ -43,19 +43,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do ...@@ -43,19 +43,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do
end end
end end
context 'with vulnerabilities in the database' do context 'with dependency_scanning dependencies' do
let_it_be(:vulnerability) { create(:vulnerability, report_type: :dependency_scanning) } let_it_be(:vulnerability) { create(:vulnerability, report_type: :dependency_scanning) }
let_it_be(:finding) { create(:vulnerabilities_finding, :with_dependency_scanning_metadata, vulnerability: vulnerability) } let_it_be(:finding) { create(:vulnerabilities_finding, :with_dependency_scanning_metadata, vulnerability: vulnerability) }
let_it_be(:finding_pipeline) { create(:vulnerabilities_finding_pipeline, finding: finding, pipeline: pipeline) } let_it_be(:finding_pipeline) { create(:vulnerabilities_finding_pipeline, finding: finding, pipeline: pipeline) }
let(:artifact) { pipeline.job_artifacts.last }
before do
artifact.each_blob do |blob|
parser.parse!(blob, report)
end
end
it 'does not causes N+1 query' do it 'does not causes N+1 query' do
control_count = ActiveRecord::QueryRecorder.new do control_count = ActiveRecord::QueryRecorder.new do
artifact.each_blob do |blob| artifact.each_blob do |blob|
...@@ -97,7 +89,23 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do ...@@ -97,7 +89,23 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do
end end
end end
context 'with container_scanning dependencies' do
let_it_be(:vulnerability) { create(:vulnerability, report_type: :container_scanning) }
let_it_be(:finding) { create(:vulnerabilities_finding, :with_container_scanning_metadata, vulnerability: vulnerability) }
let_it_be(:finding_pipeline) { create(:vulnerabilities_finding_pipeline, finding: finding, pipeline: pipeline) }
it 'adds new dependency and vulnerability to the report with modified path' do
cs_dependency = report.dependencies.detect { |dep| dep[:name] == 'org.apache.logging.log4j:log4j-api' }
expect(report.dependencies.size).to eq(22)
expect(cs_dependency[:vulnerabilities].size).to eq(1)
expect(cs_dependency.dig(:location, :path)).to eq('container-image:package-registry/package:tag')
end
end
context 'with null dependencies' do context 'with null dependencies' do
let(:empty_report) { Gitlab::Ci::Reports::DependencyList::Report.new }
let(:json_data) do let(:json_data) do
<<~JSON <<~JSON
{ {
...@@ -130,9 +138,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do ...@@ -130,9 +138,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyList do
end end
it 'ignores null dependencies' do it 'ignores null dependencies' do
parser.parse!(json_data, report) parser.parse!(json_data, empty_report)
expect(report.dependencies.size).to eq(0) expect(empty_report.dependencies.size).to eq(0)
end end
end end
end end
......
...@@ -772,6 +772,15 @@ RSpec.describe Vulnerabilities::Finding do ...@@ -772,6 +772,15 @@ RSpec.describe Vulnerabilities::Finding do
end end
end end
describe '#image' do
let(:finding) { build(:vulnerabilities_finding, :with_container_scanning_metadata) }
let(:expected_location) { finding.metadata['location']['image'] }
subject { finding.image }
it { is_expected.to eq(expected_location) }
end
describe '#evidence' do describe '#evidence' do
subject { finding.evidence } subject { finding.evidence }
......
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