Commit b090066a authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'sk/337926-add-cluster-filter' into 'master'

Add cluster_id filtering for vulnerabilites graphql query

See merge request gitlab-org/gitlab!71807
parents f02c40eb 073de687
...@@ -484,6 +484,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -484,6 +484,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="queryvulnerabilitiesclusterid"></a>`clusterId` | [`[ClustersClusterID!]`](#clustersclusterid) | Filter vulnerabilities by `cluster_id`. Vulnerabilities with a `reportType` of `cluster_image_scanning` are only included with this filter. |
| <a id="queryvulnerabilitieshasissues"></a>`hasIssues` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have linked issues. | | <a id="queryvulnerabilitieshasissues"></a>`hasIssues` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have linked issues. |
| <a id="queryvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. | | <a id="queryvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. |
| <a id="queryvulnerabilitiesimage"></a>`image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | <a id="queryvulnerabilitiesimage"></a>`image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. |
...@@ -10834,6 +10835,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -10834,6 +10835,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="groupvulnerabilitiesclusterid"></a>`clusterId` | [`[ClustersClusterID!]`](#clustersclusterid) | Filter vulnerabilities by `cluster_id`. Vulnerabilities with a `reportType` of `cluster_image_scanning` are only included with this filter. |
| <a id="groupvulnerabilitieshasissues"></a>`hasIssues` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have linked issues. | | <a id="groupvulnerabilitieshasissues"></a>`hasIssues` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have linked issues. |
| <a id="groupvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. | | <a id="groupvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. |
| <a id="groupvulnerabilitiesimage"></a>`image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | <a id="groupvulnerabilitiesimage"></a>`image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. |
...@@ -13627,6 +13629,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -13627,6 +13629,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="projectvulnerabilitiesclusterid"></a>`clusterId` | [`[ClustersClusterID!]`](#clustersclusterid) | Filter vulnerabilities by `cluster_id`. Vulnerabilities with a `reportType` of `cluster_image_scanning` are only included with this filter. |
| <a id="projectvulnerabilitieshasissues"></a>`hasIssues` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have linked issues. | | <a id="projectvulnerabilitieshasissues"></a>`hasIssues` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have linked issues. |
| <a id="projectvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. | | <a id="projectvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. |
| <a id="projectvulnerabilitiesimage"></a>`image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. | | <a id="projectvulnerabilitiesimage"></a>`image` | [`[String!]`](#string) | Filter vulnerabilities by location image. When this filter is present, the response only matches entries for a `reportType` that includes `container_scanning`, `cluster_image_scanning`. |
......
...@@ -37,6 +37,7 @@ module Security ...@@ -37,6 +37,7 @@ module Security
filter_by_scanner_ids filter_by_scanner_ids
filter_by_resolution filter_by_resolution
filter_by_issues filter_by_issues
filter_by_cluster_id
sort(vulnerabilities) sort(vulnerabilities)
end end
...@@ -102,6 +103,12 @@ module Security ...@@ -102,6 +103,12 @@ module Security
end end
end end
def filter_by_cluster_id
if params[:cluster_id].present?
@vulnerabilities = vulnerabilities.with_cluster_ids(params[:cluster_id])
end
end
def sort(items) def sort(items)
items.order_by(params[:sort]) items.order_by(params[:sort])
end end
......
...@@ -50,6 +50,12 @@ module Resolvers ...@@ -50,6 +50,12 @@ module Resolvers
"the response only matches entries for a `reportType` "\ "the response only matches entries for a `reportType` "\
"that includes #{::Vulnerabilities::Finding::REPORT_TYPES_WITH_LOCATION_IMAGE.map { |type| "`#{type}`" }.join(', ')}." "that includes #{::Vulnerabilities::Finding::REPORT_TYPES_WITH_LOCATION_IMAGE.map { |type| "`#{type}`" }.join(', ')}."
argument :cluster_id, [::Types::GlobalIDType[::Clusters::Cluster]],
prepare: ->(ids, _) { ids.map(&:model_id) },
required: false,
description: "Filter vulnerabilities by `cluster_id`. Vulnerabilities with a `reportType` "\
"of `cluster_image_scanning` are only included with this filter."
def resolve_with_lookahead(**args) def resolve_with_lookahead(**args)
return Vulnerability.none unless vulnerable return Vulnerability.none unless vulnerable
......
...@@ -121,6 +121,9 @@ module EE ...@@ -121,6 +121,9 @@ module EE
scope :with_container_image, -> (images) do scope :with_container_image, -> (images) do
joins(:findings).merge(Vulnerabilities::Finding.by_location_image(images)) joins(:findings).merge(Vulnerabilities::Finding.by_location_image(images))
end end
scope :with_cluster_ids, -> (cluster_ids) do
joins(:findings).merge(Vulnerabilities::Finding.by_location_cluster(cluster_ids))
end
delegate :scanner_name, :scanner_external_id, :scanner_id, :metadata, :message, :description, :details, delegate :scanner_name, :scanner_external_id, :scanner_id, :metadata, :message, :description, :details,
to: :finding, prefix: true, allow_nil: true to: :finding, prefix: true, allow_nil: true
......
...@@ -99,6 +99,10 @@ module Vulnerabilities ...@@ -99,6 +99,10 @@ module Vulnerabilities
where(report_type: REPORT_TYPES_WITH_LOCATION_IMAGE) where(report_type: REPORT_TYPES_WITH_LOCATION_IMAGE)
.where("vulnerability_occurrences.location -> 'image' ?| array[:images]", images: images) .where("vulnerability_occurrences.location -> 'image' ?| array[:images]", images: images)
end end
scope :by_location_cluster, -> (cluster_ids) do
where(report_type: 'cluster_image_scanning')
.where("vulnerability_occurrences.location -> 'cluster_id' ?| array[:cluster_ids]", cluster_ids: cluster_ids)
end
def self.counted_by_severity def self.counted_by_severity
group(:severity).count.transform_keys do |severity| group(:severity).count.transform_keys do |severity|
......
...@@ -532,7 +532,8 @@ FactoryBot.define do ...@@ -532,7 +532,8 @@ FactoryBot.define do
"version": "2.24-11+deb9u3" "version": "2.24-11+deb9u3"
}, },
"operating_system": "alpine 3.7", "operating_system": "alpine 3.7",
"image": "alpine:3.7" "image": "alpine:3.7",
"cluster_id": "1"
} }
finding.raw_metadata = { finding.raw_metadata = {
"category": "cluster_image_scanning", "category": "cluster_image_scanning",
...@@ -551,7 +552,8 @@ FactoryBot.define do ...@@ -551,7 +552,8 @@ FactoryBot.define do
"version": "2.24-11+deb9u3" "version": "2.24-11+deb9u3"
}, },
"operating_system": "alpine 3.7", "operating_system": "alpine 3.7",
"image": "alpine:3.7" "image": "alpine:3.7",
"cluster_id": "1"
}, },
"identifiers": [{ "identifiers": [{
"type": "cve", "type": "cve",
......
...@@ -199,4 +199,23 @@ RSpec.describe Security::VulnerabilitiesFinder do ...@@ -199,4 +199,23 @@ RSpec.describe Security::VulnerabilitiesFinder do
end end
end end
end end
context 'when filtered by cluster_id' do
let_it_be(:cluster_vulnerability) { create(:vulnerability, :cluster_image_scanning, project: project) }
let_it_be(:finding) { create(:vulnerabilities_finding, :with_cluster_image_scanning_scanning_metadata, vulnerability: cluster_vulnerability) }
let(:filters) { { cluster_id: [finding.location['cluster_id']] } }
it 'only returns vulnerabilities matching the given cluster_id' do
is_expected.to contain_exactly(cluster_vulnerability)
end
context 'when different report_type is passed' do
let(:filters) { { report_type: %w[dast], cluster_id: [finding.location['cluster_id']] }}
it 'returns empty list' do
is_expected.to be_empty
end
end
end
end end
...@@ -210,5 +210,25 @@ RSpec.describe Resolvers::VulnerabilitiesResolver do ...@@ -210,5 +210,25 @@ RSpec.describe Resolvers::VulnerabilitiesResolver do
end end
end end
end end
context 'when cluster_id is given' do
let_it_be(:cluster_vulnerability) { create(:vulnerability, :cluster_image_scanning, project: project) }
let_it_be(:cluster_finding) { create(:vulnerabilities_finding, :with_cluster_image_scanning_scanning_metadata, vulnerability: cluster_vulnerability) }
let_it_be(:cluster_gid) { ::Gitlab::GlobalId.as_global_id(cluster_finding.location['cluster_id'].to_i, model_name: 'Clusters::Cluster') }
let(:params) { { cluster_id: [cluster_gid] } }
it 'only returns vulnerabilities with given cluster' do
is_expected.to contain_exactly(cluster_vulnerability)
end
context 'when different report_type is given along with cluster' do
let(:params) { { report_type: %w[sast], cluster_id: [cluster_gid] } }
it 'returns empty list' do
is_expected.to be_empty
end
end
end
end end
end end
...@@ -601,6 +601,36 @@ RSpec.describe Vulnerability do ...@@ -601,6 +601,36 @@ RSpec.describe Vulnerability do
end end
end end
describe '.with_cluster_ids' do
let_it_be(:vulnerability) { create(:vulnerability, project: project, report_type: 'cluster_image_scanning') }
let_it_be(:finding) { create(:vulnerabilities_finding, :with_cluster_image_scanning_scanning_metadata, vulnerability: vulnerability) }
let_it_be(:cluster_ids) { [finding.location['cluster_id']] }
before do
finding_with_different_cluster_id = create(
:vulnerabilities_finding,
:with_cluster_image_scanning_scanning_metadata,
vulnerability: create(:vulnerability, report_type: 'cluster_image_scanning')
)
finding_with_different_cluster_id.location['cluster_id'] = '2'
finding_with_different_cluster_id.save!
finding_without_cluster_id = create(
:vulnerabilities_finding,
:with_cluster_image_scanning_scanning_metadata,
vulnerability: create(:vulnerability, report_type: 'cluster_image_scanning')
)
finding_without_cluster_id.location['cluster_id'] = nil
finding_without_cluster_id.save!
end
subject(:cluster_vulnerabilities) { described_class.with_cluster_ids(cluster_ids) }
it 'returns vulnerabilities with given cluster_id' do
expect(cluster_vulnerabilities).to contain_exactly(vulnerability)
end
end
describe 'created_in_time_range' do describe 'created_in_time_range' do
it 'returns vulnerabilities created in given time range', :aggregate_failures do it 'returns vulnerabilities created in given time range', :aggregate_failures do
record1 = create(:vulnerability, created_at: 1.day.ago) record1 = create(:vulnerability, created_at: 1.day.ago)
......
...@@ -363,6 +363,30 @@ RSpec.describe Vulnerabilities::Finding do ...@@ -363,6 +363,30 @@ RSpec.describe Vulnerabilities::Finding do
end end
end end
describe '.by_location_cluster' do
let_it_be(:vulnerability) { create(:vulnerability, report_type: 'cluster_image_scanning') }
let_it_be(:finding) { create(:vulnerabilities_finding, :with_cluster_image_scanning_scanning_metadata, vulnerability: vulnerability) }
let_it_be(:cluster_ids) { [finding.location['cluster_id']] }
before do
finding_with_different_cluster_id = create(
:vulnerabilities_finding,
:with_cluster_image_scanning_scanning_metadata,
vulnerability: create(:vulnerability, report_type: 'cluster_image_scanning')
)
finding_with_different_cluster_id.location['cluster_id'] = '2'
finding_with_different_cluster_id.save!
create(:vulnerabilities_finding, report_type: :dast)
end
subject(:cluster_findings) { described_class.by_location_cluster(cluster_ids) }
it 'returns findings with given cluster_id' do
expect(cluster_findings).to contain_exactly(finding)
end
end
describe '#false_positive?' do describe '#false_positive?' do
let_it_be(:finding) { create(:vulnerabilities_finding) } let_it_be(:finding) { create(:vulnerabilities_finding) }
let_it_be(:finding_with_fp) { create(:vulnerabilities_finding, vulnerability_flags: [create(:vulnerabilities_flag)]) } let_it_be(:finding_with_fp) { create(:vulnerabilities_finding, vulnerability_flags: [create(:vulnerabilities_flag)]) }
......
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