Commit 38d8bf4f authored by Brian Williams's avatar Brian Williams

Add `ClusterImageScanningType` to `VulnerabilitiesResolver`

With the new `cluster_image_scanning` vulnerabilities, we have a new
location type, but it currently is not possible to retrieve the location
data for these vulnerabilities from GraphQL. This change adds
`VulnerabilityLocation::ClusterImageScanningType` in order to make this
possible.

Changelog: added
EE: true
parent 4571d402
...@@ -15635,6 +15635,19 @@ Represents a link related to a vulnerability. ...@@ -15635,6 +15635,19 @@ Represents a link related to a vulnerability.
| <a id="vulnerabilitylinkname"></a>`name` | [`String`](#string) | Name of the link. | | <a id="vulnerabilitylinkname"></a>`name` | [`String`](#string) | Name of the link. |
| <a id="vulnerabilitylinkurl"></a>`url` | [`String!`](#string) | URL of the link. | | <a id="vulnerabilitylinkurl"></a>`url` | [`String!`](#string) | URL of the link. |
### `VulnerabilityLocationClusterImageScanning`
Represents the location of a vulnerability found by a cluster image scan.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="vulnerabilitylocationclusterimagescanningdependency"></a>`dependency` | [`VulnerableDependency`](#vulnerabledependency) | Dependency containing the vulnerability. |
| <a id="vulnerabilitylocationclusterimagescanningimage"></a>`image` | [`String`](#string) | Name of the vulnerable container image. |
| <a id="vulnerabilitylocationclusterimagescanningkubernetesresource"></a>`kubernetesResource` | [`VulnerableKubernetesResource`](#vulnerablekubernetesresource) | Kubernetes resource which uses the vulnerable container image. |
| <a id="vulnerabilitylocationclusterimagescanningoperatingsystem"></a>`operatingSystem` | [`String`](#string) | Operating system that runs on the vulnerable container image. |
### `VulnerabilityLocationContainerScanning` ### `VulnerabilityLocationContainerScanning`
Represents the location of a vulnerability found by a container security scan. Represents the location of a vulnerability found by a container security scan.
...@@ -15785,6 +15798,21 @@ Represents a vulnerable dependency. Used in vulnerability location data. ...@@ -15785,6 +15798,21 @@ Represents a vulnerable dependency. Used in vulnerability location data.
| <a id="vulnerabledependencypackage"></a>`package` | [`VulnerablePackage`](#vulnerablepackage) | Package associated with the vulnerable dependency. | | <a id="vulnerabledependencypackage"></a>`package` | [`VulnerablePackage`](#vulnerablepackage) | Package associated with the vulnerable dependency. |
| <a id="vulnerabledependencyversion"></a>`version` | [`String`](#string) | Version of the vulnerable dependency. | | <a id="vulnerabledependencyversion"></a>`version` | [`String`](#string) | Version of the vulnerable dependency. |
### `VulnerableKubernetesResource`
Represents a vulnerable Kubernetes resource. Used in vulnerability location data.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="vulnerablekubernetesresourceagent"></a>`agent` | [`ClusterAgent`](#clusteragent) | Kubernetes Agent which performed the scan. |
| <a id="vulnerablekubernetesresourceclusterid"></a>`clusterId` | [`ClustersClusterID`](#clustersclusterid) | ID of the Cluster integration which was used to perform the scan. |
| <a id="vulnerablekubernetesresourcecontainername"></a>`containerName` | [`String!`](#string) | Name of the container that had its image scanned. |
| <a id="vulnerablekubernetesresourcekind"></a>`kind` | [`String!`](#string) | Kind of the Kubernetes resource. |
| <a id="vulnerablekubernetesresourcename"></a>`name` | [`String!`](#string) | Name of the Kubernetes resource. |
| <a id="vulnerablekubernetesresourcenamespace"></a>`namespace` | [`String!`](#string) | Kubernetes namespace which the resource resides in. |
### `VulnerablePackage` ### `VulnerablePackage`
Represents a vulnerable package. Used in vulnerability dependency data. Represents a vulnerable package. Used in vulnerability dependency data.
...@@ -17967,6 +17995,7 @@ Represents a vulnerability location. The fields with data will depend on the vul ...@@ -17967,6 +17995,7 @@ Represents a vulnerability location. The fields with data will depend on the vul
One of: One of:
- [`VulnerabilityLocationClusterImageScanning`](#vulnerabilitylocationclusterimagescanning)
- [`VulnerabilityLocationContainerScanning`](#vulnerabilitylocationcontainerscanning) - [`VulnerabilityLocationContainerScanning`](#vulnerabilitylocationcontainerscanning)
- [`VulnerabilityLocationCoverageFuzzing`](#vulnerabilitylocationcoveragefuzzing) - [`VulnerabilityLocationCoverageFuzzing`](#vulnerabilitylocationcoveragefuzzing)
- [`VulnerabilityLocationDast`](#vulnerabilitylocationdast) - [`VulnerabilityLocationDast`](#vulnerabilitylocationdast)
# frozen_string_literal: true
module Types
module VulnerabilityLocation
# Vulnerability locations have their authorization enforced by VulnerabilityType
# rubocop: disable Graphql/AuthorizeTypes
class ClusterImageScanningType < ContainerScanningType
graphql_name 'VulnerabilityLocationClusterImageScanning'
description 'Represents the location of a vulnerability found by a cluster image scan'
field :kubernetes_resource, ::Types::VulnerableKubernetesResourceType, null: true,
description: 'Kubernetes resource which uses the vulnerable container image.'
end
end
end
...@@ -7,7 +7,8 @@ module Types ...@@ -7,7 +7,8 @@ module Types
graphql_name 'VulnerabilityLocation' graphql_name 'VulnerabilityLocation'
description 'Represents a vulnerability location. The fields with data will depend on the vulnerability report type' description 'Represents a vulnerability location. The fields with data will depend on the vulnerability report type'
possible_types VulnerabilityLocation::ContainerScanningType, possible_types VulnerabilityLocation::ClusterImageScanningType,
VulnerabilityLocation::ContainerScanningType,
VulnerabilityLocation::DependencyScanningType, VulnerabilityLocation::DependencyScanningType,
VulnerabilityLocation::DastType, VulnerabilityLocation::DastType,
VulnerabilityLocation::SastType, VulnerabilityLocation::SastType,
...@@ -17,7 +18,9 @@ module Types ...@@ -17,7 +18,9 @@ module Types
def self.resolve_type(object, context) def self.resolve_type(object, context)
case object[:report_type] case object[:report_type]
when 'container_scanning', 'cluster_image_scanning' when 'cluster_image_scanning'
VulnerabilityLocation::ClusterImageScanningType
when 'container_scanning'
VulnerabilityLocation::ContainerScanningType VulnerabilityLocation::ContainerScanningType
when 'dependency_scanning' when 'dependency_scanning'
VulnerabilityLocation::DependencyScanningType VulnerabilityLocation::DependencyScanningType
......
# frozen_string_literal: true
module Types
# Vulnerability locations have their authorization enforced by VulnerabilityType
# rubocop: disable Graphql/AuthorizeTypes
class VulnerableKubernetesResourceType < BaseObject
graphql_name 'VulnerableKubernetesResource'
description 'Represents a vulnerable Kubernetes resource. Used in vulnerability location data'
field :namespace, GraphQL::Types::String, null: false,
description: 'Kubernetes namespace which the resource resides in.'
field :kind, GraphQL::Types::String, null: false,
description: 'Kind of the Kubernetes resource.'
field :name, GraphQL::Types::String, null: false,
description: 'Name of the Kubernetes resource.'
field :container_name, GraphQL::Types::String, null: false,
description: 'Name of the container that had its image scanned.'
field :agent, ::Types::Clusters::AgentType, null: true,
description: 'Kubernetes Agent which performed the scan.'
field :cluster_id, ::Types::GlobalIDType[::Clusters::Cluster], null: true,
description: 'ID of the Cluster integration which was used to perform the scan.'
def agent
::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Clusters::Agent, object['agent_id']).find
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['VulnerabilityLocationClusterImageScanning'] do
it { expect(described_class).to have_graphql_fields(:dependency, :image, :operating_system, :kubernetes_resource) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['VulnerabilityLocation'] do
let(:vulnerability) { create(:vulnerability, report_type: report_type) }
it 'returns all possible types' do
expect(described_class.possible_types).to include(
Types::VulnerabilityLocation::ClusterImageScanningType,
Types::VulnerabilityLocation::ContainerScanningType,
Types::VulnerabilityLocation::DependencyScanningType,
Types::VulnerabilityLocation::DastType,
Types::VulnerabilityLocation::SastType,
Types::VulnerabilityLocation::SecretDetectionType,
Types::VulnerabilityLocation::CoverageFuzzingType,
Types::VulnerabilityLocation::GenericType
)
end
describe '#resolve_type' do
using RSpec::Parameterized::TableSyntax
where(:report_type, :expected_type) do
:cluster_image_scanning | Types::VulnerabilityLocation::ClusterImageScanningType
:container_scanning | Types::VulnerabilityLocation::ContainerScanningType
:dependency_scanning | Types::VulnerabilityLocation::DependencyScanningType
:dast | Types::VulnerabilityLocation::DastType
:api_fuzzing | Types::VulnerabilityLocation::DastType
:sast | Types::VulnerabilityLocation::SastType
:secret_detection | Types::VulnerabilityLocation::SecretDetectionType
:coverage_fuzzing | Types::VulnerabilityLocation::CoverageFuzzingType
:generic | Types::VulnerabilityLocation::GenericType
end
subject do
described_class.resolve_type(vulnerability, {})
end
with_them do
specify { expect(subject).to eq(expected_type) }
end
context 'when report_type is unknown' do
# We have to mock this one since :report_type is an enum on the Vulnerability model.
# Trying to create a vulnerability an invalid report_type causes an ArgumentError.
let(:vulnerability) { { report_type: :unknown_type } }
it 'raises an error' do
expect { subject }.to raise_error(Types::VulnerabilityLocationType::UnexpectedReportType)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['VulnerableKubernetesResource'] do
it { expect(described_class).to have_graphql_fields(:namespace, :kind, :name, :container_name, :agent, :cluster_id) }
end
...@@ -9,11 +9,33 @@ RSpec.describe 'Query.vulnerabilities.location' do ...@@ -9,11 +9,33 @@ RSpec.describe 'Query.vulnerabilities.location' do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, security_dashboard_projects: [project]) } let_it_be(:user) { create(:user, security_dashboard_projects: [project]) }
let_it_be(:agent) { create(:cluster_agent, project: project) }
let_it_be(:fields) do let_it_be(:fields) do
<<~QUERY <<~QUERY
location { location {
__typename __typename
... on VulnerabilityLocationClusterImageScanning {
image
operatingSystem
dependency {
version
package {
name
}
}
kubernetesResource {
namespace
kind
name
containerName
clusterId
agent {
id
name
}
}
}
... on VulnerabilityLocationContainerScanning { ... on VulnerabilityLocationContainerScanning {
image image
operatingSystem operatingSystem
...@@ -154,6 +176,14 @@ RSpec.describe 'Query.vulnerabilities.location' do ...@@ -154,6 +176,14 @@ RSpec.describe 'Query.vulnerabilities.location' do
package: { package: {
name: 'vulnerable_container' name: 'vulnerable_container'
} }
},
kubernetes_resource: {
namespace: "production",
kind: "Deployment",
name: "nginx-deployment",
container_name: "nginx",
cluster_id: "1",
agent_id: agent.id
} }
} }
} }
...@@ -167,14 +197,46 @@ RSpec.describe 'Query.vulnerabilities.location' do ...@@ -167,14 +197,46 @@ RSpec.describe 'Query.vulnerabilities.location' do
) )
end end
it 'returns a container location' do it 'returns a cluster image scanning location' do
location = subject.first['location'] location = subject.first['location']
expect(location['__typename']).to eq('VulnerabilityLocationContainerScanning') expect(location['__typename']).to eq('VulnerabilityLocationClusterImageScanning')
expect(location['image']).to eq('vulnerable_image') expect(location['image']).to eq('vulnerable_image')
expect(location['operatingSystem']).to eq('vulnerable_os') expect(location['operatingSystem']).to eq('vulnerable_os')
expect(location['dependency']['version']).to eq('6.6.6') expect(location['dependency']['version']).to eq('6.6.6')
expect(location['dependency']['package']['name']).to eq('vulnerable_container') expect(location['dependency']['package']['name']).to eq('vulnerable_container')
expect(location['kubernetesResource']['namespace']).to eq('production')
expect(location['kubernetesResource']['kind']).to eq('Deployment')
expect(location['kubernetesResource']['name']).to eq('nginx-deployment')
expect(location['kubernetesResource']['containerName']).to eq('nginx')
expect(location['kubernetesResource']['clusterId']).to eq('gid://gitlab/Clusters::Cluster/1')
end
context 'when user is not authorized to administrate clusters' do
before do
project.add_developer(user)
post_graphql(query, current_user: user)
end
it 'does not return agent data' do
location = subject.first['location']
expect(location['kubernetesResource']['agent']).to be_nil
end
end
context 'when user is authorized to administrate clusters' do
before do
project.add_maintainer(user)
post_graphql(query, current_user: user)
end
it 'returns agent data' do
location = subject.first['location']
expect(location['kubernetesResource']['agent']['id']).to eq("gid://gitlab/Clusters::Agent/#{agent.id}")
expect(location['kubernetesResource']['agent']['name']).to eq(agent.name)
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment