Commit 16b6fc8f authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Tiger Watson

Add filtering for cluster agent in GraphQL Vulnerabilities API

parent 5c65060d
......@@ -511,6 +511,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryvulnerabilitiesclusteragentid"></a>`clusterAgentId` | [`[ClustersAgentID!]`](#clustersagentid) | Filter vulnerabilities by `cluster_agent_id`. Vulnerabilities with a `reportType` of `cluster_image_scanning` are only included with this filter. |
| <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="queryvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. |
......@@ -10995,6 +10996,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupvulnerabilitiesclusteragentid"></a>`clusterAgentId` | [`[ClustersAgentID!]`](#clustersagentid) | Filter vulnerabilities by `cluster_agent_id`. Vulnerabilities with a `reportType` of `cluster_image_scanning` are only included with this filter. |
| <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="groupvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. |
......@@ -13808,6 +13810,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectvulnerabilitiesclusteragentid"></a>`clusterAgentId` | [`[ClustersAgentID!]`](#clustersagentid) | Filter vulnerabilities by `cluster_agent_id`. Vulnerabilities with a `reportType` of `cluster_image_scanning` are only included with this filter. |
| <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="projectvulnerabilitieshasresolution"></a>`hasResolution` | [`Boolean`](#boolean) | Returns only the vulnerabilities which have been resolved on default branch. |
......@@ -177,6 +177,7 @@ You can [configure](#customize-the-cluster-image-scanning-settings) analyzers by
| `CIS_RESOURCE_NAMESPACE` | `""` | Namespace of the Kubernetes resource you want to filter vulnerabilities for. For example, `production`. |
| `CIS_RESOURCE_KIND` | `""` | Kind of the Kubernetes resource you want to filter vulnerabilities for. For example, `deployment`. |
| `CIS_CLUSTER_IDENTIFIER` | `""` | ID of the Kubernetes cluster integrated with GitLab. This is used to map vulnerabilities to the cluster so they can be filtered in the Vulnerability Report page. |
| `CIS_CLUSTER_AGENT_IDENTIFIER` | `""` | ID of the Kubernetes cluster agent integrated with GitLab. This maps vulnerabilities to the agent so they can be filtered in the Vulnerability Report page. |
#### Override the cluster image scanning template
......
......@@ -38,6 +38,7 @@ module Security
filter_by_resolution
filter_by_issues
filter_by_cluster_id
filter_by_cluster_agent_id
sort(vulnerabilities)
end
......@@ -109,6 +110,12 @@ module Security
end
end
def filter_by_cluster_agent_id
if params[:cluster_agent_id].present?
@vulnerabilities = vulnerabilities.with_cluster_agent_ids(params[:cluster_agent_id])
end
end
def sort(items)
items.order_by(params[:sort])
end
......
......@@ -56,6 +56,12 @@ module Resolvers
description: "Filter vulnerabilities by `cluster_id`. Vulnerabilities with a `reportType` "\
"of `cluster_image_scanning` are only included with this filter."
argument :cluster_agent_id, [::Types::GlobalIDType[::Clusters::Agent]],
prepare: ->(ids, _) { ids.map(&:model_id) },
required: false,
description: "Filter vulnerabilities by `cluster_agent_id`. Vulnerabilities with a `reportType` "\
"of `cluster_image_scanning` are only included with this filter."
def resolve_with_lookahead(**args)
return Vulnerability.none unless vulnerable
......
......@@ -123,6 +123,9 @@ module EE
scope :with_cluster_ids, -> (cluster_ids) do
joins(:findings).merge(Vulnerabilities::Finding.by_location_cluster(cluster_ids))
end
scope :with_cluster_agent_ids, -> (agent_ids) do
joins(:findings).merge(Vulnerabilities::Finding.by_location_cluster_agent(agent_ids))
end
delegate :scanner_name, :scanner_external_id, :scanner_id, :metadata, :message, :description, :details,
to: :finding, prefix: true, allow_nil: true
......
......@@ -105,6 +105,10 @@ module Vulnerabilities
where(report_type: 'cluster_image_scanning')
.where("vulnerability_occurrences.location -> 'cluster_id' ?| array[:cluster_ids]", cluster_ids: cluster_ids)
end
scope :by_location_cluster_agent, -> (agent_ids) do
where(report_type: 'cluster_image_scanning')
.where("vulnerability_occurrences.location -> 'agent_id' ?| array[:agent_ids]", agent_ids: agent_ids)
end
def self.counted_by_severity
group(:severity).count.transform_keys do |severity|
......
......@@ -11,6 +11,7 @@ module Gitlab
::Gitlab::Ci::Reports::Security::Locations::ClusterImageScanning.new(
image: location_data['image'],
cluster_id: location_data.dig('kubernetes_resource', 'cluster_id'),
agent_id: location_data.dig('kubernetes_resource', 'agent_id'),
operating_system: location_data['operating_system'],
package_name: location_data.dig('dependency', 'package', 'name'),
package_version: location_data.dig('dependency', 'version'))
......
......@@ -6,9 +6,9 @@ module Gitlab
module Security
module Locations
class ClusterImageScanning < ContainerScanning
attr_reader :cluster_id
attr_reader :cluster_id, :agent_id
def initialize(image:, operating_system:, package_name: nil, package_version: nil, cluster_id: nil)
def initialize(image:, operating_system:, package_name: nil, package_version: nil, cluster_id: nil, agent_id: nil)
super(
image: image,
operating_system: operating_system,
......@@ -16,6 +16,13 @@ module Gitlab
package_version: package_version
)
@cluster_id = cluster_id
@agent_id = agent_id
end
def fingerprint_data
return super if agent_id.blank?
"AGENT_#{agent_id}:#{super}"
end
end
end
......
......@@ -533,7 +533,8 @@ FactoryBot.define do
},
"operating_system": "alpine 3.7",
"image": "alpine:3.7",
"cluster_id": "1"
"cluster_id": "1",
"agent_id": "46357"
}
finding.raw_metadata = {
"category": "cluster_image_scanning",
......@@ -553,7 +554,8 @@ FactoryBot.define do
},
"operating_system": "alpine 3.7",
"image": "alpine:3.7",
"cluster_id": "1"
"cluster_id": "1",
"agent_id": "46357"
},
"identifiers": [{
"type": "cve",
......
......@@ -218,4 +218,23 @@ RSpec.describe Security::VulnerabilitiesFinder do
end
end
end
context 'when filtered by cluster_agent_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_agent_id: [finding.location['agent_id']] } }
it 'only returns vulnerabilities matching the given agent_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_agent_id: [finding.location['agent_id']] }}
it 'returns empty list' do
is_expected.to be_empty
end
end
end
end
......@@ -27,7 +27,8 @@
"name":"sample-app",
"kind":"ReplicaSet",
"container_name":"webgoat",
"cluster_id":"1"
"cluster_id":"1",
"agent_id":"46357"
}
},
"identifiers": [
......@@ -70,7 +71,8 @@
"name":"sample-app",
"kind":"ReplicaSet",
"container_name":"webgoat",
"cluster_id":"1"
"cluster_id":"1",
"agent_id":"46357"
}
},
"identifiers": [
......
......@@ -230,5 +230,25 @@ RSpec.describe Resolvers::VulnerabilitiesResolver do
end
end
end
context 'when cluster_agent_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['agent_id'].to_i, model_name: 'Clusters::Cluster') }
let(:params) { { cluster_agent_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_agent_id: [cluster_gid] } }
it 'returns empty list' do
is_expected.to be_empty
end
end
end
end
end
......@@ -29,6 +29,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::ClusterImageScanning do
expect(location).to have_attributes(
image: image,
cluster_id: '1',
agent_id: '46357',
operating_system: 'debian:9',
package_name: 'glibc',
package_version: '2.24-11+deb9u3'
......
......@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::Security::Locations::ClusterImageScanning do
context 'when cluster_id is provided in finding' do
let(:params) do
{
image: 'registry.gitlab.com/my/project:latest',
......@@ -18,4 +19,23 @@ RSpec.describe Gitlab::Ci::Reports::Security::Locations::ClusterImageScanning do
let(:expected_fingerprint_path) { 'registry.gitlab.com/my/project:glibc' }
it_behaves_like 'vulnerability location'
end
context 'when agent id is provided in finding' do
let(:params) do
{
image: 'registry.gitlab.com/my/project:latest',
agent_id: '46357',
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('AGENT_46357:registry.gitlab.com/my/project:glibc') }
let(:expected_fingerprint_path) { 'AGENT_46357:registry.gitlab.com/my/project:glibc' }
it_behaves_like 'vulnerability location'
end
end
......@@ -631,6 +631,36 @@ RSpec.describe Vulnerability do
end
end
describe '.with_cluster_agent_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_agent_ids) { [finding.location['agent_id']] }
before do
finding_with_different_agent_id = create(
:vulnerabilities_finding,
:with_cluster_image_scanning_scanning_metadata,
vulnerability: create(:vulnerability, report_type: 'cluster_image_scanning')
)
finding_with_different_agent_id.location['agent_id'] = '2'
finding_with_different_agent_id.save!
finding_without_agent_id = create(
:vulnerabilities_finding,
:with_cluster_image_scanning_scanning_metadata,
vulnerability: create(:vulnerability, report_type: 'cluster_image_scanning')
)
finding_without_agent_id.location['agent_id'] = nil
finding_without_agent_id.save!
end
subject(:cluster_agent_vulnerabilities) { described_class.with_cluster_agent_ids(cluster_agent_ids) }
it 'returns vulnerabilities with given agent_id' do
expect(cluster_agent_vulnerabilities).to contain_exactly(vulnerability)
end
end
describe 'created_in_time_range' do
it 'returns vulnerabilities created in given time range', :aggregate_failures do
record1 = create(:vulnerability, created_at: 1.day.ago)
......
......@@ -387,6 +387,30 @@ RSpec.describe Vulnerabilities::Finding do
end
end
describe '.by_location_cluster_agent' 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(:agent_ids) { [finding.location['agent_id']] }
before do
finding_with_different_agent_id = create(
:vulnerabilities_finding,
:with_cluster_image_scanning_scanning_metadata,
vulnerability: create(:vulnerability, report_type: 'cluster_image_scanning')
)
finding_with_different_agent_id.location['agent_id'] = '2'
finding_with_different_agent_id.save!
create(:vulnerabilities_finding, report_type: :dast)
end
subject(:cluster_findings) { described_class.by_location_cluster_agent(agent_ids) }
it 'returns findings with given agent_id' do
expect(cluster_findings).to contain_exactly(finding)
end
end
describe '#false_positive?' do
let_it_be(:finding) { create(:vulnerabilities_finding) }
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