Commit 861ca1df authored by Neil McCorrison's avatar Neil McCorrison Committed by Mark Florian

Add DAST Profiles to Security Configuration page

Addresses https://gitlab.com/gitlab-org/gitlab/-/issues/241263.
parent 270576b8
...@@ -93,10 +93,9 @@ export default { ...@@ -93,10 +93,9 @@ export default {
thClass, thClass,
}, },
{ {
key: 'configured', key: 'status',
label: s__('SecurityConfiguration|Status'), label: s__('SecurityConfiguration|Status'),
thClass, thClass,
formatter: this.getStatusText,
}, },
{ {
key: 'manage', key: 'manage',
...@@ -115,15 +114,6 @@ export default { ...@@ -115,15 +114,6 @@ export default {
}, },
}, },
methods: { methods: {
getStatusText(value) {
if (value) {
return this.autoDevopsEnabled
? s__('SecurityConfiguration|Enabled with Auto DevOps')
: s__('SecurityConfiguration|Enabled');
}
return s__('SecurityConfiguration|Not enabled');
},
dismissAutoDevopsAlert() { dismissAutoDevopsAlert() {
this.autoDevopsAlertDismissed = 'true'; this.autoDevopsAlertDismissed = 'true';
}, },
......
...@@ -33,6 +33,9 @@ export default { ...@@ -33,6 +33,9 @@ export default {
canCreateSASTMergeRequest() { canCreateSASTMergeRequest() {
return Boolean(this.feature.type === 'sast' && this.createSastMergeRequestPath); return Boolean(this.feature.type === 'sast' && this.createSastMergeRequestPath);
}, },
canManageProfiles() {
return this.feature.type === 'dast_profiles';
},
getFeatureDocumentationLinkLabel() { getFeatureDocumentationLinkLabel() {
return sprintf(s__('SecurityConfiguration|Feature documentation for %{featureName}'), { return sprintf(s__('SecurityConfiguration|Feature documentation for %{featureName}'), {
featureName: this.feature.name, featureName: this.feature.name,
...@@ -44,7 +47,16 @@ export default { ...@@ -44,7 +47,16 @@ export default {
<template> <template>
<gl-button <gl-button
v-if="canConfigureFeature && feature.configured" v-if="canManageProfiles"
variant="success"
category="primary"
:href="feature.configuration_path"
data-testid="manageButton"
>{{ s__('SecurityConfiguration|Manage') }}</gl-button
>
<gl-button
v-else-if="canConfigureFeature && feature.configured"
:href="feature.configuration_path" :href="feature.configuration_path"
data-testid="configureButton" data-testid="configureButton"
>{{ s__('SecurityConfiguration|Configure') }}</gl-button >{{ s__('SecurityConfiguration|Configure') }}</gl-button
......
...@@ -12,6 +12,7 @@ module Projects ...@@ -12,6 +12,7 @@ module Projects
SCAN_DOCS = { SCAN_DOCS = {
container_scanning: 'user/application_security/container_scanning/index', container_scanning: 'user/application_security/container_scanning/index',
dast: 'user/application_security/dast/index', dast: 'user/application_security/dast/index',
dast_profiles: 'user/application_security/dast/index',
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',
...@@ -24,6 +25,7 @@ module Projects ...@@ -24,6 +25,7 @@ module Projects
{ {
container_scanning: _('Check your Docker images for known vulnerabilities.'), container_scanning: _('Check your Docker images for known vulnerabilities.'),
dast: _('Analyze a review version of your web application.'), dast: _('Analyze a review version of your web application.'),
dast_profiles: _('Saved scan settings and target site settings which are reusable.'),
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.'),
...@@ -37,6 +39,7 @@ module Projects ...@@ -37,6 +39,7 @@ module Projects
{ {
container_scanning: _('Container Scanning'), container_scanning: _('Container Scanning'),
dast: _('Dynamic Application Security Testing (DAST)'), dast: _('Dynamic Application Security Testing (DAST)'),
dast_profiles: _('DAST Profiles'),
dependency_scanning: _('Dependency Scanning'), dependency_scanning: _('Dependency Scanning'),
license_management: 'License Management', license_management: 'License Management',
license_scanning: _('License Compliance'), license_scanning: _('License Compliance'),
...@@ -93,14 +96,16 @@ module Projects ...@@ -93,14 +96,16 @@ module Projects
def features def features
scans = scan_types.map do |scan_type| scans = scan_types.map do |scan_type|
if scanner_enabled?(scan_type) if scanner_enabled?(scan_type)
scan(scan_type, configured: true) scan(scan_type, configured: true, status: auto_devops_source? ? s_('SecurityConfiguration|Enabled with Auto DevOps') : s_('SecurityConfiguration|Enabled'))
else else
scan(scan_type, configured: false) scan(scan_type, configured: false, status: s_('SecurityConfiguration|Not enabled'))
end end
end end
# TODO: remove this line with #8912 # TODO: remove this line with #8912
license_compliance_substitute(scans) license_compliance_substitute(scans)
dast_profiles_insert(scans)
end end
def latest_pipeline_path def latest_pipeline_path
...@@ -122,16 +127,29 @@ module Projects ...@@ -122,16 +127,29 @@ module Projects
if license_compliance_config if license_compliance_config
scans.map do |scan_type| scans.map do |scan_type|
scan_type[:configured] = true if scan_type[:name] == _('License Compliance') scan_type[:configured] = true if scan_type[:name] == _('License Compliance')
scan_type[:status] = s_('SecurityConfiguration|Enabled') if scan_type[:name] == _('License Compliance')
end
end end
scans
end
# DAST On-demand scans is a static (non job) entry. Add it manually following DAST
def dast_profiles_insert(scans)
index = scans.index { |scan| scan[:name] == localized_scan_names[:dast] }
unless index.nil?
scans.insert(index + 1, scan(:dast_profiles, configured: true, status: s_('SecurityConfiguration|Available for on-demand DAST')))
end end
scans scans
end end
def scan(type, configured: false) def scan(type, configured: false, status:)
{ {
type: type, type: type,
configured: configured, configured: configured,
status: status,
description: self.class.localized_scan_descriptions[type], description: self.class.localized_scan_descriptions[type],
link: help_page_path(SCAN_DOCS[type]), link: help_page_path(SCAN_DOCS[type]),
configuration_path: configuration_path(type), configuration_path: configuration_path(type),
...@@ -153,7 +171,8 @@ module Projects ...@@ -153,7 +171,8 @@ module Projects
def configuration_path(type) def configuration_path(type)
{ {
sast: project_security_configuration_sast_path(project) sast: project_security_configuration_sast_path(project),
dast_profiles: project_profiles_path(project)
}[type] }[type]
end end
end end
......
---
title: On-demand scans item on Security & Compliance configuration page
merge_request: 40474
author:
type: added
...@@ -29,7 +29,7 @@ RSpec.describe Projects::Security::ConfigurationController do ...@@ -29,7 +29,7 @@ RSpec.describe Projects::Security::ConfigurationController do
it 'responds in json format when requested' do it 'responds in json format when requested' do
get :show, params: { namespace_id: project.namespace, project_id: project, format: :json } get :show, params: { namespace_id: project.namespace, project_id: project, format: :json }
types = %w(sast dast dependency_scanning container_scanning secret_detection coverage_fuzzing license_scanning) types = %w(sast dast dast_profiles dependency_scanning container_scanning secret_detection coverage_fuzzing license_scanning)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['features'].map { |f| f['type'] }).to match_array(types) expect(json_response['features'].map { |f| f['type'] }).to match_array(types)
......
...@@ -184,7 +184,10 @@ describe('Security Configuration App', () => { ...@@ -184,7 +184,10 @@ describe('Security Configuration App', () => {
describe('given a feature enabled by Auto DevOps', () => { describe('given a feature enabled by Auto DevOps', () => {
it('displays the expected status text', () => { it('displays the expected status text', () => {
const features = generateFeatures(1, { configured: true }); const features = generateFeatures(1, {
configured: true,
status: 'Enabled with Auto DevOps',
});
createComponent({ propsData: { features, autoDevopsEnabled: true } }); createComponent({ propsData: { features, autoDevopsEnabled: true } });
......
...@@ -6,6 +6,7 @@ export const generateFeatures = (n, overrides = {}) => { ...@@ -6,6 +6,7 @@ export const generateFeatures = (n, overrides = {}) => {
link: `link-feature-${i}`, link: `link-feature-${i}`,
configuration_path: i % 2 ? `configuration_path-${i}` : null, configuration_path: i % 2 ? `configuration_path-${i}` : null,
configured: i % 2 === 0, configured: i % 2 === 0,
status: i % 2 === 0 ? 'Enabled' : 'Not enabled',
...overrides, ...overrides,
})); }));
}; };
...@@ -86,6 +86,22 @@ describe('ManageFeature component', () => { ...@@ -86,6 +86,22 @@ describe('ManageFeature component', () => {
}); });
}); });
describe('given a feature with type "dast-profiles"', () => {
beforeEach(() => {
[feature] = generateFeatures(1, { type: 'dast_profiles', configuration_path: 'foo' });
createComponent({
propsData: { feature, autoDevopsEnabled: true },
});
});
it('shows the DAST Profiles manage button', () => {
const button = findTestId('manageButton');
expect(button.exists()).toBe(true);
expect(button.attributes('href')).toBe(feature.configuration_path);
});
});
describe('given a feature type that is not "sast"', () => { describe('given a feature type that is not "sast"', () => {
beforeEach(() => { beforeEach(() => {
[feature] = generateFeatures(1, { type: 'something_that_is_not_sast' }); [feature] = generateFeatures(1, { type: 'something_that_is_not_sast' });
......
...@@ -61,13 +61,14 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -61,13 +61,14 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
it 'reports that all scanners are configured for which latest pipeline has builds' do it 'reports that all scanners are configured for which latest pipeline has builds' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly( expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: true), security_scan(:dast, configured: true, auto_dev_ops_enabled: true),
security_scan(:sast, configured: true), security_scan(:dast_profiles, configured: true, auto_dev_ops_enabled: true),
security_scan(:container_scanning, configured: false), security_scan(:sast, configured: true, auto_dev_ops_enabled: true),
security_scan(:dependency_scanning, configured: false), security_scan(:container_scanning, configured: false, auto_dev_ops_enabled: true),
security_scan(:license_scanning, configured: false), security_scan(:dependency_scanning, configured: false, auto_dev_ops_enabled: true),
security_scan(:secret_detection, configured: true), security_scan(:license_scanning, configured: false, auto_dev_ops_enabled: true),
security_scan(:coverage_fuzzing, configured: false) security_scan(:secret_detection, configured: true, auto_dev_ops_enabled: true),
security_scan(:coverage_fuzzing, configured: false, auto_dev_ops_enabled: true)
) )
end end
end end
...@@ -84,6 +85,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -84,6 +85,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
it 'reports all security jobs as unconfigured' do it 'reports all security jobs as unconfigured' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly( expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false), security_scan(:dast, configured: false),
security_scan(:dast_profiles, configured: true),
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),
...@@ -113,6 +115,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -113,6 +115,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
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
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly( expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: true), security_scan(:dast, configured: true),
security_scan(:dast_profiles, configured: true),
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),
...@@ -129,6 +132,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -129,6 +132,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly( expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false), security_scan(:dast, configured: false),
security_scan(:dast_profiles, configured: true),
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),
...@@ -151,6 +155,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -151,6 +155,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly( expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false), security_scan(:dast, configured: false),
security_scan(:dast_profiles, configured: true),
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),
...@@ -165,6 +170,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -165,6 +170,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly( expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: true), security_scan(:dast, configured: true),
security_scan(:dast_profiles, configured: true),
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),
...@@ -222,16 +228,41 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -222,16 +228,41 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
end end
end end
def security_scan(type, configured:) def security_scan(type, configured:, auto_dev_ops_enabled: false)
configuration_path = project_security_configuration_sast_path(project) if type == :sast configuration_path = configuration_path(type)
status_str = scan_status(type, configured, auto_dev_ops_enabled)
{ {
"type" => type.to_s, "type" => type.to_s,
"configured" => configured, "configured" => configured,
"status" => status_str,
"description" => described_class.localized_scan_descriptions[type], "description" => described_class.localized_scan_descriptions[type],
"link" => help_page_path(described_class::SCAN_DOCS[type]), "link" => help_page_path(described_class::SCAN_DOCS[type]),
"configuration_path" => configuration_path, "configuration_path" => configuration_path,
"name" => described_class.localized_scan_names[type] "name" => described_class.localized_scan_names[type]
} }
end end
def configuration_path(type)
if type === :dast_profiles
project_profiles_path(project)
elsif type === :sast
project_security_configuration_sast_path(project)
else
nil
end
end
def scan_status(type, configured, auto_dev_ops_enabled)
if type == :dast_profiles
"Available for on-demand DAST"
elsif configured && auto_dev_ops_enabled
"Enabled with Auto DevOps"
elsif configured
"Enabled"
else
"Not enabled"
end
end
end end
...@@ -7707,6 +7707,9 @@ msgstr "" ...@@ -7707,6 +7707,9 @@ msgstr ""
msgid "DAG visualization requires at least 3 dependent jobs." msgid "DAG visualization requires at least 3 dependent jobs."
msgstr "" msgstr ""
msgid "DAST Profiles"
msgstr ""
msgid "DNS" msgid "DNS"
msgstr "" msgstr ""
...@@ -21487,6 +21490,9 @@ msgstr "" ...@@ -21487,6 +21490,9 @@ msgstr ""
msgid "Save variables" msgid "Save variables"
msgstr "" msgstr ""
msgid "Saved scan settings and target site settings which are reusable."
msgstr ""
msgid "Saving" msgid "Saving"
msgstr "" msgstr ""
...@@ -21822,6 +21828,9 @@ msgstr "" ...@@ -21822,6 +21828,9 @@ msgstr ""
msgid "SecurityConfiguration|An error occurred while creating the merge request." msgid "SecurityConfiguration|An error occurred while creating the merge request."
msgstr "" msgstr ""
msgid "SecurityConfiguration|Available for on-demand DAST"
msgstr ""
msgid "SecurityConfiguration|Configure" msgid "SecurityConfiguration|Configure"
msgstr "" msgstr ""
......
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