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 {
thClass,
},
{
key: 'configured',
key: 'status',
label: s__('SecurityConfiguration|Status'),
thClass,
formatter: this.getStatusText,
},
{
key: 'manage',
......@@ -115,15 +114,6 @@ export default {
},
},
methods: {
getStatusText(value) {
if (value) {
return this.autoDevopsEnabled
? s__('SecurityConfiguration|Enabled with Auto DevOps')
: s__('SecurityConfiguration|Enabled');
}
return s__('SecurityConfiguration|Not enabled');
},
dismissAutoDevopsAlert() {
this.autoDevopsAlertDismissed = 'true';
},
......
......@@ -33,6 +33,9 @@ export default {
canCreateSASTMergeRequest() {
return Boolean(this.feature.type === 'sast' && this.createSastMergeRequestPath);
},
canManageProfiles() {
return this.feature.type === 'dast_profiles';
},
getFeatureDocumentationLinkLabel() {
return sprintf(s__('SecurityConfiguration|Feature documentation for %{featureName}'), {
featureName: this.feature.name,
......@@ -44,7 +47,16 @@ export default {
<template>
<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"
data-testid="configureButton"
>{{ s__('SecurityConfiguration|Configure') }}</gl-button
......
......@@ -12,6 +12,7 @@ module Projects
SCAN_DOCS = {
container_scanning: 'user/application_security/container_scanning/index',
dast: 'user/application_security/dast/index',
dast_profiles: 'user/application_security/dast/index',
dependency_scanning: 'user/application_security/dependency_scanning/index',
license_management: 'user/compliance/license_compliance/index',
license_scanning: 'user/compliance/license_compliance/index',
......@@ -24,6 +25,7 @@ module Projects
{
container_scanning: _('Check your Docker images for known vulnerabilities.'),
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.'),
license_management: _('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
{
container_scanning: _('Container Scanning'),
dast: _('Dynamic Application Security Testing (DAST)'),
dast_profiles: _('DAST Profiles'),
dependency_scanning: _('Dependency Scanning'),
license_management: 'License Management',
license_scanning: _('License Compliance'),
......@@ -93,14 +96,16 @@ module Projects
def features
scans = scan_types.map do |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
scan(scan_type, configured: false)
scan(scan_type, configured: false, status: s_('SecurityConfiguration|Not enabled'))
end
end
# TODO: remove this line with #8912
license_compliance_substitute(scans)
dast_profiles_insert(scans)
end
def latest_pipeline_path
......@@ -122,16 +127,29 @@ module Projects
if license_compliance_config
scans.map do |scan_type|
scan_type[:configured] = true if scan_type[:name] == _('License Compliance')
scan_type[:status] = s_('SecurityConfiguration|Enabled') if scan_type[:name] == _('License Compliance')
end
end
scans
end
def scan(type, configured: false)
# 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
scans
end
def scan(type, configured: false, status:)
{
type: type,
configured: configured,
status: status,
description: self.class.localized_scan_descriptions[type],
link: help_page_path(SCAN_DOCS[type]),
configuration_path: configuration_path(type),
......@@ -153,7 +171,8 @@ module Projects
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]
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
it 'responds in json format when requested' do
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(json_response['features'].map { |f| f['type'] }).to match_array(types)
......
......@@ -184,7 +184,10 @@ describe('Security Configuration App', () => {
describe('given a feature enabled by Auto DevOps', () => {
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 } });
......
......@@ -6,6 +6,7 @@ export const generateFeatures = (n, overrides = {}) => {
link: `link-feature-${i}`,
configuration_path: i % 2 ? `configuration_path-${i}` : null,
configured: i % 2 === 0,
status: i % 2 === 0 ? 'Enabled' : 'Not enabled',
...overrides,
}));
};
......@@ -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"', () => {
beforeEach(() => {
[feature] = generateFeatures(1, { type: 'something_that_is_not_sast' });
......
......@@ -61,13 +61,14 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
it 'reports that all scanners are configured for which latest pipeline has builds' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: true),
security_scan(:sast, configured: true),
security_scan(:container_scanning, configured: 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(:dast, configured: true, auto_dev_ops_enabled: true),
security_scan(:dast_profiles, configured: true, auto_dev_ops_enabled: true),
security_scan(:sast, configured: true, auto_dev_ops_enabled: true),
security_scan(:container_scanning, configured: false, auto_dev_ops_enabled: true),
security_scan(:dependency_scanning, configured: false, auto_dev_ops_enabled: true),
security_scan(:license_scanning, configured: false, auto_dev_ops_enabled: true),
security_scan(:secret_detection, configured: true, auto_dev_ops_enabled: true),
security_scan(:coverage_fuzzing, configured: false, auto_dev_ops_enabled: true)
)
end
end
......@@ -84,6 +85,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
it 'reports all security jobs as unconfigured' do
expect(Gitlab::Json.parse(subject[:features])).to contain_exactly(
security_scan(:dast, configured: false),
security_scan(:dast_profiles, configured: true),
security_scan(:sast, configured: false),
security_scan(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
......@@ -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
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(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
......@@ -129,6 +132,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
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(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
......@@ -151,6 +155,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
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(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
......@@ -165,6 +170,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter 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(:container_scanning, configured: false),
security_scan(:dependency_scanning, configured: false),
......@@ -222,16 +228,41 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
end
end
def security_scan(type, configured:)
configuration_path = project_security_configuration_sast_path(project) if type == :sast
def security_scan(type, configured:, auto_dev_ops_enabled: false)
configuration_path = configuration_path(type)
status_str = scan_status(type, configured, auto_dev_ops_enabled)
{
"type" => type.to_s,
"configured" => configured,
"status" => status_str,
"description" => described_class.localized_scan_descriptions[type],
"link" => help_page_path(described_class::SCAN_DOCS[type]),
"configuration_path" => configuration_path,
"name" => described_class.localized_scan_names[type]
}
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
......@@ -7707,6 +7707,9 @@ msgstr ""
msgid "DAG visualization requires at least 3 dependent jobs."
msgstr ""
msgid "DAST Profiles"
msgstr ""
msgid "DNS"
msgstr ""
......@@ -21487,6 +21490,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
msgid "Saved scan settings and target site settings which are reusable."
msgstr ""
msgid "Saving"
msgstr ""
......@@ -21822,6 +21828,9 @@ msgstr ""
msgid "SecurityConfiguration|An error occurred while creating the merge request."
msgstr ""
msgid "SecurityConfiguration|Available for on-demand DAST"
msgstr ""
msgid "SecurityConfiguration|Configure"
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