Commit 959baf5a authored by David O'Regan's avatar David O'Regan

Merge branch '273788-scan-execution-policy-drawer' into 'master'

Create scan execution policy drawer component

Creates the scan-execution-policy component meant to render scan
execution policies' details in the policy drawer in Threat Monitoring.

This also makes policy components composable so that more policy types
can easily be added in the future.

See merge request gitlab-org/gitlab!63533
parents da759b75 0e5f0ff8
......@@ -5,7 +5,7 @@ import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import EnvironmentPicker from './environment_picker.vue';
import NetworkPolicyDrawer from './policy_drawer/network_policy_drawer.vue';
import PolicyDrawer from './policy_drawer/policy_drawer.vue';
export default {
components: {
......@@ -16,7 +16,7 @@ export default {
GlSprintf,
GlLink,
EnvironmentPicker,
NetworkPolicyDrawer,
PolicyDrawer,
},
props: {
documentationPath: {
......@@ -195,7 +195,7 @@ export default {
</template>
</gl-table>
<network-policy-drawer
<policy-drawer
:open="hasSelectedPolicy"
:policy="selectedPolicy"
:edit-policy-path="editPolicyPath"
......
<script>
import { __ } from '~/locale';
export default {
props: {
policy: {
type: Object,
required: false,
default: null,
},
},
computed: {
enforcementStatusLabel() {
return this.policy?.isEnabled ? __('Enabled') : __('Disabled');
},
},
};
</script>
<template>
<div>
<h5 class="gl-mt-3">{{ __('Type') }}</h5>
<p data-testid="policy-type">
<slot name="type"></slot>
</p>
<slot v-bind="{ enforcementStatusLabel }"></slot>
</div>
</template>
<script>
import { __ } from '~/locale';
import {
fromYaml,
humanizeNetworkPolicy,
removeUnnecessaryDashes,
} from '../policy_editor/network_policy/lib';
import PolicyPreview from '../policy_editor/policy_preview.vue';
import BasePolicy from './base_policy.vue';
import PolicyInfoRow from './policy_info_row.vue';
export default {
components: {
BasePolicy,
PolicyPreview,
PolicyInfoRow,
},
props: {
value: {
......@@ -31,36 +34,34 @@ export default {
policyYaml() {
return removeUnnecessaryDashes(this.value);
},
enforcementStatusLabel() {
if (this.policy) {
return this.policy.isEnabled ? __('Enabled') : __('Disabled');
}
return null;
},
},
};
</script>
<template>
<div>
<h5 class="gl-mt-3">{{ __('Type') }}</h5>
<p>{{ s__('NetworkPolicies|Network policy') }}</p>
<base-policy :policy="policy">
<template #type>{{ s__('NetworkPolicies|Network policy') }}</template>
<div v-if="policy">
<template v-if="policy.description">
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Description') }}</h5>
<p data-testid="description">{{ policy.description }}</p>
</template>
<template #default="{ enforcementStatusLabel }">
<div v-if="policy">
<policy-info-row
v-if="policy.description"
data-testid="description"
:label="s__('NetworkPolicies|Description')"
>{{ policy.description }}</policy-info-row
>
<h5 class="gl-mt-6">{{ s__('NetworkPolicies|Enforcement status') }}</h5>
<p>{{ enforcementStatusLabel }}</p>
</div>
<policy-info-row :label="s__('NetworkPolicies|Enforcement status')">{{
enforcementStatusLabel
}}</policy-info-row>
</div>
<policy-preview
class="gl-mt-4"
:initial-tab="initialTab"
:policy-yaml="policyYaml"
:policy-description="humanizedPolicy"
/>
</div>
<policy-preview
class="gl-mt-4"
:initial-tab="initialTab"
:policy-yaml="policyYaml"
:policy-description="humanizedPolicy"
/>
</template>
</base-policy>
</template>
<script>
import { GlButton, GlDrawer } from '@gitlab/ui';
import { getContentWrapperHeight } from '../../utils';
import { CiliumNetworkPolicyKind } from '../policy_editor/network_policy/lib';
import {
CiliumNetworkPolicyKind,
ScanExecutionPolicyKind,
} from '../policy_editor/network_policy/lib';
import CiliumNetworkPolicy from './cilium_network_policy.vue';
import ScanExecutionPolicy from './scan_execution_policy.vue';
const policyComponent = {
[CiliumNetworkPolicyKind]: CiliumNetworkPolicy,
[ScanExecutionPolicyKind]: ScanExecutionPolicy,
};
export default {
components: {
......@@ -11,6 +20,7 @@ export default {
PolicyYamlEditor: () =>
import(/* webpackChunkName: 'policy_yaml_editor' */ '../policy_yaml_editor.vue'),
CiliumNetworkPolicy,
ScanExecutionPolicy,
},
props: {
policy: {
......@@ -25,8 +35,17 @@ export default {
},
},
computed: {
isCiliumNetworkPolicy() {
return this.policy ? this.policy.manifest.includes(CiliumNetworkPolicyKind) : false;
policyKind() {
if (this.policy?.manifest?.includes(CiliumNetworkPolicyKind)) {
return CiliumNetworkPolicyKind;
}
if (this.policy?.manifest?.includes(ScanExecutionPolicyKind)) {
return ScanExecutionPolicyKind;
}
return null;
},
policyComponent() {
return policyComponent[this.policyKind] || null;
},
},
methods: {
......@@ -62,8 +81,7 @@ export default {
</div>
</template>
<div v-if="policy">
<cilium-network-policy v-if="isCiliumNetworkPolicy" :value="policy.manifest" />
<component :is="policyComponent" v-if="policyComponent" :value="policy.manifest" />
<div v-else>
<h5>{{ s__('NetworkPolicies|Policy definition') }}</h5>
<p>
......
<script>
export default {
props: {
label: {
type: String,
required: true,
},
},
};
</script>
<template>
<div>
<h5 data-testid="label" class="gl-mt-6">{{ label }}</h5>
<p data-testid="content"><slot></slot></p>
</div>
</template>
<script>
import { GlLink } from '@gitlab/ui';
import { safeLoad } from 'js-yaml';
import BasePolicy from './base_policy.vue';
import PolicyInfoRow from './policy_info_row.vue';
export default {
components: {
GlLink,
BasePolicy,
PolicyInfoRow,
},
props: {
value: {
type: String,
required: true,
},
},
computed: {
policy() {
return safeLoad(this.value, { json: true });
},
},
};
</script>
<template>
<base-policy :policy="policy">
<template #type>{{ s__('SecurityPolicies|Scan execution') }}</template>
<template #default="{ enforcementStatusLabel }">
<div v-if="policy">
<policy-info-row
v-if="policy.description"
data-testid="description"
:label="s__('SecurityPolicies|Description')"
>{{ policy.description }}</policy-info-row
>
<!-- TODO: humanize policy rules -->
<!-- <policy-info-row
v-if="policy.rules"
data-testid="rules"
:label="s__('SecurityPolicies|Rule')"
>{{ policy.rules }}</policy-info-row
> -->
<!-- TODO: humanize policy actions -->
<!-- <policy-info-row
v-if="policy.actions"
data-testid="actions"
:label="s__('SecurityPolicies|Action')"
>{{ policy.actions }}</policy-info-row
> -->
<policy-info-row :label="s__('SecurityPolicies|Enforcement status')">{{
enforcementStatusLabel
}}</policy-info-row>
<policy-info-row
v-if="policy.latestScan"
data-testid="latest-scan"
:label="s__('SecurityPolicies|Latest scan')"
>{{ policy.latestScan.date }}
<gl-link :href="policy.latestScan.pipelineUrl">{{
s__('SecurityPolicies|view results')
}}</gl-link></policy-info-row
>
</div>
</template>
</base-policy>
</template>
......@@ -14,6 +14,7 @@ export const RuleTypeCIDR = 'NetworkPolicyRuleCIDR';
export const RuleTypeFQDN = 'NetworkPolicyRuleFQDN';
export const CiliumNetworkPolicyKind = 'CiliumNetworkPolicy';
export const ScanExecutionPolicyKind = 'scanner_profile';
export const EntityTypes = {
ALL: 'all',
......
......@@ -30,7 +30,7 @@ describe('NetworkPolicyList component', () => {
data,
store,
provide,
stubs: { NetworkPolicyDrawer: GlDrawer },
stubs: { PolicyDrawer: GlDrawer },
});
};
......
......@@ -8,32 +8,25 @@ exports[`CiliumNetworkPolicy component supported YAML renders policy preview tab
Type
</h5>
<p>
<p
data-testid="policy-type"
>
Network policy
</p>
<div>
<h5
class="gl-mt-6"
>
Description
</h5>
<p
<policy-info-row-stub
data-testid="description"
label="Description"
>
test description
</p>
</policy-info-row-stub>
<h5
class="gl-mt-6"
<policy-info-row-stub
label="Enforcement status"
>
Enforcement status
</h5>
<p>
Disabled
</p>
</policy-info-row-stub>
</div>
<policy-preview-stub
......@@ -62,7 +55,9 @@ exports[`CiliumNetworkPolicy component unsupported YAML renders policy preview t
Type
</h5>
<p>
<p
data-testid="policy-type"
>
Network policy
</p>
......
import BasePolicy from 'ee/threat_monitoring/components/policy_drawer/base_policy.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('BasePolicy component', () => {
let wrapper;
const findPolicyType = () => wrapper.findByTestId('policy-type');
const findEnforcementStatusLabel = () => wrapper.findByTestId('enforcement-status-label');
const factory = (propsData = {}) => {
wrapper = shallowMountExtended(BasePolicy, {
propsData,
slots: {
type: 'Policy type',
},
scopedSlots: {
default:
'<span data-testid="enforcement-status-label">{{ props.enforcementStatusLabel }}</span>',
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders the policy type', () => {
factory();
expect(findPolicyType().text()).toBe('Policy type');
});
it.each`
description | isEnabled | expectedLabel
${'enabled'} | ${true} | ${'Enabled'}
${'disabled'} | ${false} | ${'Disabled'}
`(
'renders the enforcement status label when policy is $description',
({ isEnabled, expectedLabel }) => {
factory({
policy: {
isEnabled,
},
});
expect(findEnforcementStatusLabel().text()).toBe(expectedLabel);
},
);
});
import BasePolicy from 'ee/threat_monitoring/components/policy_drawer/base_policy.vue';
import CiliumNetworkPolicy from 'ee/threat_monitoring/components/policy_drawer/cilium_network_policy.vue';
import { toYaml } from 'ee/threat_monitoring/components/policy_editor/network_policy/lib';
import PolicyPreview from 'ee/threat_monitoring/components/policy_editor/policy_preview.vue';
......@@ -21,6 +22,9 @@ describe('CiliumNetworkPolicy component', () => {
propsData: {
...propsData,
},
stubs: {
BasePolicy,
},
});
};
......@@ -39,7 +43,7 @@ describe('CiliumNetworkPolicy component', () => {
it('does render the policy description', () => {
expect(findDescription().exists()).toBe(true);
expect(findDescription().text()).toBe('test description');
expect(findDescription().text()).toContain('test description');
});
it('does render the policy preview', () => {
......
import CiliumNetworkPolicy from 'ee/threat_monitoring/components/policy_drawer/cilium_network_policy.vue';
import NetworkPolicyDrawer from 'ee/threat_monitoring/components/policy_drawer/network_policy_drawer.vue';
import PolicyDrawer from 'ee/threat_monitoring/components/policy_drawer/policy_drawer.vue';
import ScanExecutionPolicy from 'ee/threat_monitoring/components/policy_drawer/scan_execution_policy.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { mockPoliciesResponse, mockCiliumPolicy } from '../../mocks/mock_data';
import {
mockPoliciesResponse,
mockCiliumPolicy,
mockScanExecutionPolicy,
} from '../../mocks/mock_data';
const [mockGenericPolicy] = mockPoliciesResponse;
describe('NetworkPolicyDrawer component', () => {
describe('PolicyDrawer component', () => {
let wrapper;
const factory = ({ propsData } = {}) => {
wrapper = mountExtended(NetworkPolicyDrawer, {
wrapper = mountExtended(PolicyDrawer, {
propsData: {
editPolicyPath: '/policies/policy/edit?environment_id=-1',
open: true,
......@@ -23,6 +28,7 @@ describe('NetworkPolicyDrawer component', () => {
const findEditButton = () => wrapper.findByTestId('edit-button');
const findPolicyEditor = () => wrapper.findByTestId('policy-yaml-editor');
const findCiliumNetworkPolicy = () => wrapper.findComponent(CiliumNetworkPolicy);
const findScanExecutionPolicy = () => wrapper.findComponent(ScanExecutionPolicy);
// Shared assertions
const itRendersEditButton = () => {
......@@ -66,17 +72,21 @@ describe('NetworkPolicyDrawer component', () => {
itRendersEditButton();
});
describe('given a cilium policy', () => {
describe.each`
policyKind | mock | finder
${'cilium'} | ${mockCiliumPolicy} | ${findCiliumNetworkPolicy}
${'scan execution'} | ${mockScanExecutionPolicy} | ${findScanExecutionPolicy}
`('given a $policyKind policy', ({ policyKind, mock, finder }) => {
beforeEach(() => {
factory({
propsData: {
policy: mockCiliumPolicy,
policy: mock,
},
});
});
it('renders the network policy component', () => {
expect(findCiliumNetworkPolicy().exists()).toBe(true);
it(`renders the ${policyKind} component`, () => {
expect(finder().exists()).toBe(true);
});
itRendersEditButton();
......
import PolicyInfoRow from 'ee/threat_monitoring/components/policy_drawer/policy_info_row.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('PolicyInfoRow component', () => {
let wrapper;
const findLabel = () => wrapper.findByTestId('label');
const findContent = () => wrapper.findByTestId('content');
const factory = () => {
wrapper = shallowMountExtended(PolicyInfoRow, {
propsData: {
label: 'Some label',
},
slots: {
default: 'Some <a href="#">content</a>',
},
});
};
beforeEach(() => {
factory();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the label', () => {
expect(findLabel().text()).toBe('Some label');
});
it('renders the content', () => {
expect(findContent().text()).toBe('Some content');
});
});
import BasePolicy from 'ee/threat_monitoring/components/policy_drawer/base_policy.vue';
import ScanExecutionPolicy from 'ee/threat_monitoring/components/policy_drawer/scan_execution_policy.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockScanExecutionPolicy } from '../../mocks/mock_data';
describe('ScanExecutionPolicy component', () => {
let wrapper;
const findDescription = () => wrapper.findByTestId('description');
const factory = ({ propsData } = {}) => {
wrapper = shallowMountExtended(ScanExecutionPolicy, {
propsData,
stubs: {
BasePolicy,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('supported YAML', () => {
beforeEach(() => {
factory({ propsData: { value: mockScanExecutionPolicy.manifest } });
});
it('does render the policy description', () => {
expect(findDescription().exists()).toBe(true);
expect(findDescription().text()).toBe(
'This policy enforces pipeline configuration to have a job with DAST scan',
);
});
});
});
......@@ -52,6 +52,24 @@ spec:
endpointSelector: {}`,
};
export const mockScanExecutionPolicy = {
name: 'Scheduled DAST scan',
creationTimestamp: new Date('2021-06-07T00:00:00.000Z'),
manifest: `---
name: Enforce DAST in every pipeline
description: This policy enforces pipeline configuration to have a job with DAST scan
enabled: true
rules:
- type: pipeline
branches:
- master
actions:
- scan: dast
scanner_profile: Scanner Profile
site_profile: Site Profile
`,
};
export const mockNominalHistory = [
['2019-12-04T00:00:00.000Z', 56],
['2019-12-05T00:00:00.000Z', 2647],
......
......@@ -28898,6 +28898,21 @@ msgstr ""
msgid "SecurityOrchestration|Security policy project"
msgstr ""
msgid "SecurityPolicies|Description"
msgstr ""
msgid "SecurityPolicies|Enforcement status"
msgstr ""
msgid "SecurityPolicies|Latest scan"
msgstr ""
msgid "SecurityPolicies|Scan execution"
msgstr ""
msgid "SecurityPolicies|view results"
msgstr ""
msgid "SecurityReports|%{firstProject} and %{secondProject}"
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