Commit 8253ffdd authored by Savas Vedova's avatar Savas Vedova

Add new page for policy selection

Create a new page for selecting policy and improve the policy selection
worfklow. This feature is still behind a feature flag.
parent 34531f83
---
name: container_security_policy_selection
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80272
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353071
milestone: '14.10'
type: development
group: group::container security
default_enabled: false
<script>
import { GlPath } from '@gitlab/ui';
import { s__ } from '~/locale';
import { getParameterByName, removeParams, visitUrl } from '~/lib/utils/url_utility';
import { POLICY_TYPE_COMPONENT_OPTIONS } from '../constants';
import PolicySelection from './policy_selection.vue';
import PolicyEditor from './policy_editor.vue';
export default {
components: {
GlPath,
PolicyEditor,
PolicySelection,
},
// TODO: move this `inject` instead of `props`. We're using it in multiple levels.
props: {
assignedPolicyProject: {
type: Object,
required: true,
},
existingPolicy: {
type: Object,
required: false,
default: null,
},
},
data() {
return {
selectedPolicy: this.policyFromUrl(),
};
},
computed: {
glPathItems() {
const hasPolicy = Boolean(this.selectedPolicy);
return [
{
title: this.$options.i18n.choosePolicyType,
selected: !hasPolicy,
},
{
title: this.$options.i18n.policyDetails,
selected: hasPolicy,
disabled: !hasPolicy,
},
];
},
title() {
if (this.existingPolicy) {
return this.$options.i18n.editTitles[this.selectedPolicy.value];
}
if (this.selectedPolicy) {
return this.$options.i18n.titles[this.selectedPolicy.value];
}
return this.$options.i18n.titles.default;
},
},
methods: {
handlePathSelection({ title }) {
if (title === this.$options.i18n.choosePolicyType) {
visitUrl(removeParams(['type'], window.location.href));
}
},
policyFromUrl() {
const policyType = getParameterByName('type');
return Object.values(POLICY_TYPE_COMPONENT_OPTIONS).find(
({ urlParameter }) => urlParameter === policyType,
);
},
},
i18n: {
titles: {
[POLICY_TYPE_COMPONENT_OPTIONS.container.value]: s__(
'SecurityOrchestration|New network policy',
),
[POLICY_TYPE_COMPONENT_OPTIONS.scanResult.value]: s__(
'SecurityOrchestration|New scan result policy',
),
[POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.value]: s__(
'SecurityOrchestration|New scan exection policy',
),
default: s__('SecurityOrchestration|New policy'),
},
editTitles: {
[POLICY_TYPE_COMPONENT_OPTIONS.container.value]: s__(
'SecurityOrchestration|Edit network policy',
),
[POLICY_TYPE_COMPONENT_OPTIONS.scanResult.value]: s__(
'SecurityOrchestration|Edit scan result policy',
),
[POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.value]: s__(
'SecurityOrchestration|Edit scan exection policy',
),
default: s__('SecurityOrchestration|New policy'),
},
choosePolicyType: s__('SecurityOrchestration|Step 1: Choose a policy type'),
policyDetails: s__('SecurityOrchestration|Step 2: Policy details'),
},
};
</script>
<template>
<div>
<header class="gl-pb-5 gl-border-b-none">
<h3>{{ title }}</h3>
<gl-path v-if="!existingPolicy" :items="glPathItems" @selected="handlePathSelection" />
</header>
<policy-selection v-if="!selectedPolicy" />
<policy-editor
v-else
:assigned-policy-project="assignedPolicyProject"
:existing-policy="existingPolicy"
:selected-policy-type="selectedPolicy.value"
/>
</div>
</template>
<script>
import networkIllustration from '@gitlab/svgs/dist/illustrations/network.svg';
import shieldCheckIllustration from '@gitlab/svgs/dist/illustrations/shield-check.svg';
import magnifyingGlassIllustration from '@gitlab/svgs/dist/illustrations/magnifying-glass.svg';
import { GlCard, GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { POLICY_TYPE_COMPONENT_OPTIONS } from '../constants';
const i18n = {
examples: __('Examples'),
selectPolicy: s__('SecurityOrchestration|Select policy'),
networkPolicyTitle: s__('SecurityOrchestration|Network policy'),
networkPolicyDesc: s__(
'SecurityOrchestration|Use network policies to create firewall rules for network connections in your Kubernetes cluster.',
),
networkPolicyExample: s__(
'SecurityOrchestration|Allow all inbound traffic to all pods from all pods on ports 443/TCP.',
),
scanResultPolicyTitle: s__('SecurityOrchestration|Scan result policy'),
scanResultPolicyDesc: s__(
'SecurityOrchestration|Use a scan result policy to create rules that ensure security issues are checked before merging a merge request.',
),
scanResultPolicyExample: s__(
'SecurityOrchestration|If any scanner finds a newly detected critical vulnerability in an open merge request targeting the master branch, then require two approvals from any member of App security.',
),
scanExecutionPolicyTitle: s__('SecurityOrchestration|Scan execution policy'),
scanExecutionPolicyDesc: s__(
'SecurityOrchestration|Scan execution policy allow to create rules which forces security scans for particular branches at certain time. Supported types are SAST, DAST, Secret detection, Container scan, License scan, API fuzzing, coverage-guided fuzzing.',
),
scanExecutionPolicyExample: s__(
'SecurityOrchestration|Run a DAST scan with Scan Profile A and Site Profile A when a pipeline run against the main branch.',
),
};
export default {
components: {
GlCard,
GlButton,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
},
methods: {
constructUrl(policyType) {
return mergeUrlParams({ type: policyType }, window.location.href);
},
},
policies: [
{
urlParameter: POLICY_TYPE_COMPONENT_OPTIONS.container.urlParameter,
title: i18n.networkPolicyTitle,
description: i18n.networkPolicyDesc,
example: i18n.networkPolicyExample,
svg: networkIllustration,
},
{
urlParameter: POLICY_TYPE_COMPONENT_OPTIONS.scanResult.urlParameter,
title: i18n.scanResultPolicyTitle,
description: i18n.scanResultPolicyDesc,
example: i18n.scanResultPolicyExample,
svg: shieldCheckIllustration,
},
{
urlParameter: POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.urlParameter,
title: i18n.scanExecutionPolicyTitle,
description: i18n.scanExecutionPolicyDesc,
example: i18n.scanExecutionPolicyExample,
svg: magnifyingGlassIllustration,
},
],
i18n,
safeHtmlConfig: { ADD_TAGS: ['use'] },
};
</script>
<template>
<div class="gl-display-grid gl-md-grid-template-columns-2 gl-gap-6">
<gl-card
v-for="option in $options.policies"
:key="option.title"
body-class="gl-p-6 gl-display-flex gl-flex-grow-1"
>
<div class="gl-mr-6 gl-text-white">
<div v-safe-html:[$options.safeHtmlConfig]="option.svg"></div>
</div>
<div class="gl-display-flex gl-flex-direction-column">
<h4 class="gl-mt-0">{{ option.title }}</h4>
<p>{{ option.description }}</p>
<h5>{{ $options.i18n.examples }}</h5>
<p class="gl-flex-grow-1">{{ option.example }}</p>
<div>
<gl-button
variant="confirm"
:href="constructUrl(option.urlParameter)"
:data-testid="`select-policy-${option.urlParameter}`"
>{{ $options.i18n.selectPolicy }}</gl-button
>
</div>
</div>
</gl-card>
</div>
</template>
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import PolicyEditorApp from './components/policy_editor/policy_editor.vue';
import NewPolicyApp from './components/policy_editor/new_policy.vue';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from './constants';
import createStore from './store';
import { gqClient, isValidEnvironmentId } from './utils';
......@@ -62,6 +63,12 @@ export default () => {
const scanResultPolicyApprovers = scanResultApprovers ? JSON.parse(scanResultApprovers) : [];
let component = PolicyEditorApp;
if (gon.features?.containerSecurityPolicySelection) {
component = NewPolicyApp;
}
return new Vue({
el,
apolloProvider,
......@@ -79,7 +86,7 @@ export default () => {
},
store,
render(createElement) {
return createElement(PolicyEditorApp, { props });
return createElement(component, { props });
},
});
};
......@@ -10,6 +10,7 @@ module Projects
before_action do
push_frontend_feature_flag(:scan_result_policy, project, default_enabled: :yaml)
push_frontend_feature_flag(:container_security_policy_selection, project)
end
feature_category :security_orchestration
......
import { GlPath } from '@gitlab/ui';
import * as urlUtils from '~/lib/utils/url_utility';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { POLICY_TYPE_COMPONENT_OPTIONS } from 'ee/threat_monitoring/components/constants';
import NewPolicy from 'ee/threat_monitoring/components/policy_editor/new_policy.vue';
import PolicySelection from 'ee/threat_monitoring/components/policy_editor/policy_selection.vue';
import PolicyEditor from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue';
describe('NewPolicy component', () => {
let wrapper;
const findPolicySelection = () => wrapper.findComponent(PolicySelection);
const findPolicyEditor = () => wrapper.findComponent(PolicyEditor);
const findPath = () => wrapper.findComponent(GlPath);
const factory = ({ propsData } = {}) => {
wrapper = shallowMountExtended(NewPolicy, {
propsData: {
assignedPolicyProject: {},
...propsData,
},
stubs: { GlPath: true },
});
};
afterEach(() => {
wrapper.destroy();
});
describe('when there is no type query parameter', () => {
beforeEach(() => {
factory();
});
it('should display the title correctly', () => {
expect(wrapper.findByText(NewPolicy.i18n.titles.default).exists()).toBe(true);
});
it('should display the path items correctly', () => {
expect(findPath().props('items')).toMatchObject([
{
selected: true,
title: NewPolicy.i18n.choosePolicyType,
},
{
disabled: true,
selected: false,
title: NewPolicy.i18n.policyDetails,
},
]);
});
it('should display the correct view', () => {
expect(findPolicySelection().exists()).toBe(true);
expect(findPolicyEditor().exists()).toBe(false);
});
});
describe('when there is a type query parameter', () => {
beforeEach(() => {
jest
.spyOn(urlUtils, 'getParameterByName')
.mockReturnValue(POLICY_TYPE_COMPONENT_OPTIONS.scanResult.urlParameter);
factory({
propsData: {
existingPolicy: {
id: 'policy-id',
},
},
});
});
it('should display the title correctly', () => {
expect(wrapper.findByText(NewPolicy.i18n.editTitles.scanResult).exists()).toBe(true);
});
it('should not display the GlPath component when there is an existing policy', () => {
expect(findPath().exists()).toBe(false);
});
it('should display the correct view according to the selected policy', () => {
expect(findPolicySelection().exists()).toBe(false);
expect(findPolicyEditor().exists()).toBe(true);
});
});
});
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { POLICY_TYPE_COMPONENT_OPTIONS } from 'ee/threat_monitoring/components/constants';
import PolicySelection from 'ee/threat_monitoring/components/policy_editor/policy_selection.vue';
describe('PolicySelection component', () => {
let wrapper;
const factory = () => {
wrapper = shallowMountExtended(PolicySelection, {
stubs: { GlCard: true },
});
};
afterEach(() => {
wrapper.destroy();
});
describe.each`
id | title
${POLICY_TYPE_COMPONENT_OPTIONS.container.urlParameter} | ${PolicySelection.i18n.networkPolicyTitle}
${POLICY_TYPE_COMPONENT_OPTIONS.scanResult.urlParameter} | ${PolicySelection.i18n.scanResultPolicyTitle}
${POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.urlParameter} | ${PolicySelection.i18n.scanExecutionPolicyTitle}
`('selection card: $title', ({ id, title }) => {
beforeEach(() => {
factory();
});
it(`should display the title`, () => {
expect(wrapper.findByText(title).exists()).toBe(true);
});
it(`should have a button to link to the second page`, () => {
expect(wrapper.findByTestId(`select-policy-${id}`).attributes('href')).toContain(
`?type=${id}`,
);
});
});
});
......@@ -14891,6 +14891,9 @@ msgstr ""
msgid "Example: @sub\\.company\\.com$"
msgstr ""
msgid "Examples"
msgstr ""
msgid "Except policy:"
msgstr ""
......@@ -32931,6 +32934,9 @@ msgstr ""
msgid "SecurityOrchestration|All policies"
msgstr ""
msgid "SecurityOrchestration|Allow all inbound traffic to all pods from all pods on ports 443/TCP."
msgstr ""
msgid "SecurityOrchestration|An error occurred assigning your security policy project"
msgstr ""
......@@ -32946,12 +32952,21 @@ msgstr ""
msgid "SecurityOrchestration|Don't show the alert anymore"
msgstr ""
msgid "SecurityOrchestration|Edit network policy"
msgstr ""
msgid "SecurityOrchestration|Edit policy"
msgstr ""
msgid "SecurityOrchestration|Edit policy project"
msgstr ""
msgid "SecurityOrchestration|Edit scan exection policy"
msgstr ""
msgid "SecurityOrchestration|Edit scan result policy"
msgstr ""
msgid "SecurityOrchestration|Empty policy name"
msgstr ""
......@@ -32961,6 +32976,9 @@ msgstr ""
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
msgstr ""
msgid "SecurityOrchestration|If any scanner finds a newly detected critical vulnerability in an open merge request targeting the master branch, then require two approvals from any member of App security."
msgstr ""
msgid "SecurityOrchestration|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
msgstr ""
......@@ -32973,9 +32991,21 @@ msgstr ""
msgid "SecurityOrchestration|Network"
msgstr ""
msgid "SecurityOrchestration|Network policy"
msgstr ""
msgid "SecurityOrchestration|New network policy"
msgstr ""
msgid "SecurityOrchestration|New policy"
msgstr ""
msgid "SecurityOrchestration|New scan exection policy"
msgstr ""
msgid "SecurityOrchestration|New scan result policy"
msgstr ""
msgid "SecurityOrchestration|No actions defined - policy will not run."
msgstr ""
......@@ -33024,6 +33054,9 @@ msgstr ""
msgid "SecurityOrchestration|Rules"
msgstr ""
msgid "SecurityOrchestration|Run a DAST scan with Scan Profile A and Site Profile A when a pipeline run against the main branch."
msgstr ""
msgid "SecurityOrchestration|Runs %{actions} and %{lastAction} scans"
msgstr ""
......@@ -33042,12 +33075,21 @@ msgstr ""
msgid "SecurityOrchestration|Scan execution policies can only be created by project owners."
msgstr ""
msgid "SecurityOrchestration|Scan execution policy"
msgstr ""
msgid "SecurityOrchestration|Scan execution policy allow to create rules which forces security scans for particular branches at certain time. Supported types are SAST, DAST, Secret detection, Container scan, License scan, API fuzzing, coverage-guided fuzzing."
msgstr ""
msgid "SecurityOrchestration|Scan result"
msgstr ""
msgid "SecurityOrchestration|Scan result policies can only be created by project owners."
msgstr ""
msgid "SecurityOrchestration|Scan result policy"
msgstr ""
msgid "SecurityOrchestration|Scan to be performed %{cadence}"
msgstr ""
......@@ -33066,6 +33108,9 @@ msgstr ""
msgid "SecurityOrchestration|Select a project to store your security policies in. %{linkStart}More information.%{linkEnd}"
msgstr ""
msgid "SecurityOrchestration|Select policy"
msgstr ""
msgid "SecurityOrchestration|Select security project"
msgstr ""
......@@ -33075,6 +33120,12 @@ msgstr ""
msgid "SecurityOrchestration|Status"
msgstr ""
msgid "SecurityOrchestration|Step 1: Choose a policy type"
msgstr ""
msgid "SecurityOrchestration|Step 2: Policy details"
msgstr ""
msgid "SecurityOrchestration|Summary"
msgstr ""
......@@ -33102,6 +33153,12 @@ msgstr ""
msgid "SecurityOrchestration|Update scan policies"
msgstr ""
msgid "SecurityOrchestration|Use a scan result policy to create rules that ensure security issues are checked before merging a merge request."
msgstr ""
msgid "SecurityOrchestration|Use network policies to create firewall rules for network connections in your Kubernetes cluster."
msgstr ""
msgid "SecurityOrchestration|View policy project"
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