Commit 436fa8d2 authored by Ash McKenzie's avatar Ash McKenzie

Merge branch '227878-add-new-security-charts-page' into 'master'

Add new security charts page

See merge request gitlab-org/gitlab!38088
parents 4b662d49 3b51f3dd
...@@ -93,25 +93,14 @@ NOTE: **Note:** ...@@ -93,25 +93,14 @@ NOTE: **Note:**
The Security Dashboard only shows projects with [security reports](#supported-reports) enabled in a The Security Dashboard only shows projects with [security reports](#supported-reports) enabled in a
group. group.
![Dashboard with action buttons and metrics](img/group_security_dashboard_v13_2_noNav.png) ![Dashboard with action buttons and metrics](img/group_security_dashboard_v13_3.png)
You can filter which vulnerabilities the Security Dashboard displays by: There is a timeline chart that shows how many open
- Status
- Severity
- Scanner
- Project
A table lists the vulnerabilities, sorted by severity. The table shows each vulnerability's status,
severity, and description. Clicking a vulnerability takes you to its [Vulnerability Details](../vulnerabilities)
page to view more information about that vulnerability.
Next to the list is a timeline chart that shows how many open
vulnerabilities your projects had at various points in time. You can filter among 30, 60, and vulnerabilities your projects had at various points in time. You can filter among 30, 60, and
90 days, with the default being 90. Hover over the chart to get more details about 90 days, with the default being 90. Hover over the chart to get more details about
the open vulnerabilities at a specific time. the open vulnerabilities at a specific time.
Below the timeline chart is a list of projects, grouped and sorted by the severity of the vulnerability found: Next to the timeline chart is a list of projects, grouped and sorted by the severity of the vulnerability found:
- F: 1 or more "critical" - F: 1 or more "critical"
- D: 1 or more "high" or "unknown" - D: 1 or more "high" or "unknown"
...@@ -122,7 +111,7 @@ Below the timeline chart is a list of projects, grouped and sorted by the severi ...@@ -122,7 +111,7 @@ Below the timeline chart is a list of projects, grouped and sorted by the severi
Projects with no vulnerability tests configured will not appear in the list. Additionally, dismissed Projects with no vulnerability tests configured will not appear in the list. Additionally, dismissed
vulnerabilities are not included either. vulnerabilities are not included either.
Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities). Navigate to the group's [Vulnerability Report](#vulnerability-list) to view the vulnerabilities found.
## Instance Security Dashboard ## Instance Security Dashboard
...@@ -200,8 +189,19 @@ to configure daily security scans. ...@@ -200,8 +189,19 @@ to configure daily security scans.
Each dashboard's vulnerability list contains vulnerabilities from the latest scans that were merged Each dashboard's vulnerability list contains vulnerabilities from the latest scans that were merged
into the default branch. into the default branch.
Click any vulnerability in the table to see more information on that vulnerability. To create an
issue associated with the vulnerability, click the **Create Issue** button. ![Vulnerability Report](img/group_vulnerability_report_v13_3.png)
You can filter which vulnerabilities the Security Dashboard displays by:
- Status
- Severity
- Scanner
- Project
Clicking any vulnerability in the table takes you to its
[Vulnerability Details](../vulnerabilities) page to see more information on that vulnerability.
To create an issue associated with the vulnerability, click the **Create Issue** button.
![Create an issue for the vulnerability](img/standalone_vulnerability_page_v13_1.png) ![Create an issue for the vulnerability](img/standalone_vulnerability_page_v13_1.png)
...@@ -221,3 +221,5 @@ questions that you know someone might ask. ...@@ -221,3 +221,5 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`. Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. --> but commented out to help encourage others to add to it in the future. -->
Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
import initFirstClassSecurityDashboard from 'ee/security_dashboard/first_class_init'; import initSecurityCharts from 'ee/security_dashboard/security_charts_init';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants'; import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initFirstClassSecurityDashboard( initSecurityCharts(document.getElementById('js-group-security-dashboard'), DASHBOARD_TYPES.GROUP);
document.getElementById('js-group-security-dashboard'),
DASHBOARD_TYPES.GROUP,
);
}); });
import initFirstClassSecurityDashboard from 'ee/security_dashboard/first_class_init';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
document.addEventListener('DOMContentLoaded', () => {
initFirstClassSecurityDashboard(
document.getElementById('js-group-vulnerabilities'),
DASHBOARD_TYPES.GROUP,
);
});
...@@ -3,18 +3,13 @@ import { GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,18 +3,13 @@ import { GlLoadingIcon } from '@gitlab/ui';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import GroupSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import GroupSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import CsvExportButton from './csv_export_button.vue'; import CsvExportButton from './csv_export_button.vue';
import VulnerabilitySeverity from './vulnerability_severity.vue';
import vulnerabilityHistoryQuery from '../graphql/group_vulnerability_history.query.graphql';
import DashboardNotConfigured from './empty_states/group_dashboard_not_configured.vue'; import DashboardNotConfigured from './empty_states/group_dashboard_not_configured.vue';
export default { export default {
components: { components: {
SecurityDashboardLayout, SecurityDashboardLayout,
GroupSecurityVulnerabilities, GroupSecurityVulnerabilities,
VulnerabilitySeverity,
VulnerabilityChart,
Filters, Filters,
CsvExportButton, CsvExportButton,
DashboardNotConfigured, DashboardNotConfigured,
...@@ -25,10 +20,6 @@ export default { ...@@ -25,10 +20,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
vulnerableProjectsEndpoint: {
type: String,
required: true,
},
vulnerabilitiesExportEndpoint: { vulnerabilitiesExportEndpoint: {
type: String, type: String,
required: true, required: true,
...@@ -39,7 +30,6 @@ export default { ...@@ -39,7 +30,6 @@ export default {
filters: {}, filters: {},
projects: [], projects: [],
projectsWereFetched: false, projectsWereFetched: false,
vulnerabilityHistoryQuery,
}; };
}, },
computed: { computed: {
...@@ -67,7 +57,7 @@ export default { ...@@ -67,7 +57,7 @@ export default {
<template #header> <template #header>
<header class="page-title-holder flex-fill d-flex align-items-center"> <header class="page-title-holder flex-fill d-flex align-items-center">
<h2 class="page-title flex-grow"> <h2 class="page-title flex-grow">
{{ s__('SecurityReports|Group Security Dashboard') }} {{ s__('SecurityReports|Vulnerability Report') }}
</h2> </h2>
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" /> <csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
</header> </header>
...@@ -80,14 +70,6 @@ export default { ...@@ -80,14 +70,6 @@ export default {
:filters="filters" :filters="filters"
@projectFetch="handleProjectsFetch" @projectFetch="handleProjectsFetch"
/> />
<template #aside>
<vulnerability-chart
:query="vulnerabilityHistoryQuery"
:group-full-path="groupFullPath"
class="mb-4"
/>
<vulnerability-severity :endpoint="vulnerableProjectsEndpoint" />
</template>
</security-dashboard-layout> </security-dashboard-layout>
</div> </div>
</template> </template>
...@@ -10,7 +10,7 @@ export default { ...@@ -10,7 +10,7 @@ export default {
<template functional> <template functional>
<div> <div>
<h1>{{ $options.i18n.title }}</h1> <h2>{{ $options.i18n.title }}</h2>
<div class="security-charts gl-display-flex gl-flex-wrap"> <div class="security-charts gl-display-flex gl-flex-wrap">
<slot></slot> <slot></slot>
</div> </div>
......
...@@ -9,7 +9,6 @@ class Groups::Security::DashboardController < Groups::ApplicationController ...@@ -9,7 +9,6 @@ class Groups::Security::DashboardController < Groups::ApplicationController
private private
def dashboard_available? def dashboard_available?
group.feature_available?(:security_dashboard) && can?(current_user, :read_group_security_dashboard, group)
can?(current_user, :read_group_security_dashboard, group)
end end
end end
# frozen_string_literal: true
module Groups
module Security
class VulnerabilitiesController < Groups::ApplicationController
layout 'group'
def index
render :unavailable unless dashboard_available?
end
private
def dashboard_available?
can?(current_user, :read_group_security_dashboard, group)
end
end
end
end
...@@ -14,4 +14,12 @@ module SecurityHelper ...@@ -14,4 +14,12 @@ module SecurityHelper
vulnerabilities_export_endpoint: expose_path(api_v4_security_vulnerability_exports_path) vulnerabilities_export_endpoint: expose_path(api_v4_security_vulnerability_exports_path)
} }
end end
def security_dashboard_unavailable_view_data
{
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index'),
is_unavailable: "true"
}
end
end end
- breadcrumb_title _("Security Dashboard") - breadcrumb_title _("Security Dashboard")
- page_title _("Security Dashboard") - page_title _("Security Dashboard")
#js-group-security-dashboard{ data: { is_unavailable: "true", #js-group-security-dashboard{ data: security_dashboard_unavailable_view_data }
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index') } }
- breadcrumb_title _("Vulnerability Report")
- page_title _("Vulnerability Report")
#js-group-vulnerabilities{ data: group_level_security_dashboard_data(@group) }
- breadcrumb_title _("Vulnerability Report")
- page_title _("Vulnerability Report")
#js-group-vulnerabilities{ data: security_dashboard_unavailable_view_data }
- main_path = primary_group_level_security_feature_path(@group) - main_path = primary_group_level_security_feature_path(@group)
- if main_path.present? - if main_path.present?
= nav_link(path: %w[dashboard#show compliance_dashboards#show credentials#index]) do = nav_link(path: %w[dashboard#show vulnerabilities#index compliance_dashboards#show credentials#index]) do
= link_to main_path, data: { qa_selector: 'security_compliance_link' } do = link_to main_path, data: { qa_selector: 'security_compliance_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('shield') = sprite_icon('shield')
...@@ -9,8 +9,13 @@ ...@@ -9,8 +9,13 @@
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } } %ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } }
- if group_level_security_dashboard_available?(@group) - if group_level_security_dashboard_available?(@group)
= nav_link(path: 'dashboard#show') do = nav_link(path: 'dashboard#show') do
= link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do = link_to group_security_dashboard_path(@group), title: _('Security Dashboard'), data: { qa_selector: 'security_dashboard_link' } do
%span= _('Security') %span= _('Security Dashboard')
- if group_level_security_dashboard_available?(@group)
= nav_link(path: 'vulnerabilities#index') do
= link_to group_security_vulnerabilities_path(@group), title: _('Vulnerability Report') do
%span= _('Vulnerability Report')
- if group_level_compliance_dashboard_available?(@group) - if group_level_compliance_dashboard_available?(@group)
= nav_link(path: 'compliance_dashboards#show') do = nav_link(path: 'compliance_dashboards#show') do
......
---
title: Add new security charts page and unavailable view
merge_request: 38088
author:
type: changed
...@@ -147,6 +147,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -147,6 +147,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
namespace :security do namespace :security do
resource :dashboard, only: [:show], controller: :dashboard resource :dashboard, only: [:show], controller: :dashboard
resources :vulnerabilities, only: [:index]
resource :compliance_dashboard, only: [:show] resource :compliance_dashboard, only: [:show]
resources :vulnerable_projects, only: [:index] resources :vulnerable_projects, only: [:index]
resource :discover, only: [:show], controller: :discover resource :discover, only: [:show], controller: :discover
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::Security::VulnerabilitiesController do
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
sign_in(user)
end
describe 'GET index' do
subject { get :index, params: { group_id: group.to_param } }
context 'when security dashboard feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
context 'and user is allowed to access group security vulnerabilities' do
before do
group.add_developer(user)
end
it { is_expected.to have_gitlab_http_status(:ok) }
end
context 'when user is not allowed to access group security vulnerabilities' do
it { is_expected.to have_gitlab_http_status(:ok) }
it { is_expected.to render_template(:unavailable) }
end
end
context 'when security dashboard feature is disabled' do
it { is_expected.to have_gitlab_http_status(:ok) }
it { is_expected.to render_template(:unavailable) }
end
end
end
...@@ -158,7 +158,8 @@ RSpec.describe 'Group navbar' do ...@@ -158,7 +158,8 @@ RSpec.describe 'Group navbar' do
new_nav_item: { new_nav_item: {
nav_item: _('Security & Compliance'), nav_item: _('Security & Compliance'),
nav_sub_items: [ nav_sub_items: [
_('Security'), _('Security Dashboard'),
_('Vulnerability Report'),
_('Compliance') _('Compliance')
] ]
} }
......
...@@ -3,8 +3,6 @@ import { GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,8 +3,6 @@ import { GlLoadingIcon } from '@gitlab/ui';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import FirstClassGroupDashboard from 'ee/security_dashboard/components/first_class_group_security_dashboard.vue'; import FirstClassGroupDashboard from 'ee/security_dashboard/components/first_class_group_security_dashboard.vue';
import FirstClassGroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import FirstClassGroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue';
import VulnerabilitySeverity from 'ee/security_dashboard/components/vulnerability_severity.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.vue'; import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.vue';
import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue'; import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
...@@ -20,8 +18,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -20,8 +18,6 @@ describe('First Class Group Dashboard Component', () => {
const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout); const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout);
const findGroupVulnerabilities = () => wrapper.find(FirstClassGroupVulnerabilities); const findGroupVulnerabilities = () => wrapper.find(FirstClassGroupVulnerabilities);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findCsvExportButton = () => wrapper.find(CsvExportButton); const findCsvExportButton = () => wrapper.find(CsvExportButton);
const findFilters = () => wrapper.find(Filters); const findFilters = () => wrapper.find(Filters);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
...@@ -83,10 +79,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -83,10 +79,6 @@ describe('First Class Group Dashboard Component', () => {
expect(findFilters().exists()).toBe(true); expect(findFilters().exists()).toBe(true);
}); });
it('has the vulnerability history chart', () => {
expect(findVulnerabilityChart().props('groupFullPath')).toBe(groupFullPath);
});
it('responds to the projectFetch event', () => { it('responds to the projectFetch event', () => {
const projects = [{ id: 1, name: 'GitLab Org' }]; const projects = [{ id: 1, name: 'GitLab Org' }];
findGroupVulnerabilities().vm.$listeners.projectFetch(projects); findGroupVulnerabilities().vm.$listeners.projectFetch(projects);
...@@ -104,10 +96,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -104,10 +96,6 @@ describe('First Class Group Dashboard Component', () => {
}); });
}); });
it('displays the vulnerability severity in an aside', () => {
expect(findVulnerabilitySeverity().exists()).toBe(true);
});
it('displays the csv export button', () => { it('displays the csv export button', () => {
expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toBe( expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toBe(
vulnerabilitiesExportEndpoint, vulnerabilitiesExportEndpoint,
......
...@@ -25,6 +25,10 @@ RSpec.describe 'Group routing', "routing" do ...@@ -25,6 +25,10 @@ RSpec.describe 'Group routing', "routing" do
it 'shows group dashboard' do it 'shows group dashboard' do
expect(get('/groups/gitlabhq/-/security/dashboard')).to route_to('groups/security/dashboard#show', group_id: 'gitlabhq') expect(get('/groups/gitlabhq/-/security/dashboard')).to route_to('groups/security/dashboard#show', group_id: 'gitlabhq')
end end
it 'shows vulnerability list' do
expect(get('/groups/gitlabhq/-/security/vulnerabilities')).to route_to('groups/security/vulnerabilities#index', group_id: 'gitlabhq')
end
end end
describe 'dependency proxy for containers' do describe 'dependency proxy for containers' do
......
...@@ -21507,9 +21507,6 @@ msgstr "" ...@@ -21507,9 +21507,6 @@ msgstr ""
msgid "SecurityReports|Fuzzing artifacts" msgid "SecurityReports|Fuzzing artifacts"
msgstr "" msgstr ""
msgid "SecurityReports|Group Security Dashboard"
msgstr ""
msgid "SecurityReports|Hide dismissed" msgid "SecurityReports|Hide dismissed"
msgstr "" msgstr ""
...@@ -21636,6 +21633,9 @@ msgstr "" ...@@ -21636,6 +21633,9 @@ msgstr ""
msgid "SecurityReports|Undo dismiss" msgid "SecurityReports|Undo dismiss"
msgstr "" msgstr ""
msgid "SecurityReports|Vulnerability Report"
msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully." msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
msgstr "" msgstr ""
...@@ -27025,6 +27025,9 @@ msgstr "" ...@@ -27025,6 +27025,9 @@ msgstr ""
msgid "Vulnerabilities over time" msgid "Vulnerabilities over time"
msgstr "" msgstr ""
msgid "Vulnerability Report"
msgstr ""
msgid "Vulnerability remediated. Review before resolving." msgid "Vulnerability remediated. Review before resolving."
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