Commit 39b70204 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch 'scanner-awareness' into 'master'

Create GraphQL query to extract analyzers information

See merge request gitlab-org/gitlab!36153
parents 01bb455f 5d3942d2
......@@ -9884,6 +9884,11 @@ type Project {
"""
sastCiConfiguration: SastCiConfiguration
"""
Information about security analyzers used in the project
"""
securityScanners: SecurityScanners
"""
Detailed version of a Sentry error on the project
"""
......@@ -12053,6 +12058,37 @@ type SecurityReportSummarySection {
vulnerabilitiesCount: Int
}
"""
The type of the security scanner.
"""
enum SecurityScannerType {
CONTAINER_SCANNING
DAST
DEPENDENCY_SCANNING
SAST
SECRET_DETECTION
}
"""
Represents a list of security scanners
"""
type SecurityScanners {
"""
List of analyzers which are available for the project.
"""
available: [SecurityScannerType!]
"""
List of analyzers which are enabled for the project.
"""
enabled: [SecurityScannerType!]
"""
List of analyzers which ran successfully in the latest pipeline.
"""
pipelineRun: [SecurityScannerType!]
}
"""
A Sentry error.
"""
......
......@@ -29080,6 +29080,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "securityScanners",
"description": "Information about security analyzers used in the project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "SecurityScanners",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sentryDetailedError",
"description": "Detailed version of a Sentry error on the project",
......@@ -35291,6 +35305,126 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "SecurityScannerType",
"description": "The type of the security scanner.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "SAST",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "DAST",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "DEPENDENCY_SCANNING",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "CONTAINER_SCANNING",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SECRET_DETECTION",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SecurityScanners",
"description": "Represents a list of security scanners",
"fields": [
{
"name": "available",
"description": "List of analyzers which are available for the project.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "SecurityScannerType",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "enabled",
"description": "List of analyzers which are enabled for the project.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "SecurityScannerType",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pipelineRun",
"description": "List of analyzers which ran successfully in the latest pipeline.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "SecurityScannerType",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryDetailedError",
......@@ -1434,6 +1434,7 @@ Information about pagination in a connection.
| `requirement` | Requirement | Find a single requirement. Available only when feature flag `requirements_management` is enabled. |
| `requirementStatesCount` | RequirementStatesCount | Number of requirements for the project by their state |
| `sastCiConfiguration` | SastCiConfiguration | SAST CI configuration for the project |
| `securityScanners` | SecurityScanners | Information about security analyzers used in the project |
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `sentryErrors` | SentryErrorCollection | Paginated collection of Sentry errors on the project |
| `serviceDeskAddress` | String | E-mail address of the service desk. |
......@@ -1746,6 +1747,16 @@ Represents a section of a summary of a security report
| `scannedResourcesCsvPath` | String | Path to download all the scanned resources in CSV format |
| `vulnerabilitiesCount` | Int | Total number of vulnerabilities |
## SecurityScanners
Represents a list of security scanners
| Name | Type | Description |
| --- | ---- | ---------- |
| `available` | SecurityScannerType! => Array | List of analyzers which are available for the project. |
| `enabled` | SecurityScannerType! => Array | List of analyzers which are enabled for the project. |
| `pipelineRun` | SecurityScannerType! => Array | List of analyzers which ran successfully in the latest pipeline. |
## SentryDetailedError
A Sentry error.
......
......@@ -6,6 +6,12 @@ module EE
extend ActiveSupport::Concern
prepended do
field :securityScanners, ::Types::SecurityScanners, null: true,
description: 'Information about security analyzers used in the project',
resolve: -> (project, _args, ctx) do
project
end
field :vulnerabilities,
::Types::VulnerabilityType.connection_type,
null: true,
......
# frozen_string_literal: true
module Types
class SecurityScannerTypeEnum < BaseEnum
graphql_name 'SecurityScannerType'
description 'The type of the security scanner.'
::Security::SecurityJobsFinder.allowed_job_types.each do |scanner|
value scanner.upcase.to_s
end
end
end
# frozen_string_literal: true
module Types
# rubocop: disable Graphql/AuthorizeTypes
class SecurityScanners < BaseObject
graphql_name 'SecurityScanners'
description 'Represents a list of security scanners'
field :enabled, [::Types::SecurityScannerTypeEnum], null: true,
description: 'List of analyzers which are enabled for the project.',
calls_gitaly: true,
resolve: -> (project, _args, ctx) do
project.enabled_scanners
end
field :available, [::Types::SecurityScannerTypeEnum], null: true,
description: 'List of analyzers which are available for the project.',
resolve: -> (project, _args, ctx) do
project.available_scanners
end
field :pipelineRun, [::Types::SecurityScannerTypeEnum], null: true,
description: 'List of analyzers which ran successfully in the latest pipeline.',
calls_gitaly: true,
resolve: -> (project, _args, ctx) do
project.scanners_run_in_last_pipeline
end
end
end
# frozen_string_literal: true
module EE
module ProjectSecurityScannersInformation
include ::Gitlab::Utils::StrongMemoize
include LatestPipelineInformation
def available_scanners
all_security_scanners.map { |scanner| scanner.upcase.to_s if feature_available?(scanner) }.compact
end
def enabled_scanners
all_security_scanners.map { |scanner| scanner.upcase.to_s if scanner_enabled?(scanner) }.compact
end
def scanners_run_in_last_pipeline
latest_builds_reports(only_successful_builds: true).map { |scanner| scanner.upcase.to_s }.compact
end
private
def all_security_scanners
::Security::SecurityJobsFinder.allowed_job_types
end
end
end
# frozen_string_literal: true
module LatestPipelineInformation
private
def scanner_enabled?(scan_type)
latest_builds_reports.include?(scan_type)
end
def latest_builds_reports(only_successful_builds: false)
strong_memoize("latest_builds_reports_#{only_successful_builds}" ) do
builds = latest_security_builds
builds = builds.select { |build| build.status == 'success' } if only_successful_builds
builds.map do |build|
if Feature.enabled?(:ci_build_metadata_config)
build.metadata.config_options[:artifacts][:reports].keys.map(&:to_sym)
else
build.options[:artifacts][:reports].keys
end
end.flatten
end
end
def latest_security_builds
return [] unless latest_default_branch_pipeline
::Security::SecurityJobsFinder.new(pipeline: latest_default_branch_pipeline).execute +
::Security::LicenseComplianceJobsFinder.new(pipeline: latest_default_branch_pipeline).execute
end
def latest_default_branch_pipeline
strong_memoize(:pipeline) { latest_pipeline_for_ref }
end
def auto_devops_source?
latest_default_branch_pipeline&.auto_devops_source?
end
end
......@@ -20,6 +20,7 @@ module EE
include InsightsFeature
include DeprecatedApprovalsBeforeMerge
include UsageStatistics
include ProjectSecurityScannersInformation
ignore_columns :mirror_last_update_at, :mirror_last_successful_update_at, remove_after: '2019-12-15', remove_with: '12.6'
......
......@@ -5,6 +5,7 @@ module Projects
class ConfigurationPresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize
include AutoDevopsHelper
include LatestPipelineInformation
presents :project
......@@ -79,9 +80,7 @@ module Projects
def features
scans = scan_types.map do |scan_type|
if auto_devops_source?
scan(scan_type, configured: true)
elsif latest_builds_reports.include?(scan_type)
if scanner_enabled?(scan_type)
scan(scan_type, configured: true)
else
scan(scan_type, configured: false)
......@@ -92,29 +91,6 @@ module Projects
license_compliance_substitute(scans)
end
def latest_builds_reports
strong_memoize(:reports) do
latest_security_builds.map do |build|
if Feature.enabled?(:ci_build_metadata_config)
build.metadata.config_options[:artifacts][:reports].keys.map(&:to_sym)
else
build.options[:artifacts][:reports].keys
end
end.flatten
end
end
def latest_security_builds
return [] unless latest_default_branch_pipeline
::Security::SecurityJobsFinder.new(pipeline: latest_default_branch_pipeline).execute +
::Security::LicenseComplianceJobsFinder.new(pipeline: latest_default_branch_pipeline).execute
end
def latest_default_branch_pipeline
strong_memoize(:pipeline) { latest_pipeline_for_ref }
end
def latest_pipeline_path
return help_page_path('ci/pipelines') unless latest_default_branch_pipeline
......@@ -150,10 +126,6 @@ module Projects
}
end
def auto_devops_source?
latest_default_branch_pipeline&.auto_devops_source?
end
def scan_types
::Security::SecurityJobsFinder.allowed_job_types + ::Security::LicenseComplianceJobsFinder.allowed_job_types
end
......
---
title: Create GraphQL query to extract analyzers information
merge_request: 36153
author:
type: added
......@@ -22,6 +22,45 @@ RSpec.describe GitlabSchema.types['Project'] do
expect(described_class).to include_graphql_fields(*expected_fields)
end
describe 'security_scanners' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
let_it_be(:user) { create(:user) }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
securityScanners {
enabled
available
pipelineRun
}
}
}
)
end
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
before do
project.add_developer(user)
create(:ci_build, :sast, pipeline: pipeline, status: 'success')
create(:ci_build, :dast, pipeline: pipeline, status: 'success')
create(:ci_build, :secret_detection, pipeline: pipeline, status: 'pending')
end
it 'returns a list of analyzers enabled for the project' do
query_result = subject.dig('data', 'project', 'securityScanners', 'enabled')
expect(query_result).to match_array(%w(SAST DAST SECRET_DETECTION))
end
it 'returns a list of analyzers which were run in the last pipeline for the project' do
query_result = subject.dig('data', 'project', 'securityScanners', 'pipelineRun')
expect(query_result).to match_array(%w(DAST SAST))
end
end
describe 'vulnerabilities' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SecurityScanners'] do
specify { expect(described_class.graphql_name).to eq('SecurityScanners') }
it 'has specific fields' do
expected_fields = %w[enabled available pipelineRun]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::EE::ProjectSecurityScannersInformation do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
before do
create(:ci_build, :sast, pipeline: pipeline, status: 'success')
create(:ci_build, :dast, pipeline: pipeline, status: 'success')
create(:ci_build, :secret_detection, pipeline: pipeline, status: 'pending')
end
describe '#available_scanners' do
before do
allow(project).to receive(:feature_available?) { false }
allow(project).to receive(:feature_available?).with(:sast) { true }
allow(project).to receive(:feature_available?).with(:dast) { true }
end
it 'returns a list of all scanners available for the project' do
expect(project.available_scanners).to match_array(%w(SAST DAST))
end
end
describe '#enabled_scanners' do
it 'returns a list of all scanners enabled for the project' do
expect(project.enabled_scanners).to match_array(%w(SAST DAST SECRET_DETECTION))
end
end
describe '#scanners_run_by_last_pipeline' do
it 'returns a list of all scanners which were run successfully in the latest pipeline' do
expect(project.scanners_run_in_last_pipeline).to match_array(%w(DAST SAST))
end
end
end
......@@ -39,13 +39,16 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
context "when the latest default branch pipeline's source is auto devops" do
before do
create(
pipeline = create(
:ci_pipeline,
:auto_devops_source,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
create(:ci_build, :sast, pipeline: pipeline, status: 'success')
create(:ci_build, :dast, pipeline: pipeline, status: 'success')
create(:ci_build, :secret_detection, pipeline: pipeline, status: 'pending')
end
it 'reports that auto devops is enabled' do
......@@ -56,13 +59,13 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
expect(subject[:can_toggle_auto_fix_settings]).to be_truthy
end
it 'reports that all security jobs are configured' do
it 'reports that all scanners are configured for which latest pipeline has builds' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: true),
security_scan(:sast, configured: true),
security_scan(:container_scanning, configured: true),
security_scan(:dependency_scanning, configured: true),
security_scan(:license_scanning, configured: true),
security_scan(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: true)
)
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