Commit 0100e720 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '342135-move-presenters' into 'master'

Move Security ConfigurationPresenter to FOSS

See merge request gitlab-org/gitlab!75472
parents 491c2376 8df132a7
...@@ -17,14 +17,14 @@ module Projects ...@@ -17,14 +17,14 @@ module Projects
features: features, features: features,
help_page_path: help_page_path('user/application_security/index'), help_page_path: help_page_path('user/application_security/index'),
latest_pipeline_path: latest_pipeline_path, latest_pipeline_path: latest_pipeline_path,
auto_fix_enabled: autofix_enabled,
can_toggle_auto_fix_settings: auto_fix_permission,
# TODO: gitlab_ci_present will incorrectly report `false` if the CI/CD configuration file name # TODO: gitlab_ci_present will incorrectly report `false` if the CI/CD configuration file name
# has been customized and a file with the given custom name exists in the repo. This edge case # has been customized and a file with the given custom name exists in the repo. This edge case
# will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/342465 # will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/342465
gitlab_ci_present: project.repository.gitlab_ci_yml.present?, gitlab_ci_present: project.repository.gitlab_ci_yml.present?,
gitlab_ci_history_path: gitlab_ci_history_path, gitlab_ci_history_path: gitlab_ci_history_path,
auto_fix_user_path: '/' # TODO: real link will be updated with https://gitlab.com/gitlab-org/gitlab/-/issues/215669 auto_fix_enabled: autofix_enabled,
can_toggle_auto_fix_settings: can_toggle_autofix,
auto_fix_user_path: auto_fix_user_path
} }
end end
...@@ -38,12 +38,9 @@ module Projects ...@@ -38,12 +38,9 @@ module Projects
private private
def autofix_enabled def autofix_enabled; end
{
dependency_scanning: project_settings&.auto_fix_dependency_scanning, def auto_fix_user_path; end
container_scanning: project_settings&.auto_fix_container_scanning
}
end
def can_enable_auto_devops? def can_enable_auto_devops?
feature_available?(:builds, current_user) && feature_available?(:builds, current_user) &&
...@@ -51,11 +48,13 @@ module Projects ...@@ -51,11 +48,13 @@ module Projects
!archived? !archived?
end end
def can_toggle_autofix; end
def gitlab_ci_history_path def gitlab_ci_history_path
return '' if project.empty_repo? return '' if project.empty_repo?
gitlab_ci = Gitlab::FileDetector::PATTERNS[:gitlab_ci] gitlab_ci = ::Gitlab::FileDetector::PATTERNS[:gitlab_ci]
Gitlab::Routing.url_helpers.project_blame_path(project, File.join(project.default_branch_or_main, gitlab_ci)) ::Gitlab::Routing.url_helpers.project_blame_path(project, File.join(project.default_branch_or_main, gitlab_ci))
end end
def features def features
...@@ -75,11 +74,13 @@ module Projects ...@@ -75,11 +74,13 @@ module Projects
end end
def scan(type, configured: false) def scan(type, configured: false)
scan = ::Gitlab::Security::ScanConfiguration.new(project: project, type: type, configured: configured)
{ {
type: type, type: scan.type,
configured: configured, configured: scan.configured?,
configuration_path: configuration_path(type), configuration_path: scan.configuration_path,
available: feature_available(type) available: scan.available?
} }
end end
...@@ -90,23 +91,8 @@ module Projects ...@@ -90,23 +91,8 @@ module Projects
def project_settings def project_settings
project.security_setting project.security_setting
end end
def configuration_path(type)
{
sast: project_security_configuration_sast_path(project),
dast: project_security_configuration_dast_path(project),
dast_profiles: project_security_configuration_dast_scans_path(project),
api_fuzzing: project_security_configuration_api_fuzzing_path(project),
corpus_management: (project_security_configuration_corpus_management_path(project) if ::Feature.enabled?(:corpus_management, project, default_enabled: :yaml) && scanner_enabled?(:coverage_fuzzing))
}[type]
end
def feature_available(type)
# SAST and Secret Detection are always available, but this isn't
# reflected by our license model yet.
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/333113
%w[sast secret_detection].include?(type) || project.licensed_feature_available?(type)
end
end end
end end
end end
Projects::Security::ConfigurationPresenter.prepend_mod_with('Projects::Security::ConfigurationPresenter')
# frozen_string_literal: true
module EE
module Projects
module Security
module ConfigurationPresenter
extend ::Gitlab::Utils::Override
private
override :can_toggle_autofix
def can_toggle_autofix
try(:auto_fix_permission)
end
override :autofix_enabled
def autofix_enabled
{
dependency_scanning: project_settings&.auto_fix_dependency_scanning,
container_scanning: project_settings&.auto_fix_container_scanning
}
end
override :auto_fix_user_path
def auto_fix_user_path
'/' # TODO: real link will be updated with https://gitlab.com/gitlab-org/gitlab/-/issues/348463
end
end
end
end
end
# frozen_string_literal: true
module EE
module Gitlab
module Security
module ScanConfiguration
extend ::Gitlab::Utils::Override
override :available?
def available?
super || project.licensed_feature_available?(type)
end
override :configuration_path
def configuration_path
super if available? || always_available?
end
private
override :configurable_scans
def configurable_scans
strong_memoize(:configurable_scans) do
{
dast: project_security_configuration_dast_path(project),
dast_profiles: project_security_configuration_dast_scans_path(project),
api_fuzzing: project_security_configuration_api_fuzzing_path(project),
corpus_management: (project_security_configuration_corpus_management_path(project) if ::Feature.enabled?(:corpus_management, project, default_enabled: :yaml))
}.merge(super)
end
end
def always_available?
[:corpus_management, :dast_profiles].include?(type)
end
end
end
end
end
...@@ -39,7 +39,7 @@ module Gitlab ...@@ -39,7 +39,7 @@ module Gitlab
# The record hasn't been loaded yet, so # The record hasn't been loaded yet, so
# hit the database with all pending IDs to prevent N+1 # hit the database with all pending IDs to prevent N+1
profiles_by_project_id = @lazy_state[:dast_pending_profiles].group_by(&:project_id) profiles_by_project_id = @lazy_state[:dast_pending_profiles].group_by(&:project_id)
policy_configurations = Security::OrchestrationPolicyConfiguration.for_project(profiles_by_project_id.keys).index_by(&:project_id) policy_configurations = ::Security::OrchestrationPolicyConfiguration.for_project(profiles_by_project_id.keys).index_by(&:project_id)
profiles_by_project_id.each do |project_id, dast_pending_profiles| profiles_by_project_id.each do |project_id, dast_pending_profiles|
dast_pending_profiles.each do |profile| dast_pending_profiles.each do |profile|
......
...@@ -8,7 +8,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -8,7 +8,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
before_all do before_all do
project.add_developer(user) project.add_maintainer(user)
end end
before do before do
...@@ -17,12 +17,14 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -17,12 +17,14 @@ RSpec.describe 'User sees Security Configuration table', :js do
context 'with security_dashboard feature available' do context 'with security_dashboard feature available' do
before do before do
stub_licensed_features(security_dashboard: true, sast: true, sast_iac: true, dast: true) stub_licensed_features(security_dashboard: true, sast: true, sast_iac: true, dast: true,
dependency_scanning: true, container_scanning: true, coverage_fuzzing: true,
cluster_image_scanning: true, api_fuzzing: true)
end end
context 'with no SAST report' do context 'with no SAST report' do
it 'shows SAST is not enabled' do it 'shows SAST is not enabled' do
visit(project_security_configuration_path(project)) visit_configuration_page
within_sast_card do within_sast_card do
expect(page).to have_text('SAST') expect(page).to have_text('SAST')
...@@ -38,7 +40,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -38,7 +40,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
end end
it 'shows SAST is enabled' do it 'shows SAST is enabled' do
visit(project_security_configuration_path(project)) visit_configuration_page
within_sast_card do within_sast_card do
expect(page).to have_text('SAST') expect(page).to have_text('SAST')
...@@ -50,7 +52,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -50,7 +52,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
context 'enabling SAST IaC' do context 'enabling SAST IaC' do
it 'redirects to new MR page' do it 'redirects to new MR page' do
visit(project_security_configuration_path(project)) visit_configuration_page
within_sast_iac_card do within_sast_iac_card do
expect(page).to have_text('Infrastructure as Code (IaC) Scanning') expect(page).to have_text('Infrastructure as Code (IaC) Scanning')
...@@ -67,12 +69,13 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -67,12 +69,13 @@ RSpec.describe 'User sees Security Configuration table', :js do
context 'with no DAST report' do context 'with no DAST report' do
it 'shows DAST is not enabled' do it 'shows DAST is not enabled' do
visit(project_security_configuration_path(project)) visit_configuration_page
within_dast_card do within_dast_card do
expect(page).to have_text('DAST') expect(page).to have_text('DAST')
expect(page).to have_text('Not enabled') expect(page).to have_text('Not enabled')
expect(page).to have_link('Enable DAST') expect(page).to have_link('Enable DAST')
expect(page).to have_link('Manage scans')
end end
end end
end end
...@@ -83,15 +86,108 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -83,15 +86,108 @@ RSpec.describe 'User sees Security Configuration table', :js do
end end
it 'shows DAST is enabled' do it 'shows DAST is enabled' do
visit(project_security_configuration_path(project)) visit_configuration_page
within_dast_card do within_dast_card do
expect(page).to have_text('DAST') expect(page).to have_text('DAST')
expect(page).to have_text('Enabled') expect(page).to have_text('Enabled')
expect(page).to have_link('Configure DAST') expect(page).to have_link('Configure DAST')
expect(page).to have_link('Manage scans')
end end
end end
end end
context 'with no Dependency Scanning report' do
it 'shows Dependency Scanning is disabled' do
visit_configuration_page
within_dependency_scanning_card do
expect(page).to have_text('Dependency Scanning')
expect(page).to have_text('Not enabled')
expect(page).to have_button('Configure with a merge request')
end
end
end
context 'with Dependency Scanning report' do
before do
create(:ci_build, :dependency_scanning, pipeline: pipeline, status: 'success')
end
it 'shows Dependency Scanning is enabled' do
visit_configuration_page
within_dependency_scanning_card do
expect(page).to have_text('Dependency Scanning')
expect(page).to have_text('Enabled')
expect(page).to have_link('Configuration guide')
end
end
end
context 'with no Container Scanning report' do
it 'shows Container Scanning is disabled' do
visit_configuration_page
within_container_scanning_card do
expect(page).to have_text('Container Scanning')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Configuration guide')
end
end
end
context 'with no Cluster Image scanning report' do
it 'shows Cluster Image scanning is disabled' do
visit_configuration_page
within_cluster_image_card do
expect(page).to have_text('Cluster Image Scanning')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Configuration guide')
end
end
end
context 'with no Secret Detection report' do
it 'shows Secret Detection is disabled' do
visit_configuration_page
within_secret_detection_card do
expect(page).to have_text('Secret Detection')
expect(page).to have_text('Not enabled')
expect(page).to have_button('Configure with a merge request')
end
end
end
context 'with no API Fuzzing report' do
it 'shows API Fuzzing is disabled' do
visit_configuration_page
within_api_fuzzing_card do
expect(page).to have_text('API Fuzzing')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Enable API Fuzzing')
end
end
end
context 'with no Coverage Fuzzing' do
it 'shows Coverage Fuzzing is disabled' do
visit_configuration_page
within_coverage_fuzzing_card do
expect(page).to have_text('Coverage Fuzzing')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Configuration guide')
end
end
end
end
def visit_configuration_page
visit(project_security_configuration_path(project))
end end
def within_sast_card def within_sast_card
...@@ -111,4 +207,40 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -111,4 +207,40 @@ RSpec.describe 'User sees Security Configuration table', :js do
yield yield
end end
end end
def within_dependency_scanning_card
within '[data-testid="security-testing-card"]:nth-of-type(4)' do
yield
end
end
def within_container_scanning_card
within '[data-testid="security-testing-card"]:nth-of-type(5)' do
yield
end
end
def within_cluster_image_card
within '[data-testid="security-testing-card"]:nth-of-type(6)' do
yield
end
end
def within_secret_detection_card
within '[data-testid="security-testing-card"]:nth-of-type(7)' do
yield
end
end
def within_api_fuzzing_card
within '[data-testid="security-testing-card"]:nth-of-type(8)' do
yield
end
end
def within_coverage_fuzzing_card
within '[data-testid="security-testing-card"]:nth-of-type(9)' do
yield
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Gitlab::Security::ScanConfiguration do
let_it_be(:project) { create(:project, :repository) }
let(:scan) { described_class.new(project: project, type: type, configured: configured) }
describe '#available?' do
subject { scan.available? }
let(:configured) { true }
context 'with a core scanner' do
let(:type) { :sast }
before do
stub_licensed_features(sast: false)
end
it 'core scanners (SAST, Secret Detection) are always available' do
is_expected.to be_truthy
end
end
context 'with licensed scanner that is available' do
let(:type) { :api_fuzzing }
before do
stub_licensed_features(api_fuzzing: true)
end
it { is_expected.to be_truthy }
end
context 'with licensed scanner that is not available' do
let(:type) { :api_fuzzing }
before do
stub_licensed_features(api_fuzzing: false)
end
it { is_expected.to be_falsey }
end
context 'with custom scanner' do
let(:type) { :my_scanner }
it { is_expected.to be_falsey }
end
end
describe '#configuration_path' do
subject { scan.configuration_path }
let(:configured) { true }
context 'with licensed scanner' do
let(:type) { :dast }
let(:configuration_path) { "/#{project.namespace.path}/#{project.name}/-/security/configuration/dast" }
before do
stub_licensed_features(dast: true)
end
it { is_expected.to eq(configuration_path) }
end
context 'with always available scanner' do
let(:type) { :dast_profiles }
let(:configuration_path) { "/#{project.namespace.path}/#{project.name}/-/security/configuration/dast_scans" }
it { is_expected.to eq(configuration_path) }
end
context 'with a scanner under feature flag' do
let(:type) { :corpus_management }
let(:configuration_path) { "/#{project.namespace.path}/#{project.name}/-/security/configuration/corpus_management" }
it { is_expected.to eq(configuration_path) }
context 'when feature flag is disabled' do
before do
stub_feature_flags(corpus_management: false)
end
it { is_expected.to be_nil }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationPresenter do
include Gitlab::Routing.url_helpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:current_user) { create(:user) }
describe '#to_h' do
subject(:result) { described_class.new(project, auto_fix_permission: true, current_user: current_user).to_h }
it 'includes settings for auto_fix feature' do
auto_fix = result[:auto_fix_enabled]
expect(auto_fix[:dependency_scanning]).to be_truthy
expect(auto_fix[:container_scanning]).to be_truthy
end
it 'reports auto_fix permissions' do
expect(result[:can_toggle_auto_fix_settings]).to be_truthy
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationPresenter do
include Gitlab::Routing.url_helpers
let(:project) { create(:project, :repository) }
let(:project_with_no_repo) { create(:project) }
let(:current_user) { create(:user) }
it 'presents the given project' do
presenter = described_class.new(project)
expect(presenter.id).to be(project.id)
end
before do
project.add_maintainer(current_user)
stub_licensed_features(licensed_scan_types.to_h { |type| [type, true] })
end
describe '#to_h' do
subject { described_class.new(project, auto_fix_permission: true, current_user: current_user).to_html_data_attribute }
it 'includes links to auto devops and secure product docs' do
expect(subject[:auto_devops_help_page_path]).to eq(help_page_path('topics/autodevops/index'))
expect(subject[:help_page_path]).to eq(help_page_path('user/application_security/index'))
end
it 'includes settings for auto_fix feature' do
auto_fix = Gitlab::Json.parse(subject[:auto_fix_enabled])
expect(auto_fix['dependency_scanning']).to be_truthy
expect(auto_fix['container_scanning']).to be_truthy
end
it 'includes the path to gitlab_ci history' do
expect(subject[:gitlab_ci_history_path]).to eq(project_blame_path(project, 'master/.gitlab-ci.yml'))
end
context 'when the project is empty' do
subject { described_class.new(project_with_no_repo, auto_fix_permission: true, current_user: current_user).to_html_data_attribute }
it 'includes a blank gitlab_ci history path' do
expect(subject[:gitlab_ci_history_path]).to eq('')
end
end
context 'when the project has no default branch set' do
before do
allow(project).to receive(:default_branch).and_return(nil)
end
it 'includes the path to gitlab_ci history' do
expect(subject[:gitlab_ci_history_path]).to eq(project_blame_path(project, 'master/.gitlab-ci.yml'))
end
end
context "when the latest default branch pipeline's source is auto devops" do
before do
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
expect(subject[:auto_devops_enabled]).to be_truthy
end
it 'reports auto_fix permissions' do
expect(subject[:can_toggle_auto_fix_settings]).to be_truthy
end
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(:sast_iac, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:cluster_image_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: true),
security_scan(:coverage_fuzzing, configured: false),
security_scan(:api_fuzzing, configured: false),
security_scan(:dast_profiles, configured: true),
security_scan(:corpus_management, configured: true)
)
end
end
context "when coverage fuzzing has run in a pipeline with feature flag off" do
before do
stub_feature_flags(corpus_management: false)
pipeline = create(
:ci_pipeline,
:auto_devops_source,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
create(:ci_build, :coverage_fuzzing, pipeline: pipeline, status: 'success')
end
it 'reports that coverage fuzzing, corpus management, and DAST are configured' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false),
security_scan(:sast, configured: false),
security_scan(:sast_iac, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:cluster_image_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: false),
security_scan(:coverage_fuzzing, configured: true),
security_scan(:api_fuzzing, configured: false),
security_scan(:dast_profiles, configured: true),
security_scan(:corpus_management, configured: true)
)
end
end
context "when coverage fuzzing has run in a pipeline with feature flag on" do
before do
stub_feature_flags(corpus_management: true)
pipeline = create(
:ci_pipeline,
:auto_devops_source,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
create(:ci_build, :coverage_fuzzing, pipeline: pipeline, status: 'success')
end
it 'reports that coverage fuzzing, corpus management, and DAST are configured' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false),
security_scan(:sast, configured: false),
security_scan(:sast_iac, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:cluster_image_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: false),
security_scan(:coverage_fuzzing, configured: true),
security_scan(:api_fuzzing, configured: false),
security_scan(:dast_profiles, configured: true),
security_scan(:corpus_management, configured: true, configuration_path: project_security_configuration_corpus_management_path(project))
)
end
end
context 'when the project has no default branch pipeline' do
it 'reports that auto devops is disabled' do
expect(subject[:auto_devops_enabled]).to be_falsy
end
it 'includes a link to CI pipeline docs' do
expect(subject[:latest_pipeline_path]).to eq(help_page_path('ci/pipelines'))
end
it 'reports all security jobs as unconfigured' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false),
security_scan(:sast, configured: false),
security_scan(:sast_iac, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:cluster_image_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: false),
security_scan(:coverage_fuzzing, configured: false),
security_scan(:api_fuzzing, configured: false),
security_scan(:dast_profiles, configured: true),
security_scan(:corpus_management, configured: true)
)
end
end
context 'when latest default branch pipeline`s source is not auto devops' do
let(:pipeline) do
create(
:ci_pipeline,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
end
before do
create(:ci_build, :sast, pipeline: pipeline)
create(:ci_build, :dast, pipeline: pipeline)
create(:ci_build, :secret_detection, pipeline: pipeline)
end
it 'uses the latest default branch pipeline to determine whether a security job is configured' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: true),
security_scan(:dast_profiles, configured: true),
security_scan(:sast, configured: true),
security_scan(:sast_iac, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:cluster_image_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: true),
security_scan(:coverage_fuzzing, configured: false),
security_scan(:api_fuzzing, configured: false),
security_scan(:corpus_management, configured: true)
)
end
it 'detects security jobs even when the job has more than one report' do
config = { artifacts: { reports: { other_job: ['gl-other-report.json'], sast: ['gl-sast-report.json'] } } }
complicated_job = build_stubbed(:ci_build, options: config)
allow_next_instance_of(::Security::SecurityJobsFinder) do |finder|
allow(finder).to receive(:execute).and_return([complicated_job])
end
subject
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false),
security_scan(:dast_profiles, configured: true),
security_scan(:sast, configured: true),
security_scan(:sast_iac, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:cluster_image_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: false),
security_scan(:secret_detection, configured: false),
security_scan(:coverage_fuzzing, configured: false),
security_scan(:api_fuzzing, configured: false),
security_scan(:corpus_management, configured: true)
)
end
it 'detect new license compliance job' do
create(:ci_build, :license_scanning, pipeline: pipeline)
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: true),
security_scan(:dast_profiles, configured: true),
security_scan(:sast, configured: true),
security_scan(:sast_iac, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:cluster_image_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
security_scan(:license_scanning, configured: true),
security_scan(:secret_detection, configured: true),
security_scan(:coverage_fuzzing, configured: false),
security_scan(:api_fuzzing, configured: false),
security_scan(:corpus_management, configured: true)
)
end
it 'includes a link to the latest pipeline' do
expect(subject[:latest_pipeline_path]).to eq(project_pipeline_path(project, pipeline))
end
context "while retrieving information about gitlab ci file" do
context 'when a .gitlab-ci.yml file exists' do
before do
project.repository.create_file(
project.creator,
Gitlab::FileDetector::PATTERNS[:gitlab_ci],
'contents go here',
message: 'test',
branch_name: 'master')
end
it 'expects gitlab_ci_present to be true' do
expect(subject[:gitlab_ci_present]).to eq(true)
end
end
context 'when a .gitlab-ci.yml file does not exist' do
it 'expects gitlab_ci_present to be false if the file is not present' do
expect(subject[:gitlab_ci_present]).to eq(false)
end
end
end
it 'includes the auto_devops_path' do
expect(subject[:auto_devops_path]).to eq(project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
context "while retrieving information about user's ability to enable auto_devops" do
using RSpec::Parameterized::TableSyntax
where(:is_admin, :archived, :feature_available, :result) do
true | true | true | false
false | true | true | false
true | false | true | true
false | false | true | false
true | true | false | false
false | true | false | false
true | false | false | false
false | false | false | false
end
with_them do
before do
allow_any_instance_of(described_class).to receive(:can?).and_return(is_admin)
allow_any_instance_of(described_class).to receive(:archived?).and_return(archived)
allow_any_instance_of(described_class).to receive(:feature_available?).and_return(feature_available)
end
it 'includes can_enable_auto_devops' do
expect(subject[:can_enable_auto_devops]).to eq(result)
end
end
end
end
end
def security_scan(type, configured:, configuration_path: nil)
path = configuration_path || configuration_path(type)
{
"type" => type.to_s,
"configured" => configured,
"configuration_path" => path,
"available" => licensed_scan_types.include?(type)
}
end
def configuration_path(type)
{
dast: project_security_configuration_dast_path(project),
dast_profiles: project_security_configuration_dast_scans_path(project),
sast: project_security_configuration_sast_path(project),
api_fuzzing: project_security_configuration_api_fuzzing_path(project),
corpus_management: nil
}[type]
end
def licensed_scan_types
::Security::SecurityJobsFinder.allowed_job_types + ::Security::LicenseComplianceJobsFinder.allowed_job_types - [:cluster_image_scanning]
end
end
# frozen_string_literal: true
module Gitlab
module Security
class ScanConfiguration
include ::Gitlab::Utils::StrongMemoize
include Gitlab::Routing.url_helpers
attr_reader :type
def initialize(project:, type:, configured: false)
@project = project
@type = type
@configured = configured
end
def available?
# SAST and Secret Detection are always available, but this isn't
# reflected by our license model yet.
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/333113
%i[sast secret_detection].include?(type)
end
def configured?
configured
end
def configuration_path
configurable_scans[type]
end
private
attr_reader :project, :configured
def configurable_scans
strong_memoize(:configurable_scans) do
{
sast: project_security_configuration_sast_path(project)
}
end
end
end
end
end
Gitlab::Security::ScanConfiguration.prepend_mod_with('Gitlab::Security::ScanConfiguration')
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Gitlab::Security::ScanConfiguration do
let_it_be(:project) { create(:project, :repository) }
let(:scan) { described_class.new(project: project, type: type, configured: configured) }
describe '#available?' do
subject { scan.available? }
let(:configured) { true }
context 'with a core scanner' do
let(:type) { :sast }
it { is_expected.to be_truthy }
end
context 'with custom scanner' do
let(:type) { :my_scanner }
it { is_expected.to be_falsey }
end
end
describe '#configured?' do
subject { scan.configured? }
let(:type) { :sast }
let(:configured) { false }
it { is_expected.to be_falsey }
end
describe '#configuration_path' do
subject { scan.configuration_path }
let(:configured) { true }
context 'with a non configurable scanner' do
let(:type) { :secret_detection }
it { is_expected.to be_nil }
end
context 'with licensed scanner for FOSS environment' do
let(:type) { :dast }
before do
stub_env('FOSS_ONLY', '1')
end
it { is_expected.to be_nil }
end
context 'with custom scanner' do
let(:type) { :my_scanner }
it { is_expected.to be_nil }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationPresenter do
include Gitlab::Routing.url_helpers
using RSpec::Parameterized::TableSyntax
let(:project_with_repo) { create(:project, :repository) }
let(:project_with_no_repo) { create(:project) }
let(:current_user) { create(:user) }
let(:presenter) { described_class.new(project, current_user: current_user) }
before do
stub_licensed_features(licensed_scan_types.to_h { |type| [type, true] })
stub_feature_flags(corpus_management: false)
end
describe '#to_html_data_attribute' do
subject(:html_data) { presenter.to_html_data_attribute }
context 'when latest default branch pipeline`s source is not auto devops' do
let(:project) { project_with_repo }
let(:pipeline) do
create(
:ci_pipeline,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
end
let!(:build_sast) { create(:ci_build, :sast, pipeline: pipeline) }
let!(:build_dast) { create(:ci_build, :dast, pipeline: pipeline) }
let!(:build_license_scanning) { create(:ci_build, :license_scanning, pipeline: pipeline) }
it 'includes links to auto devops and secure product docs' do
expect(html_data[:auto_devops_help_page_path]).to eq(help_page_path('topics/autodevops/index'))
expect(html_data[:help_page_path]).to eq(help_page_path('user/application_security/index'))
end
it 'returns info that Auto DevOps is not enabled' do
expect(html_data[:auto_devops_enabled]).to eq(false)
expect(html_data[:auto_devops_path]).to eq(project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
it 'includes a link to the latest pipeline' do
expect(html_data[:latest_pipeline_path]).to eq(project_pipeline_path(project, pipeline))
end
it 'has stubs for autofix' do
expect(html_data.keys).to include(:can_toggle_auto_fix_settings, :auto_fix_enabled, :auto_fix_user_path)
end
context "while retrieving information about user's ability to enable auto_devops" do
where(:is_admin, :archived, :feature_available, :result) do
true | true | true | false
false | true | true | false
true | false | true | true
false | false | true | false
true | true | false | false
false | true | false | false
true | false | false | false
false | false | false | false
end
with_them do
before do
allow_next_instance_of(described_class) do |presenter|
allow(presenter).to receive(:can?).and_return(is_admin)
allow(presenter).to receive(:archived?).and_return(archived)
allow(presenter).to receive(:feature_available?).and_return(feature_available)
end
end
it 'includes can_enable_auto_devops' do
expect(html_data[:can_enable_auto_devops]).to eq(result)
end
end
end
it 'includes feature information' do
feature = Gitlab::Json.parse(html_data[:features]).find { |scan| scan['type'] == 'sast' }
expect(feature['type']).to eq('sast')
expect(feature['configured']).to eq(true)
expect(feature['configuration_path']).to eq(project_security_configuration_sast_path(project))
expect(feature['available']).to eq(true)
end
context 'when checking features configured status' do
let(:features) { Gitlab::Json.parse(html_data[:features]) }
where(:type, :configured) do
:dast | true
:dast_profiles | true
:sast | true
:sast_iac | false
:container_scanning | false
:cluster_image_scanning | false
:dependency_scanning | false
:license_scanning | true
:secret_detection | false
:coverage_fuzzing | false
:api_fuzzing | false
:corpus_management | true
end
with_them do
it 'returns proper configuration status' do
feature = features.find { |scan| scan['type'] == type.to_s }
expect(feature['configured']).to eq(configured)
end
end
end
context 'when the job has more than one report' do
let(:features) { Gitlab::Json.parse(html_data[:features]) }
let!(:artifacts) do
{ artifacts: { reports: { other_job: ['gl-other-report.json'], sast: ['gl-sast-report.json'] } } }
end
let!(:complicated_job) { build_stubbed(:ci_build, options: artifacts) }
before do
allow_next_instance_of(::Security::SecurityJobsFinder) do |finder|
allow(finder).to receive(:execute).and_return([complicated_job])
end
end
where(:type, :configured) do
:dast | false
:dast_profiles | true
:sast | true
:sast_iac | false
:container_scanning | false
:cluster_image_scanning | false
:dependency_scanning | false
:license_scanning | true
:secret_detection | false
:coverage_fuzzing | false
:api_fuzzing | false
:corpus_management | true
end
with_them do
it 'properly detects security jobs' do
feature = features.find { |scan| scan['type'] == type.to_s }
expect(feature['configured']).to eq(configured)
end
end
end
it 'includes a link to the latest pipeline' do
expect(subject[:latest_pipeline_path]).to eq(project_pipeline_path(project, pipeline))
end
context "while retrieving information about gitlab ci file" do
context 'when a .gitlab-ci.yml file exists' do
let!(:ci_config) do
project.repository.create_file(
project.creator,
Gitlab::FileDetector::PATTERNS[:gitlab_ci],
'contents go here',
message: 'test',
branch_name: 'master')
end
it 'expects gitlab_ci_present to be true' do
expect(html_data[:gitlab_ci_present]).to eq(true)
end
end
context 'when a .gitlab-ci.yml file does not exist' do
it 'expects gitlab_ci_present to be false if the file is not present' do
expect(html_data[:gitlab_ci_present]).to eq(false)
end
end
end
it 'includes the path to gitlab_ci history' do
expect(subject[:gitlab_ci_history_path]).to eq(project_blame_path(project, 'master/.gitlab-ci.yml'))
end
end
context 'when the project is empty' do
let(:project) { project_with_no_repo }
it 'includes a blank gitlab_ci history path' do
expect(html_data[:gitlab_ci_history_path]).to eq('')
end
end
context 'when the project has no default branch set' do
let(:project) { project_with_repo }
it 'includes the path to gitlab_ci history' do
allow(project).to receive(:default_branch).and_return(nil)
expect(html_data[:gitlab_ci_history_path]).to eq(project_blame_path(project, 'master/.gitlab-ci.yml'))
end
end
context "when the latest default branch pipeline's source is auto devops" do
let(:project) { project_with_repo }
let(:pipeline) do
create(
:ci_pipeline,
:auto_devops_source,
project: project,
ref: project.default_branch,
sha: project.commit.sha
)
end
let!(:build_sast) { create(:ci_build, :sast, pipeline: pipeline, status: 'success') }
let!(:build_dast) { create(:ci_build, :dast, pipeline: pipeline, status: 'success') }
let!(:ci_build) { create(:ci_build, :secret_detection, pipeline: pipeline, status: 'pending') }
it 'reports that auto devops is enabled' do
expect(html_data[:auto_devops_enabled]).to be_truthy
end
context 'when gathering feature data' do
let(:features) { Gitlab::Json.parse(html_data[:features]) }
where(:type, :configured) do
:dast | true
:dast_profiles | true
:sast | true
:sast_iac | false
:container_scanning | false
:cluster_image_scanning | false
:dependency_scanning | false
:license_scanning | false
:secret_detection | true
:coverage_fuzzing | false
:api_fuzzing | false
:corpus_management | true
end
with_them do
it 'reports that all scanners are configured for which latest pipeline has builds' do
feature = features.find { |scan| scan['type'] == type.to_s }
expect(feature['configured']).to eq(configured)
end
end
end
end
context 'when the project has no default branch pipeline' do
let(:project) { project_with_repo }
it 'reports that auto devops is disabled' do
expect(html_data[:auto_devops_enabled]).to be_falsy
end
it 'includes a link to CI pipeline docs' do
expect(html_data[:latest_pipeline_path]).to eq(help_page_path('ci/pipelines'))
end
context 'when gathering feature data' do
let(:features) { Gitlab::Json.parse(html_data[:features]) }
where(:type, :configured) do
:dast | false
:dast_profiles | true
:sast | false
:sast_iac | false
:container_scanning | false
:cluster_image_scanning | false
:dependency_scanning | false
:license_scanning | false
:secret_detection | false
:coverage_fuzzing | false
:api_fuzzing | false
:corpus_management | true
end
with_them do
it 'reports all security jobs as unconfigured with exception of "fake" jobs' do
feature = features.find { |scan| scan['type'] == type.to_s }
expect(feature['configured']).to eq(configured)
end
end
end
end
def licensed_scan_types
::Security::SecurityJobsFinder.allowed_job_types + ::Security::LicenseComplianceJobsFinder.allowed_job_types - [:cluster_image_scanning]
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