Commit 98e2875f authored by Saikat Sarkar's avatar Saikat Sarkar

Show configuration status of stand-alone secrets analyzer

parent fd05e5ef
...@@ -27,6 +27,7 @@ module Ci ...@@ -27,6 +27,7 @@ module Ci
accessibility: 'gl-accessibility.json', accessibility: 'gl-accessibility.json',
codequality: 'gl-code-quality-report.json', codequality: 'gl-code-quality-report.json',
sast: 'gl-sast-report.json', sast: 'gl-sast-report.json',
secret_detection: 'gl-secret-detection-report.json',
dependency_scanning: 'gl-dependency-scanning-report.json', dependency_scanning: 'gl-dependency-scanning-report.json',
container_scanning: 'gl-container-scanning-report.json', container_scanning: 'gl-container-scanning-report.json',
dast: 'gl-dast-report.json', dast: 'gl-dast-report.json',
...@@ -63,6 +64,7 @@ module Ci ...@@ -63,6 +64,7 @@ module Ci
accessibility: :raw, accessibility: :raw,
codequality: :raw, codequality: :raw,
sast: :raw, sast: :raw,
secret_detection: :raw,
dependency_scanning: :raw, dependency_scanning: :raw,
container_scanning: :raw, container_scanning: :raw,
dast: :raw, dast: :raw,
...@@ -177,7 +179,8 @@ module Ci ...@@ -177,7 +179,8 @@ module Ci
cobertura: 17, cobertura: 17,
terraform: 18, # Transformed json terraform: 18, # Transformed json
accessibility: 19, accessibility: 19,
cluster_applications: 20 cluster_applications: 20,
secret_detection: 21 ## EE-specific
} }
enum file_format: { enum file_format: {
......
...@@ -11989,7 +11989,8 @@ type Vulnerability { ...@@ -11989,7 +11989,8 @@ type Vulnerability {
project: Project project: Project
""" """
Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST) Type of the security report that found the vulnerability (SAST,
DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION)
""" """
reportType: VulnerabilityReportType reportType: VulnerabilityReportType
...@@ -12202,6 +12203,7 @@ enum VulnerabilityReportType { ...@@ -12202,6 +12203,7 @@ enum VulnerabilityReportType {
DAST DAST
DEPENDENCY_SCANNING DEPENDENCY_SCANNING
SAST SAST
SECRET_DETECTION
} }
""" """
......
...@@ -35483,7 +35483,7 @@ ...@@ -35483,7 +35483,7 @@
}, },
{ {
"name": "reportType", "name": "reportType",
"description": "Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST)", "description": "Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION)",
"args": [ "args": [
], ],
...@@ -36156,6 +36156,12 @@ ...@@ -36156,6 +36156,12 @@
"description": null, "description": null,
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "SECRET_DETECTION",
"description": null,
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"possibleTypes": null "possibleTypes": null
...@@ -1812,7 +1812,7 @@ Represents a vulnerability. ...@@ -1812,7 +1812,7 @@ Represents a vulnerability.
| `id` | ID! | GraphQL ID of the vulnerability | | `id` | ID! | GraphQL ID of the vulnerability |
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability | | `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability |
| `project` | Project | The project on which the vulnerability was found | | `project` | Project | The project on which the vulnerability was found |
| `reportType` | VulnerabilityReportType | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST) | | `reportType` | VulnerabilityReportType | Type of the security report that found the vulnerability (SAST, DEPENDENCY_SCANNING, CONTAINER_SCANNING, DAST, SECRET_DETECTION) |
| `severity` | VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) | | `severity` | VulnerabilitySeverity | Severity of the vulnerability (INFO, UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL) |
| `state` | VulnerabilityState | State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED) | | `state` | VulnerabilityState | State of the vulnerability (DETECTED, DISMISSED, RESOLVED, CONFIRMED) |
| `title` | String | Title of the vulnerability | | `title` | String | Title of the vulnerability |
......
...@@ -17,7 +17,7 @@ module EE ...@@ -17,7 +17,7 @@ module EE
before_action :whitelist_query_limiting_ee_merge, only: [:merge] before_action :whitelist_query_limiting_ee_merge, only: [:merge]
before_action :whitelist_query_limiting_ee_show, only: [:show] before_action :whitelist_query_limiting_ee_show, only: [:show]
before_action :authorize_read_pipeline!, only: [:container_scanning_reports, :dependency_scanning_reports, before_action :authorize_read_pipeline!, only: [:container_scanning_reports, :dependency_scanning_reports,
:sast_reports, :dast_reports, :metrics_reports] :sast_reports, :secret_detection_reports, :dast_reports, :metrics_reports]
end end
def approve def approve
...@@ -62,6 +62,10 @@ module EE ...@@ -62,6 +62,10 @@ module EE
reports_response(merge_request.compare_sast_reports(current_user)) reports_response(merge_request.compare_sast_reports(current_user))
end end
def secret_detection_reports
reports_response(merge_request.compare_secret_detection_reports(current_user))
end
def dast_reports def dast_reports
reports_response(merge_request.compare_dast_reports(current_user)) reports_response(merge_request.compare_dast_reports(current_user))
end end
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
module Security module Security
class SecurityJobsFinder < JobsFinder class SecurityJobsFinder < JobsFinder
def self.allowed_job_types def self.allowed_job_types
[:sast, :dast, :dependency_scanning, :container_scanning] [:sast, :dast, :dependency_scanning, :container_scanning, :secret_detection]
end end
end end
end end
...@@ -12,6 +12,7 @@ module EE ...@@ -12,6 +12,7 @@ module EE
LICENSED_PARSER_FEATURES = { LICENSED_PARSER_FEATURES = {
sast: :sast, sast: :sast,
secret_detection: :secret_detection,
dependency_scanning: :dependency_scanning, dependency_scanning: :dependency_scanning,
container_scanning: :container_scanning, container_scanning: :container_scanning,
dast: :dast dast: :dast
......
...@@ -11,12 +11,13 @@ module EE ...@@ -11,12 +11,13 @@ module EE
prepended do prepended do
after_destroy :log_geo_deleted_event after_destroy :log_geo_deleted_event
SECURITY_REPORT_FILE_TYPES = %w[sast dependency_scanning container_scanning dast].freeze SECURITY_REPORT_FILE_TYPES = %w[sast secret_detection dependency_scanning container_scanning dast].freeze
LICENSE_SCANNING_REPORT_FILE_TYPES = %w[license_management license_scanning].freeze LICENSE_SCANNING_REPORT_FILE_TYPES = %w[license_management license_scanning].freeze
DEPENDENCY_LIST_REPORT_FILE_TYPES = %w[dependency_scanning].freeze DEPENDENCY_LIST_REPORT_FILE_TYPES = %w[dependency_scanning].freeze
METRICS_REPORT_FILE_TYPES = %w[metrics].freeze METRICS_REPORT_FILE_TYPES = %w[metrics].freeze
CONTAINER_SCANNING_REPORT_TYPES = %w[container_scanning].freeze CONTAINER_SCANNING_REPORT_TYPES = %w[container_scanning].freeze
SAST_REPORT_TYPES = %w[sast].freeze SAST_REPORT_TYPES = %w[sast].freeze
SECRET_DETECTION_REPORT_TYPES = %w[secret_detection].freeze
DAST_REPORT_TYPES = %w[dast].freeze DAST_REPORT_TYPES = %w[dast].freeze
scope :not_expired, -> { where('expire_at IS NULL OR expire_at > ?', Time.current) } scope :not_expired, -> { where('expire_at IS NULL OR expire_at > ?', Time.current) }
...@@ -43,6 +44,10 @@ module EE ...@@ -43,6 +44,10 @@ module EE
with_file_types(SAST_REPORT_TYPES) with_file_types(SAST_REPORT_TYPES)
end end
scope :secret_detection_reports, -> do
with_file_types(SECRET_DETECTION_REPORT_TYPES)
end
scope :dast_reports, -> do scope :dast_reports, -> do
with_file_types(DAST_REPORT_TYPES) with_file_types(DAST_REPORT_TYPES)
end end
......
...@@ -29,7 +29,7 @@ module EE ...@@ -29,7 +29,7 @@ module EE
# Legacy way to fetch security reports based on job name. This has been replaced by the reports feature. # Legacy way to fetch security reports based on job name. This has been replaced by the reports feature.
scope :with_legacy_security_reports, -> do scope :with_legacy_security_reports, -> do
joins(:artifacts).where(ci_builds: { name: %w[sast dependency_scanning sast:container container_scanning dast] }) joins(:artifacts).where(ci_builds: { name: %w[sast secret_detection dependency_scanning sast:container container_scanning dast] })
end end
scope :with_vulnerabilities, -> do scope :with_vulnerabilities, -> do
...@@ -41,6 +41,7 @@ module EE ...@@ -41,6 +41,7 @@ module EE
REPORT_LICENSED_FEATURES = { REPORT_LICENSED_FEATURES = {
codequality: nil, codequality: nil,
sast: %i[sast], sast: %i[sast],
secret_detection: %i[secret_detection],
dependency_scanning: %i[dependency_scanning], dependency_scanning: %i[dependency_scanning],
container_scanning: %i[container_scanning], container_scanning: %i[container_scanning],
dast: %i[dast], dast: %i[dast],
......
...@@ -182,12 +182,22 @@ module EE ...@@ -182,12 +182,22 @@ module EE
!!(actual_head_pipeline&.has_reports?(::Ci::JobArtifact.sast_reports)) !!(actual_head_pipeline&.has_reports?(::Ci::JobArtifact.sast_reports))
end end
def has_secret_detection_reports?
!!(actual_head_pipeline&.has_reports?(::Ci::JobArtifact.secret_detection_reports))
end
def compare_sast_reports(current_user) def compare_sast_reports(current_user)
return missing_report_error("SAST") unless has_sast_reports? return missing_report_error("SAST") unless has_sast_reports?
compare_reports(::Ci::CompareSastReportsService, current_user) compare_reports(::Ci::CompareSastReportsService, current_user)
end end
def compare_secret_detection_reports(current_user)
return missing_report_error("secret detection") unless has_secret_detection_reports?
compare_reports(::Ci::CompareSecretDetectionReportsService, current_user)
end
def has_dast_reports? def has_dast_reports?
!!(actual_head_pipeline&.has_reports?(::Ci::JobArtifact.dast_reports)) !!(actual_head_pipeline&.has_reports?(::Ci::JobArtifact.dast_reports))
end end
......
...@@ -294,6 +294,7 @@ module EE ...@@ -294,6 +294,7 @@ module EE
def store_security_reports_available? def store_security_reports_available?
feature_available?(:sast) || feature_available?(:sast) ||
feature_available?(:secret_detection) ||
feature_available?(:dependency_scanning) || feature_available?(:dependency_scanning) ||
feature_available?(:container_scanning) || feature_available?(:container_scanning) ||
feature_available?(:dast) feature_available?(:dast)
......
...@@ -127,6 +127,7 @@ class License < ApplicationRecord ...@@ -127,6 +127,7 @@ class License < ApplicationRecord
report_approver_rules report_approver_rules
requirements requirements
sast sast
secret_detection
security_dashboard security_dashboard
status_page status_page
subepics subepics
......
...@@ -14,7 +14,8 @@ module Security ...@@ -14,7 +14,8 @@ module Security
sast: 1, sast: 1,
dependency_scanning: 2, dependency_scanning: 2,
container_scanning: 3, container_scanning: 3,
dast: 4 dast: 4,
secret_detection: 5
} }
end end
end end
...@@ -15,7 +15,7 @@ module Vulnerabilities ...@@ -15,7 +15,7 @@ module Vulnerabilities
attr_accessor :vulnerability_data attr_accessor :vulnerability_data
enum feedback_type: { dismissal: 0, issue: 1, merge_request: 2 }, _prefix: :for enum feedback_type: { dismissal: 0, issue: 1, merge_request: 2 }, _prefix: :for
enum category: { sast: 0, dependency_scanning: 1, container_scanning: 2, dast: 3 } enum category: { sast: 0, dependency_scanning: 1, container_scanning: 2, dast: 3, secret_detection: 4 }
validates :project, presence: true validates :project, presence: true
validates :author, presence: true validates :author, presence: true
......
...@@ -53,7 +53,8 @@ module Vulnerabilities ...@@ -53,7 +53,8 @@ module Vulnerabilities
sast: 0, sast: 0,
dependency_scanning: 1, dependency_scanning: 1,
container_scanning: 2, container_scanning: 2,
dast: 3 dast: 3,
secret_detection: 4
}.with_indifferent_access.freeze }.with_indifferent_access.freeze
enum confidence: CONFIDENCE_LEVELS, _prefix: :confidence enum confidence: CONFIDENCE_LEVELS, _prefix: :confidence
......
...@@ -22,9 +22,10 @@ module EE ...@@ -22,9 +22,10 @@ module EE
return false unless can?(current_user, :read_vulnerability, pipeline.project) return false unless can?(current_user, :read_vulnerability, pipeline.project)
batch_lookup_report_artifact_for_file_type(:sast) || batch_lookup_report_artifact_for_file_type(:sast) ||
batch_lookup_report_artifact_for_file_type(:dependency_scanning) || batch_lookup_report_artifact_for_file_type(:secret_detection) ||
batch_lookup_report_artifact_for_file_type(:dast) || batch_lookup_report_artifact_for_file_type(:dependency_scanning) ||
batch_lookup_report_artifact_for_file_type(:container_scanning) batch_lookup_report_artifact_for_file_type(:dast) ||
batch_lookup_report_artifact_for_file_type(:container_scanning)
end end
def downloadable_path_for_report_type(file_type) def downloadable_path_for_report_type(file_type)
......
...@@ -13,7 +13,8 @@ module Projects ...@@ -13,7 +13,8 @@ module Projects
dependency_scanning: 'user/application_security/dependency_scanning/index', dependency_scanning: 'user/application_security/dependency_scanning/index',
license_management: 'user/compliance/license_compliance/index', license_management: 'user/compliance/license_compliance/index',
license_scanning: 'user/compliance/license_compliance/index', license_scanning: 'user/compliance/license_compliance/index',
sast: 'user/application_security/sast/index' sast: 'user/application_security/sast/index',
secret_detection: 'user/application_security/secret_detection/index'
}.freeze }.freeze
def self.localized_scan_descriptions def self.localized_scan_descriptions
...@@ -23,7 +24,8 @@ module Projects ...@@ -23,7 +24,8 @@ module Projects
dependency_scanning: _('Analyze your dependencies for known vulnerabilities.'), dependency_scanning: _('Analyze your dependencies for known vulnerabilities.'),
license_management: _('Search your project dependencies for their licenses and apply policies.'), license_management: _('Search your project dependencies for their licenses and apply policies.'),
license_scanning: _('Search your project dependencies for their licenses and apply policies.'), license_scanning: _('Search your project dependencies for their licenses and apply policies.'),
sast: _('Analyze your source code for known vulnerabilities.') sast: _('Analyze your source code for known vulnerabilities.'),
secret_detection: _('Analyze your source code and git history for secrets')
}.freeze }.freeze
end end
...@@ -34,7 +36,8 @@ module Projects ...@@ -34,7 +36,8 @@ module Projects
dependency_scanning: _('Dependency Scanning'), dependency_scanning: _('Dependency Scanning'),
license_management: 'License Management', license_management: 'License Management',
license_scanning: _('License Compliance'), license_scanning: _('License Compliance'),
sast: _('Static Application Security Testing (SAST)') sast: _('Static Application Security Testing (SAST)'),
secret_detection: _('Secret Detection')
}.freeze }.freeze
end end
......
# frozen_string_literal: true
module Ci
class CompareSecretDetectionReportsService < ::Ci::CompareReportsBaseService
def comparer_class
Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer
end
def serializer_class
Vulnerabilities::FindingDiffSerializer
end
def get_report(pipeline)
Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: { report_type: %w[secret_detection], scope: 'all' }).execute
end
def build_comparer(base_pipeline, head_pipeline)
comparer_class.new(get_report(base_pipeline), get_report(head_pipeline), head_security_scans: head_pipeline.security_scans)
end
end
end
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
window.gl.mrWidgetData.is_geo_secondary_node = '#{Gitlab::Geo.secondary?}' === 'true'; window.gl.mrWidgetData.is_geo_secondary_node = '#{Gitlab::Geo.secondary?}' === 'true';
window.gl.mrWidgetData.geo_secondary_help_path = '#{help_page_path("administration/geo/replication/configuration.md")}'; window.gl.mrWidgetData.geo_secondary_help_path = '#{help_page_path("administration/geo/replication/configuration.md")}';
window.gl.mrWidgetData.sast_help_path = '#{help_page_path("user/application_security/sast/index")}'; window.gl.mrWidgetData.sast_help_path = '#{help_page_path("user/application_security/sast/index")}';
window.gl.mrWidgetData.secret_detection_help_path = '#{help_page_path("user/application_security/secret_detection/index")}';
window.gl.mrWidgetData.container_scanning_help_path = '#{help_page_path("user/application_security/container_scanning/index")}'; window.gl.mrWidgetData.container_scanning_help_path = '#{help_page_path("user/application_security/container_scanning/index")}';
window.gl.mrWidgetData.dast_help_path = '#{help_page_path("user/application_security/dast/index")}'; window.gl.mrWidgetData.dast_help_path = '#{help_page_path("user/application_security/dast/index")}';
window.gl.mrWidgetData.dependency_scanning_help_path = '#{help_page_path("user/application_security/dependency_scanning/index")}'; window.gl.mrWidgetData.dependency_scanning_help_path = '#{help_page_path("user/application_security/dependency_scanning/index")}';
......
---
title: Show the status of stand-alone secrets analyzer on the configuration page
merge_request: 31167
author:
type: added
...@@ -9,6 +9,7 @@ resources :merge_requests, only: [], constraints: { id: /\d+/ } do ...@@ -9,6 +9,7 @@ resources :merge_requests, only: [], constraints: { id: /\d+/ } do
get :container_scanning_reports get :container_scanning_reports
get :dependency_scanning_reports get :dependency_scanning_reports
get :sast_reports get :sast_reports
get :secret_detection_reports
get :dast_reports get :dast_reports
get :approvals get :approvals
......
...@@ -15,6 +15,7 @@ module EE ...@@ -15,6 +15,7 @@ module EE
container_scanning: ::Gitlab::Ci::Parsers::Security::ContainerScanning, container_scanning: ::Gitlab::Ci::Parsers::Security::ContainerScanning,
dast: ::Gitlab::Ci::Parsers::Security::Dast, dast: ::Gitlab::Ci::Parsers::Security::Dast,
sast: ::Gitlab::Ci::Parsers::Security::Sast, sast: ::Gitlab::Ci::Parsers::Security::Sast,
secret_detection: ::Gitlab::Ci::Parsers::Security::SecretDetection,
metrics: ::Gitlab::Ci::Parsers::Metrics::Generic metrics: ::Gitlab::Ci::Parsers::Metrics::Generic
}) })
end end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Parsers
module Security
class SecretDetection < Common
include Security::Concerns::DeprecatedSyntax
DEPRECATED_REPORT_VERSION = "1.2".freeze
private
def create_location(location_data)
::Gitlab::Ci::Reports::Security::Locations::SecretDetection.new(
file_path: location_data['file'],
start_line: location_data['start_line'],
end_line: location_data['end_line'],
class_name: location_data['class'],
method_name: location_data['method']
)
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
module Locations
class SecretDetection < Base
attr_reader :class_name
attr_reader :end_line
attr_reader :file_path
attr_reader :method_name
attr_reader :start_line
def initialize(file_path:, start_line:, end_line: nil, class_name: nil, method_name: nil)
@class_name = class_name
@end_line = end_line
@file_path = file_path
@method_name = method_name
@start_line = start_line
end
private
def fingerprint_data
"#{file_path}:#{start_line}:#{end_line}"
end
end
end
end
end
end
end
...@@ -628,6 +628,77 @@ describe Projects::MergeRequestsController do ...@@ -628,6 +628,77 @@ describe Projects::MergeRequestsController do
it_behaves_like 'authorize read pipeline' it_behaves_like 'authorize read pipeline'
end end
describe 'GET #secret_detection_reports' do
let(:merge_request) { create(:ee_merge_request, :with_secret_detection_reports, source_project: project, author: create(:user)) }
let(:params) do
{
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid
}
end
subject { get :secret_detection_reports, params: params, format: :json }
before do
allow_any_instance_of(::MergeRequest).to receive(:compare_reports)
.with(::Ci::CompareSecretDetectionReportsService, project.users.first).and_return(comparison_status)
end
context 'when comparison is being processed' do
let(:comparison_status) { { status: :parsing } }
it 'sends polling interval' do
expect(::Gitlab::PollingInterval).to receive(:set_header)
subject
end
it 'returns 204 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when comparison is done' do
let(:comparison_status) { { status: :parsed, data: { added: [], fixed: [], existing: [] } } }
it 'does not send polling interval' do
expect(::Gitlab::PollingInterval).not_to receive(:set_header)
subject
end
it 'returns 200 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq({ "added" => [], "fixed" => [], "existing" => [] })
end
end
context 'when user created corrupted vulnerability reports' do
let(:comparison_status) { { status: :error, status_reason: 'Failed to parse secret detection reports' } }
it 'does not send polling interval' do
expect(::Gitlab::PollingInterval).not_to receive(:set_header)
subject
end
it 'returns 400 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq({ 'status_reason' => 'Failed to parse secret detection reports' })
end
end
it_behaves_like 'authorize read pipeline'
end
describe 'GET #dast_reports' do describe 'GET #dast_reports' do
let(:merge_request) { create(:ee_merge_request, :with_dast_reports, source_project: project) } let(:merge_request) { create(:ee_merge_request, :with_dast_reports, source_project: project) }
let(:params) do let(:params) do
......
...@@ -6,7 +6,7 @@ FactoryBot.define do ...@@ -6,7 +6,7 @@ FactoryBot.define do
failure_reason { Ci::Build.failure_reasons[:protected_environment_failure] } failure_reason { Ci::Build.failure_reasons[:protected_environment_failure] }
end end
%i[codequality container_scanning dast dependency_scanning license_management license_scanning performance sast].each do |report_type| %i[codequality container_scanning dast dependency_scanning license_management license_scanning performance sast secret_detection].each do |report_type|
trait "legacy_#{report_type}".to_sym do trait "legacy_#{report_type}".to_sym do
success success
artifacts artifacts
...@@ -54,6 +54,12 @@ FactoryBot.define do ...@@ -54,6 +54,12 @@ FactoryBot.define do
end end
end end
trait :secret_detection_feature_branch do
after(:build) do |build|
build.job_artifacts << create(:ee_ci_job_artifact, :secret_detection_feature_branch, job: build)
end
end
trait :dast_feature_branch do trait :dast_feature_branch do
after(:build) do |build| after(:build) do |build|
build.job_artifacts << create(:ee_ci_job_artifact, :dast_feature_branch, job: build) build.job_artifacts << create(:ee_ci_job_artifact, :dast_feature_branch, job: build)
......
...@@ -12,6 +12,16 @@ FactoryBot.define do ...@@ -12,6 +12,16 @@ FactoryBot.define do
end end
end end
trait :secret_detection do
file_type { :secret_detection }
file_format { :raw }
after(:build) do |artifact, _|
artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/security_reports/master/gl-secret-detection-report.json'), 'application/json')
end
end
trait :dast do trait :dast do
file_format { :raw } file_format { :raw }
file_type { :dast } file_type { :dast }
...@@ -119,6 +129,16 @@ FactoryBot.define do ...@@ -119,6 +129,16 @@ FactoryBot.define do
end end
end end
trait :secret_detection_feature_branch do
file_format { :raw }
file_type { :secret_detection }
after(:build) do |artifact, _|
artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/security_reports/feature-branch/gl-secret-detection-report.json'), 'application/json')
end
end
trait :sast_deprecated do trait :sast_deprecated do
file_type { :sast } file_type { :sast }
file_format { :raw } file_format { :raw }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
FactoryBot.define do FactoryBot.define do
factory :ee_ci_pipeline, class: 'Ci::Pipeline', parent: :ci_pipeline do factory :ee_ci_pipeline, class: 'Ci::Pipeline', parent: :ci_pipeline do
%i[container_scanning dast dependency_list dependency_scanning license_management license_scanning sast].each do |report_type| %i[container_scanning dast dependency_list dependency_scanning license_management license_scanning sast secret_detection].each do |report_type|
trait "with_#{report_type}_report".to_sym do trait "with_#{report_type}_report".to_sym do
status { :success } status { :success }
...@@ -52,6 +52,14 @@ FactoryBot.define do ...@@ -52,6 +52,14 @@ FactoryBot.define do
end end
end end
trait :with_secret_detection_feature_branch do
status { :success }
after(:build) do |pipeline, evaluator|
pipeline.builds << build(:ee_ci_build, :secret_detection_feature_branch, pipeline: pipeline, project: pipeline.project)
end
end
trait :with_dast_feature_branch do trait :with_dast_feature_branch do
status { :success } status { :success }
......
...@@ -133,6 +133,18 @@ FactoryBot.define do ...@@ -133,6 +133,18 @@ FactoryBot.define do
end end
end end
trait :with_secret_detection_reports do
after(:build) do |merge_request|
merge_request.head_pipeline = build(
:ee_ci_pipeline,
:success,
:with_secret_detection_report,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
end
trait :with_dast_reports do trait :with_dast_reports do
after(:build) do |merge_request| after(:build) do |merge_request|
merge_request.head_pipeline = build( merge_request.head_pipeline = build(
......
...@@ -15,12 +15,14 @@ describe Security::SecurityJobsFinder do ...@@ -15,12 +15,14 @@ describe Security::SecurityJobsFinder do
let!(:sast_build) { create(:ci_build, :sast, pipeline: pipeline) } let!(:sast_build) { create(:ci_build, :sast, pipeline: pipeline) }
let!(:container_scanning_build) { create(:ci_build, :container_scanning, pipeline: pipeline) } let!(:container_scanning_build) { create(:ci_build, :container_scanning, pipeline: pipeline) }
let!(:dast_build) { create(:ci_build, :dast, pipeline: pipeline) } let!(:dast_build) { create(:ci_build, :dast, pipeline: pipeline) }
let!(:secret_detection_build) { create(:ci_build, :secret_detection, pipeline: pipeline) }
let(:finder) { described_class.new(pipeline: pipeline, job_types: [:sast, :container_scanning]) } let(:finder) { described_class.new(pipeline: pipeline, job_types: [:sast, :container_scanning, :secret_detection]) }
it 'returns only those requested' do it 'returns only those requested' do
is_expected.to include(sast_build) is_expected.to include(sast_build)
is_expected.to include(container_scanning_build) is_expected.to include(container_scanning_build)
is_expected.to include(secret_detection_build)
is_expected.not_to include(dast_build) is_expected.not_to include(dast_build)
end end
...@@ -30,13 +32,14 @@ describe Security::SecurityJobsFinder do ...@@ -30,13 +32,14 @@ describe Security::SecurityJobsFinder do
let!(:sast_build) { create(:ci_build, :sast, pipeline: pipeline) } let!(:sast_build) { create(:ci_build, :sast, pipeline: pipeline) }
let!(:container_scanning_build) { create(:ci_build, :container_scanning, pipeline: pipeline) } let!(:container_scanning_build) { create(:ci_build, :container_scanning, pipeline: pipeline) }
let!(:dast_build) { create(:ci_build, :dast, pipeline: pipeline) } let!(:dast_build) { create(:ci_build, :dast, pipeline: pipeline) }
let!(:secret_detection_build) { create(:ci_build, :secret_detection, pipeline: pipeline) }
let!(:license_management_build) { create(:ci_build, :license_management, pipeline: pipeline) } let!(:license_management_build) { create(:ci_build, :license_management, pipeline: pipeline) }
it 'returns only the security jobs' do it 'returns only the security jobs' do
is_expected.to include(sast_build) is_expected.to include(sast_build)
is_expected.to include(container_scanning_build) is_expected.to include(container_scanning_build)
is_expected.to include(dast_build) is_expected.to include(dast_build)
is_expected.to include(secret_detection_build)
is_expected.not_to include(license_management_build) is_expected.not_to include(license_management_build)
end end
end end
......
{
"version": "3.0",
"vulnerabilities": [],
"remediations": []
}
{
"version": "3.0",
"vulnerabilities": [
{
"id": "27d2322d519c94f803ffed1cf6d14e455df97e5a0668e229eb853fdb0d277d2c",
"category": "secret_detection",
"name": "AWS API key",
"message": "AWS API key",
"description": "Historic AWS secret has been found in commit 0830d9e4c0b43c0533cde798841b499e9df0653a.",
"cve": "aws-key.py:e275768c071cf6a6ea70a70b40f27c98debfe26bfe623c1539ec21c4478c6fca:AWS",
"severity": "Critical",
"confidence": "Unknown",
"scanner": {
"id": "gitleaks",
"name": "Gitleaks"
},
"location": {
"file": "aws-key.py",
"dependency": {
"package": {}
}
},
"identifiers": [
{
"type": "gitleaks_rule_id",
"name": "Gitleaks rule ID AWS",
"value": "AWS"
}
]
}
],
"remediations": []
}
...@@ -4,6 +4,6 @@ require 'spec_helper' ...@@ -4,6 +4,6 @@ require 'spec_helper'
describe GitlabSchema.types['VulnerabilityReportType'] do describe GitlabSchema.types['VulnerabilityReportType'] do
it 'exposes all vulnerability report types' do it 'exposes all vulnerability report types' do
expect(described_class.values.keys).to contain_exactly(*%w[SAST DAST CONTAINER_SCANNING DEPENDENCY_SCANNING]) expect(described_class.values.keys).to contain_exactly(*%w[SAST SECRET_DETECTION DAST CONTAINER_SCANNING DEPENDENCY_SCANNING])
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Parsers::Security::SecretDetection do
describe '#parse!' do
subject(:parser) { described_class.new }
let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
let(:created_at) { 2.weeks.ago }
context "when parsing valid reports" do
where(report_format: %i(secret_detection))
with_them do
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, commit_sha, created_at) }
let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do
artifact.each_blob do |blob|
parser.parse!(blob, report)
end
end
it "parses all identifiers and occurrences" do
expect(report.occurrences.length).to eq(1)
expect(report.identifiers.length).to eq(1)
expect(report.scanners.length).to eq(1)
end
it 'generates expected location' do
location = report.occurrences.first.location
expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::SecretDetection)
expect(location).to have_attributes(
file_path: 'aws-key.py',
start_line: nil,
end_line: nil,
class_name: nil,
method_name: nil
)
end
it "generates expected metadata_version" do
expect(report.occurrences.first.metadata_version).to eq('3.0')
end
end
end
context "when parsing an empty report" do
let(:report) { Gitlab::Ci::Reports::Security::Report.new('secret_detection', commit_sha, created_at) }
let(:blob) { Gitlab::Json.generate({}) }
it { expect(parser.parse!(blob, report)).to be_empty }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Locations::SecretDetection do
let(:params) do
{
file_path: 'src/main/App.java',
start_line: 29,
end_line: 31,
class_name: 'com.gitlab.security_products.tests.App',
method_name: 'insecureCypher'
}
end
let(:mandatory_params) { %i[file_path start_line] }
let(:expected_fingerprint) { Digest::SHA1.hexdigest('src/main/App.java:29:31') }
it_behaves_like 'vulnerability location'
end
...@@ -56,9 +56,10 @@ describe EE::Ci::JobArtifact do ...@@ -56,9 +56,10 @@ describe EE::Ci::JobArtifact do
subject { Ci::JobArtifact.security_reports } subject { Ci::JobArtifact.security_reports }
context 'when there is a security report' do context 'when there is a security report' do
let!(:artifact) { create(:ee_ci_job_artifact, :sast) } let!(:sast_artifact) { create(:ee_ci_job_artifact, :sast) }
let!(:secret_detection_artifact) { create(:ee_ci_job_artifact, :secret_detection) }
it { is_expected.to eq([artifact]) } it { is_expected.to eq([sast_artifact, secret_detection_artifact]) }
end end
context 'when there are no security reports' do context 'when there are no security reports' do
......
...@@ -1284,7 +1284,7 @@ describe Namespace do ...@@ -1284,7 +1284,7 @@ describe Namespace do
subject { namespace.store_security_reports_available? } subject { namespace.store_security_reports_available? }
context 'when at least one security report feature is enabled' do context 'when at least one security report feature is enabled' do
where(report_type: [:sast, :dast, :dependency_scanning, :container_scanning]) where(report_type: [:sast, :secret_detection, :dast, :dependency_scanning, :container_scanning])
with_them do with_them do
before do before do
......
...@@ -207,6 +207,28 @@ describe MergeRequest do ...@@ -207,6 +207,28 @@ describe MergeRequest do
end end
end end
describe '#has_secret_detection_reports?' do
subject { merge_request.has_secret_detection_reports? }
let(:project) { create(:project, :repository) }
before do
stub_licensed_features(secret_detection: true)
end
context 'when head pipeline has secret detection reports' do
let(:merge_request) { create(:ee_merge_request, :with_secret_detection_reports, source_project: project) }
it { is_expected.to be_truthy }
end
context 'when head pipeline does not have secrets detection reports' do
let(:merge_request) { create(:ee_merge_request, source_project: project) }
it { is_expected.to be_falsey }
end
end
describe '#has_dast_reports?' do describe '#has_dast_reports?' do
subject { merge_request.has_dast_reports? } subject { merge_request.has_dast_reports? }
...@@ -336,6 +358,66 @@ describe MergeRequest do ...@@ -336,6 +358,66 @@ describe MergeRequest do
end end
end end
describe '#compare_secret_detection_reports' do
subject { merge_request.compare_secret_detection_reports(current_user) }
let(:project) { create(:project, :repository) }
let(:current_user) { project.users.first }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:base_pipeline) do
create(:ee_ci_pipeline,
:with_secret_detection_report,
project: project,
ref: merge_request.target_branch,
sha: merge_request.diff_base_sha)
end
before do
merge_request.update!(head_pipeline_id: head_pipeline.id)
end
context 'when head pipeline has secret detection reports' do
let!(:head_pipeline) do
create(:ee_ci_pipeline,
:with_secret_detection_report,
project: project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
context 'when reactive cache worker is parsing asynchronously' do
it 'returns status' do
expect(subject[:status]).to eq(:parsing)
end
end
context 'when reactive cache worker is inline' do
before do
synchronous_reactive_cache(merge_request)
end
it 'returns status and data' do
expect_any_instance_of(Ci::CompareSecretDetectionReportsService)
.to receive(:execute).with(base_pipeline, head_pipeline).and_call_original
subject
end
context 'when cached results is not latest' do
before do
allow_any_instance_of(Ci::CompareSecretDetectionReportsService)
.to receive(:latest?).and_return(false)
end
it 'raises and InvalidateReactiveCache error' do
expect { subject }.to raise_error(ReactiveCaching::InvalidateReactiveCache)
end
end
end
end
end
describe '#compare_sast_reports' do describe '#compare_sast_reports' do
subject { merge_request.compare_sast_reports(current_user) } subject { merge_request.compare_sast_reports(current_user) }
......
...@@ -132,6 +132,9 @@ describe Vulnerabilities::Occurrence do ...@@ -132,6 +132,9 @@ describe Vulnerabilities::Occurrence do
create_list(:vulnerabilities_occurrence, 1, create_list(:vulnerabilities_occurrence, 1,
pipelines: [pipeline], project: project, report_type: :dast, severity: :low) pipelines: [pipeline], project: project, report_type: :dast, severity: :low)
create_list(:vulnerabilities_occurrence, 2,
pipelines: [pipeline], project: project, report_type: :secret_detection, severity: :critical)
end end
end end
...@@ -145,7 +148,7 @@ describe Vulnerabilities::Occurrence do ...@@ -145,7 +148,7 @@ describe Vulnerabilities::Occurrence do
let(:range) { 3.days } let(:range) { 3.days }
it 'returns expected counts for occurrences' do it 'returns expected counts for occurrences' do
first, second = subject first, second, third = subject
expect(first.day).to eq(date_2) expect(first.day).to eq(date_2)
expect(first.severity).to eq('low') expect(first.severity).to eq('low')
...@@ -153,6 +156,9 @@ describe Vulnerabilities::Occurrence do ...@@ -153,6 +156,9 @@ describe Vulnerabilities::Occurrence do
expect(second.day).to eq(date_2) expect(second.day).to eq(date_2)
expect(second.severity).to eq('medium') expect(second.severity).to eq('medium')
expect(second.count).to eq(1) expect(second.count).to eq(1)
expect(third.day).to eq(date_2)
expect(third.severity).to eq('critical')
expect(third.count).to eq(2)
end end
end end
...@@ -160,7 +166,7 @@ describe Vulnerabilities::Occurrence do ...@@ -160,7 +166,7 @@ describe Vulnerabilities::Occurrence do
let(:range) { 4.days } let(:range) { 4.days }
it 'returns expected counts for occurrences' do it 'returns expected counts for occurrences' do
first, second, third = subject first, second, third, forth = subject
expect(first.day).to eq(date_1) expect(first.day).to eq(date_1)
expect(first.severity).to eq('high') expect(first.severity).to eq('high')
...@@ -171,12 +177,16 @@ describe Vulnerabilities::Occurrence do ...@@ -171,12 +177,16 @@ describe Vulnerabilities::Occurrence do
expect(third.day).to eq(date_2) expect(third.day).to eq(date_2)
expect(third.severity).to eq('medium') expect(third.severity).to eq('medium')
expect(third.count).to eq(1) expect(third.count).to eq(1)
expect(forth.day).to eq(date_2)
expect(forth.severity).to eq('critical')
expect(forth.count).to eq(2)
end end
end end
end end
describe '.by_report_types' do describe '.by_report_types' do
let!(:vulnerability_sast) { create(:vulnerabilities_occurrence, report_type: :sast) } let!(:vulnerability_sast) { create(:vulnerabilities_occurrence, report_type: :sast) }
let!(:vulnerability_secret_detection) { create(:vulnerabilities_occurrence, report_type: :secret_detection) }
let!(:vulnerability_dast) { create(:vulnerabilities_occurrence, report_type: :dast) } let!(:vulnerability_dast) { create(:vulnerabilities_occurrence, report_type: :dast) }
let!(:vulnerability_depscan) { create(:vulnerabilities_occurrence, report_type: :dependency_scanning) } let!(:vulnerability_depscan) { create(:vulnerabilities_occurrence, report_type: :dependency_scanning) }
...@@ -191,10 +201,10 @@ describe Vulnerabilities::Occurrence do ...@@ -191,10 +201,10 @@ describe Vulnerabilities::Occurrence do
end end
context 'with array of params' do context 'with array of params' do
let(:param) { [1, 3] } let(:param) { [1, 3, 4] }
it 'returns found records' do it 'returns found records' do
is_expected.to contain_exactly(vulnerability_dast, vulnerability_depscan) is_expected.to contain_exactly(vulnerability_dast, vulnerability_depscan, vulnerability_secret_detection)
end end
end end
......
...@@ -13,7 +13,8 @@ describe Vulnerability do ...@@ -13,7 +13,8 @@ describe Vulnerability do
{ sast: 0, { sast: 0,
dependency_scanning: 1, dependency_scanning: 1,
container_scanning: 2, container_scanning: 2,
dast: 3 } dast: 3,
secret_detection: 4 }
end end
it { is_expected.to define_enum_for(:state).with_values(state_values) } it { is_expected.to define_enum_for(:state).with_values(state_values) }
......
...@@ -42,7 +42,8 @@ describe Projects::Security::ConfigurationPresenter do ...@@ -42,7 +42,8 @@ describe Projects::Security::ConfigurationPresenter do
security_scan(:sast, configured: true), security_scan(:sast, configured: true),
security_scan(:container_scanning, configured: true), security_scan(:container_scanning, configured: true),
security_scan(:dependency_scanning, configured: true), security_scan(:dependency_scanning, configured: true),
security_scan(:license_scanning, configured: true) security_scan(:license_scanning, configured: true),
security_scan(:secret_detection, configured: true)
) )
end end
end end
...@@ -62,7 +63,8 @@ describe Projects::Security::ConfigurationPresenter do ...@@ -62,7 +63,8 @@ describe Projects::Security::ConfigurationPresenter do
security_scan(:sast, configured: false), security_scan(:sast, configured: false),
security_scan(:container_scanning, configured: false), security_scan(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false), security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false) security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: false)
) )
end end
end end
...@@ -80,6 +82,7 @@ describe Projects::Security::ConfigurationPresenter do ...@@ -80,6 +82,7 @@ describe Projects::Security::ConfigurationPresenter do
before do before do
create(:ci_build, :sast, pipeline: pipeline) create(:ci_build, :sast, pipeline: pipeline)
create(:ci_build, :dast, pipeline: pipeline) create(:ci_build, :dast, pipeline: pipeline)
create(:ci_build, :secret_detection, pipeline: pipeline)
end end
it 'uses the latest default branch pipeline to determine whether a security job is configured' do it 'uses the latest default branch pipeline to determine whether a security job is configured' do
...@@ -88,7 +91,8 @@ describe Projects::Security::ConfigurationPresenter do ...@@ -88,7 +91,8 @@ describe Projects::Security::ConfigurationPresenter do
security_scan(:sast, configured: true), security_scan(:sast, configured: true),
security_scan(:container_scanning, configured: false), security_scan(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false), security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false) security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: true)
) )
end end
...@@ -102,7 +106,8 @@ describe Projects::Security::ConfigurationPresenter do ...@@ -102,7 +106,8 @@ describe Projects::Security::ConfigurationPresenter do
security_scan(:sast, configured: true), security_scan(:sast, configured: true),
security_scan(:container_scanning, configured: false), security_scan(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false), security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false) security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: false)
) )
end end
...@@ -122,7 +127,8 @@ describe Projects::Security::ConfigurationPresenter do ...@@ -122,7 +127,8 @@ describe Projects::Security::ConfigurationPresenter do
security_scan(:sast, configured: true), security_scan(:sast, configured: true),
security_scan(:container_scanning, configured: false), security_scan(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false), security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false) security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: false)
) )
end end
...@@ -134,7 +140,8 @@ describe Projects::Security::ConfigurationPresenter do ...@@ -134,7 +140,8 @@ describe Projects::Security::ConfigurationPresenter do
security_scan(:sast, configured: true), security_scan(:sast, configured: true),
security_scan(:container_scanning, configured: false), security_scan(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false), security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: true) security_scan(:license_scanning, configured: true),
security_scan(:secret_detection, configured: true)
) )
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Ci::CompareSecretDetectionReportsService do
let(:current_user) { build(:user, :admin) }
let(:service) { described_class.new(project, current_user) }
let(:project) { build(:project, :repository) }
before do
stub_licensed_features(container_scanning: true)
stub_licensed_features(secret_detection: true)
end
describe '#execute' do
subject { service.execute(base_pipeline, head_pipeline) }
context 'when head pipeline has secret_detection reports' do
let!(:base_pipeline) { create(:ee_ci_pipeline) }
let!(:head_pipeline) { create(:ee_ci_pipeline, :with_secret_detection_report, project: project) }
it 'reports new vulnerabilities' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]['added'].count).to eq(1)
expect(subject[:data]['existing'].count).to eq(0)
expect(subject[:data]['fixed'].count).to eq(0)
end
end
context 'when base and head pipelines have secret_detection reports' do
let!(:base_pipeline) { create(:ee_ci_pipeline, :with_secret_detection_report, project: project) }
let!(:head_pipeline) { create(:ee_ci_pipeline, :with_secret_detection_feature_branch, project: project) }
it 'reports status as parsed' do
expect(subject[:status]).to eq(:parsed)
end
it 'populates fields based on current_user' do
payload = subject[:data]['existing'].first
expect(payload).to be_nil
expect(service.current_user).to eq(current_user)
end
it 'reports new vulnerability' do
expect(subject[:data]['added'].count).to eq(0)
end
it 'reports existing secret_detection vulnerabilities' do
expect(subject[:data]['existing'].count).to eq(0)
end
it 'reports fixed secret_detection vulnerabilities' do
expect(subject[:data]['fixed'].count).to eq(1)
compare_keys = subject[:data]['fixed'].map { |t| t['identifiers'].first['external_id'] }
expected_keys = %w(AWS)
expect(compare_keys - expected_keys).to eq([])
end
end
end
end
...@@ -2413,6 +2413,9 @@ msgstr "" ...@@ -2413,6 +2413,9 @@ msgstr ""
msgid "Analyze your dependencies for known vulnerabilities." msgid "Analyze your dependencies for known vulnerabilities."
msgstr "" msgstr ""
msgid "Analyze your source code and git history for secrets"
msgstr ""
msgid "Analyze your source code for known vulnerabilities." msgid "Analyze your source code for known vulnerabilities."
msgstr "" msgstr ""
...@@ -19071,6 +19074,9 @@ msgstr "" ...@@ -19071,6 +19074,9 @@ msgstr ""
msgid "Secret" msgid "Secret"
msgstr "" msgstr ""
msgid "Secret Detection"
msgstr ""
msgid "Security" msgid "Security"
msgstr "" msgstr ""
......
...@@ -400,6 +400,14 @@ FactoryBot.define do ...@@ -400,6 +400,14 @@ FactoryBot.define do
end end
end end
trait :secret_detection do
options do
{
artifacts: { reports: { secret_detection: 'gl-secret-detection-report.json' } }
}
end
end
trait :dependency_scanning do trait :dependency_scanning do
options do options do
{ {
......
...@@ -30,7 +30,7 @@ describe Ci::RetryBuildService do ...@@ -30,7 +30,7 @@ describe Ci::RetryBuildService do
created_at updated_at started_at finished_at queued_at erased_by created_at updated_at started_at finished_at queued_at erased_by
erased_at auto_canceled_by job_artifacts job_artifacts_archive erased_at auto_canceled_by job_artifacts job_artifacts_archive
job_artifacts_metadata job_artifacts_trace job_artifacts_junit job_artifacts_metadata job_artifacts_trace job_artifacts_junit
job_artifacts_sast job_artifacts_dependency_scanning job_artifacts_sast job_artifacts_secret_detection job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast job_artifacts_container_scanning job_artifacts_dast
job_artifacts_license_management job_artifacts_license_scanning job_artifacts_license_management job_artifacts_license_scanning
job_artifacts_performance job_artifacts_lsif job_artifacts_performance job_artifacts_lsif
......
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