Commit b3b98f4e authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '339034-humanize-scan-rules-actions' into 'master'

Create human-reable actions and rules

See merge request gitlab-org/gitlab!68806
parents 4dcc8bca 40a5c7c6
<script> <script>
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { fromYaml } from '../policy_editor/scan_execution_policy/lib'; import {
fromYaml,
humanizeActions,
humanizeRules,
} from '../policy_editor/scan_execution_policy/lib';
import BasePolicy from './base_policy.vue'; import BasePolicy from './base_policy.vue';
import PolicyInfoRow from './policy_info_row.vue'; import PolicyInfoRow from './policy_info_row.vue';
...@@ -17,6 +21,12 @@ export default { ...@@ -17,6 +21,12 @@ export default {
}, },
}, },
computed: { computed: {
humanizedActions() {
return humanizeActions(this.parsedYaml.actions);
},
humanizedRules() {
return humanizeRules(this.parsedYaml.rules);
},
parsedYaml() { parsedYaml() {
try { try {
return fromYaml(this.policy.yaml); return fromYaml(this.policy.yaml);
...@@ -30,45 +40,39 @@ export default { ...@@ -30,45 +40,39 @@ export default {
<template> <template>
<base-policy :policy="policy"> <base-policy :policy="policy">
<template #type>{{ s__('SecurityPolicies|Scan execution') }}</template> <template #type>{{ s__('SecurityOrchestration|Scan execution') }}</template>
<template #default="{ enforcementStatusLabel }"> <template #default="{ enforcementStatusLabel }">
<div v-if="parsedYaml"> <div v-if="parsedYaml">
<policy-info-row <policy-info-row
v-if="parsedYaml.description" v-if="parsedYaml.description"
data-testid="description" data-testid="policy-description"
:label="s__('SecurityPolicies|Description')" :label="s__('SecurityOrchestration|Description')"
>{{ parsedYaml.description }}</policy-info-row
> >
{{ parsedYaml.description }}
</policy-info-row>
<!-- TODO: humanize policy rules --> <policy-info-row data-testid="policy-rules" :label="s__('SecurityOrchestration|Rule')">
<!-- <policy-info-row <p v-for="rule in humanizedRules" :key="rule">{{ rule }}</p>
v-if="policy.rules" </policy-info-row>
data-testid="rules"
:label="s__('SecurityPolicies|Rule')"
>{{ policy.rules }}</policy-info-row
> -->
<!-- TODO: humanize policy actions --> <policy-info-row data-testid="policy-actions" :label="s__('SecurityOrchestration|Action')">
<!-- <policy-info-row <p v-for="action in humanizedActions" :key="action">{{ action }}</p>
v-if="policy.actions" </policy-info-row>
data-testid="actions"
:label="s__('SecurityPolicies|Action')"
>{{ policy.actions }}</policy-info-row
> -->
<policy-info-row :label="s__('SecurityPolicies|Enforcement status')">{{ <policy-info-row :label="s__('SecurityOrchestration|Enforcement Status')">
enforcementStatusLabel {{ enforcementStatusLabel }}
}}</policy-info-row> </policy-info-row>
<policy-info-row <policy-info-row
v-if="parsedYaml.latestScan" v-if="policy.latestScan"
data-testid="latest-scan" data-testid="policy-latest-scan"
:label="s__('SecurityPolicies|Latest scan')" :label="s__('SecurityOrchestration|Latest scan')"
>{{ parsedYaml.latestScan.date }} >
<gl-link :href="parsedYaml.latestScan.pipelineUrl">{{ {{ policy.latestScan.date }}
s__('SecurityPolicies|view results') <gl-link :href="policy.latestScan.pipelineUrl">
}}</gl-link></policy-info-row {{ s__('SecurityOrchestration|view results') }}
</gl-link></policy-info-row
> >
</div> </div>
</template> </template>
......
...@@ -5,6 +5,7 @@ export const DEFAULT_MR_TITLE = s__('SecurityOrchestration|Update scan execution ...@@ -5,6 +5,7 @@ export const DEFAULT_MR_TITLE = s__('SecurityOrchestration|Update scan execution
export const GRAPHQL_ERROR_MESSAGE = s__( export const GRAPHQL_ERROR_MESSAGE = s__(
'SecurityOrchestration|There was a problem creating the new security policy', 'SecurityOrchestration|There was a problem creating the new security policy',
); );
export const NO_RULE_MESSAGE = s__('SecurityOrhestration|No rules defined - policy will not run.');
export const SECURITY_POLICY_ACTIONS = { export const SECURITY_POLICY_ACTIONS = {
APPEND: 'APPEND', APPEND: 'APPEND',
......
import { convertToTitleCase, humanize } from '~/lib/utils/text_utility';
import { sprintf, s__, n__ } from '~/locale';
import { NO_RULE_MESSAGE } from './constants';
const getActionText = (scanType) =>
sprintf(s__('SecurityOrchestration|Executes a %{scanType} scan'), {
scanType: convertToTitleCase(humanize(scanType)),
});
/**
* Create a human-readable list of strings, adding the necessary punctuation and conjunctions
* @param {Array} branches strings representing branches
* @returns {String}
*/
const humanizeBranches = (originalBranches) => {
const branches = [...originalBranches];
const plural = n__('branch', 'branches', branches.length);
if (branches.length === 1) {
return sprintf(s__('SecurityOrchestration|%{branches} %{plural}'), {
branches: branches.join(','),
plural,
});
}
const lastBranch = branches.pop();
return sprintf(s__('SecurityOrchestration|%{branches} and %{lastBranch} %{plural}'), {
branches: branches.join(', '),
lastBranch,
plural,
});
};
const humanizeCadence = (cadence) => {
return cadence;
};
const humanizePipelineRule = (rule) => {
return sprintf(
s__('SecurityOrchestration|Scan to be performed on every pipeline on the %{branches}'),
{ branches: humanizeBranches(rule.branches) },
);
};
const humanizeScheduleRule = (rule) => {
return sprintf(
s__('SecurityOrchestration|Scan to be performed every %{cadence} on the %{branches}'),
{ cadence: humanizeCadence(rule.cadence), branches: humanizeBranches(rule.branches) },
);
};
const HUMANIZE_RULES_METHODS = {
pipeline: humanizePipelineRule,
schedule: humanizeScheduleRule,
};
/**
* Create a human-readable version of the actions
* @param {Array} actions [{"scan":"dast","scanner_profile":"Scanner Profile","site_profile":"Site Profile"},{"type":"secret_detection"}]
* @returns {Set}
*/
export const humanizeActions = (actions) => {
return new Set(actions.map((action) => getActionText(action.scan)));
};
/**
* Create a human-readable version of the rules
* @param {Array} rules [{"type":"schedule","cadence":"*\/10 * * * *","branches":["master"]},{"type":"pipeline","branches":["master"]}]
* @returns {Array}
*/
export const humanizeRules = (rules) => {
const humanizedRules = rules.reduce((acc, curr) => {
return curr.branches ? [...acc, HUMANIZE_RULES_METHODS[curr.type](curr)] : acc;
}, []);
return humanizedRules.length ? humanizedRules : [NO_RULE_MESSAGE];
};
...@@ -2,6 +2,7 @@ export { fromYaml } from './from_yaml'; ...@@ -2,6 +2,7 @@ export { fromYaml } from './from_yaml';
export { toYaml } from './to_yaml'; export { toYaml } from './to_yaml';
export * from './constants'; export * from './constants';
export * from './humanize';
export * from './utils'; export * from './utils';
export const DEFAULT_SCAN_EXECUTION_POLICY = `type: scan_execution_policy export const DEFAULT_SCAN_EXECUTION_POLICY = `type: scan_execution_policy
......
import BasePolicy from 'ee/threat_monitoring/components/policy_drawer/base_policy.vue'; 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 ScanExecutionPolicy from 'ee/threat_monitoring/components/policy_drawer/scan_execution_policy.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockScanExecutionPolicy } from '../../mocks/mock_data'; import {
mockSecretDetectionScanExecutionManifest,
mockScanExecutionPolicy,
} from '../../mocks/mock_data';
describe('ScanExecutionPolicy component', () => { describe('ScanExecutionPolicy component', () => {
let wrapper; let wrapper;
const findDescription = () => wrapper.findByTestId('description'); const findActions = () => wrapper.findByTestId('policy-actions');
const findDescription = () => wrapper.findByTestId('policy-description');
const findLatestScan = () => wrapper.findByTestId('policy-latest-scan');
const findRules = () => wrapper.findByTestId('policy-rules');
const factory = ({ propsData } = {}) => { const factory = ({ propsData } = {}) => {
wrapper = shallowMountExtended(ScanExecutionPolicy, { wrapper = shallowMountExtended(ScanExecutionPolicy, {
...@@ -21,16 +27,45 @@ describe('ScanExecutionPolicy component', () => { ...@@ -21,16 +27,45 @@ describe('ScanExecutionPolicy component', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('supported YAML', () => { describe('default policy', () => {
beforeEach(() => { beforeEach(() => {
factory({ propsData: { policy: mockScanExecutionPolicy } }); factory({ propsData: { policy: mockScanExecutionPolicy } });
}); });
it('does render the policy description', () => { it.each`
expect(findDescription().exists()).toBe(true); component | finder | text
expect(findDescription().text()).toBe( ${'actions'} | ${findActions} | ${''}
'This policy enforces pipeline configuration to have a job with DAST scan', ${'rules'} | ${findRules} | ${''}
); ${'description'} | ${findDescription} | ${'This policy enforces pipeline configuration to have a job with DAST scan'}
${'latest scan'} | ${findLatestScan} | ${''}
`('does render the policy $component', ({ finder, text }) => {
const component = finder();
expect(component.exists()).toBe(true);
if (text) {
expect(component.text()).toBe(text);
}
});
});
describe('empty policy', () => {
beforeEach(() => {
factory({
propsData: {
policy: {
...mockScanExecutionPolicy,
latestScan: undefined,
yaml: mockSecretDetectionScanExecutionManifest,
},
},
});
});
it.each`
component | finder
${'description'} | ${findDescription}
${'latest scan'} | ${findLatestScan}
`('does render the policy $component', ({ finder }) => {
expect(finder().exists()).toBe(false);
}); });
}); });
}); });
import {
humanizeActions,
humanizeRules,
NO_RULE_MESSAGE,
} from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib';
const mockActions = [
{ scan: 'dast', scanner_profile: 'Scanner Profile', site_profile: 'Site Profile' },
{ scan: 'dast', scanner_profile: 'Scanner Profile 01', site_profile: 'Site Profile 01' },
{ scan: 'secret_detection' },
];
const mockRules = [
{ type: 'schedule', cadence: '*/10 * * * *', branches: ['main'] },
{ type: 'pipeline', branches: ['release/*', 'staging'] },
{ type: 'pipeline', branches: ['release/1.*', 'canary', 'staging'] },
{ type: 'pipeline' },
];
describe('humanizeActions', () => {
it('returns an empty Array of actions as an empty Set', () => {
expect(humanizeActions([])).toStrictEqual(new Set());
});
it('returns a single action as human-readable string', () => {
expect(humanizeActions([mockActions[0]])).toStrictEqual(new Set(['Executes a Dast scan']));
});
it('returns multiple actions as human-readable strings', () => {
expect(humanizeActions(mockActions)).toStrictEqual(
new Set(['Executes a Dast scan', 'Executes a Secret Detection scan']),
);
});
});
describe('humanizeRules', () => {
it('returns the empty rules message in an Array if no rules are specified', () => {
expect(humanizeRules([])).toStrictEqual([NO_RULE_MESSAGE]);
});
it('returns the empty rules message in an Array if a single rule is passed in without a branch', () => {
expect(humanizeRules([])).toStrictEqual([NO_RULE_MESSAGE]);
});
it('returns a single rule as a human-readable string', () => {
expect(humanizeRules([mockRules[0]])).toStrictEqual([
'Scan to be performed every */10 * * * * on the main branch',
]);
});
it('returns multiple rules with different number of branches as human-readable strings', () => {
expect(humanizeRules(mockRules)).toStrictEqual([
'Scan to be performed every */10 * * * * on the main branch',
'Scan to be performed on every pipeline on the release/* and staging branches',
'Scan to be performed on every pipeline on the release/1.*, canary and staging branches',
]);
});
});
export const mockSecretDetectionScanExecutionManifest = `---
name: Enforce DAST in every pipeline
enabled: false,
rules:
- type: pipeline
branches:
- main
- release/*
- staging
actions:
- scan: secret_detection
`;
export const mockDastAndSecretDetectionScanExecutionManifest = `---
name: Enforce DAST in every pipeline
description: This policy enforces pipeline configuration to have a job with DAST scan
enabled: true
rules:
- type: schedule
cadence: "*/10 * * * *"
branches:
- main
- type: pipeline
branches:
- main
- release/*
- staging
actions:
- scan: dast
scanner_profile: Scanner Profile
site_profile: Site Profile
- scan: secret_detection
`;
export const mockEnvironmentsResponse = { export const mockEnvironmentsResponse = {
environments: [ environments: [
{ {
...@@ -155,6 +189,7 @@ export const mockScanExecutionPolicy = { ...@@ -155,6 +189,7 @@ export const mockScanExecutionPolicy = {
updatedAt: new Date('2021-06-07T00:00:00.000Z'), updatedAt: new Date('2021-06-07T00:00:00.000Z'),
yaml: mockDastScanExecutionManifest, yaml: mockDastScanExecutionManifest,
enabled: true, enabled: true,
latestScan: { date: new Date('2021-06-07T00:00:00.000Z'), pipelineUrl: 'path/to/pipeline' },
}; };
export const mockScanExecutionPoliciesResponse = [mockScanExecutionPolicy]; export const mockScanExecutionPoliciesResponse = [mockScanExecutionPolicy];
......
...@@ -29652,12 +29652,24 @@ msgstr "" ...@@ -29652,12 +29652,24 @@ msgstr ""
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request" msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request"
msgstr "" msgstr ""
msgid "SecurityOrchestration|%{branches} %{plural}"
msgstr ""
msgid "SecurityOrchestration|%{branches} and %{lastBranch} %{plural}"
msgstr ""
msgid "SecurityOrchestration|Action"
msgstr ""
msgid "SecurityOrchestration|All policies" msgid "SecurityOrchestration|All policies"
msgstr "" msgstr ""
msgid "SecurityOrchestration|An error occurred assigning your security policy project" msgid "SecurityOrchestration|An error occurred assigning your security policy project"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Description"
msgstr ""
msgid "SecurityOrchestration|Edit policy" msgid "SecurityOrchestration|Edit policy"
msgstr "" msgstr ""
...@@ -29667,9 +29679,18 @@ msgstr "" ...@@ -29667,9 +29679,18 @@ msgstr ""
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}" msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Enforcement Status"
msgstr ""
msgid "SecurityOrchestration|Executes a %{scanType} scan"
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}." 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 "" msgstr ""
msgid "SecurityOrchestration|Latest scan"
msgstr ""
msgid "SecurityOrchestration|Network" msgid "SecurityOrchestration|Network"
msgstr "" msgstr ""
...@@ -29697,12 +29718,21 @@ msgstr "" ...@@ -29697,12 +29718,21 @@ msgstr ""
msgid "SecurityOrchestration|Policy type" msgid "SecurityOrchestration|Policy type"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Rule"
msgstr ""
msgid "SecurityOrchestration|Scan Execution" msgid "SecurityOrchestration|Scan Execution"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Scan execution" msgid "SecurityOrchestration|Scan execution"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Scan to be performed every %{cadence} on the %{branches}"
msgstr ""
msgid "SecurityOrchestration|Scan to be performed on every pipeline on the %{branches}"
msgstr ""
msgid "SecurityOrchestration|Security policy project was linked successfully" msgid "SecurityOrchestration|Security policy project was linked successfully"
msgstr "" msgstr ""
...@@ -29727,30 +29757,21 @@ msgstr "" ...@@ -29727,30 +29757,21 @@ msgstr ""
msgid "SecurityOrchestration|Update scan execution policies" msgid "SecurityOrchestration|Update scan execution policies"
msgstr "" msgstr ""
msgid "SecurityPolicies|+%{count} more" msgid "SecurityOrchestration|view results"
msgstr "" msgstr ""
msgid "SecurityPolicies|Description" msgid "SecurityOrhestration|No rules defined - policy will not run."
msgstr "" msgstr ""
msgid "SecurityPolicies|Enforcement status" msgid "SecurityPolicies|+%{count} more"
msgstr "" msgstr ""
msgid "SecurityPolicies|Environment(s)" msgid "SecurityPolicies|Environment(s)"
msgstr "" msgstr ""
msgid "SecurityPolicies|Latest scan"
msgstr ""
msgid "SecurityPolicies|Policy type" msgid "SecurityPolicies|Policy type"
msgstr "" msgstr ""
msgid "SecurityPolicies|Scan execution"
msgstr ""
msgid "SecurityPolicies|view results"
msgstr ""
msgid "SecurityReports|%{firstProject} and %{secondProject}" msgid "SecurityReports|%{firstProject} and %{secondProject}"
msgstr "" msgstr ""
...@@ -38981,6 +39002,11 @@ msgstr "" ...@@ -38981,6 +39002,11 @@ msgstr ""
msgid "blocks" msgid "blocks"
msgstr "" msgstr ""
msgid "branch"
msgid_plural "branches"
msgstr[0] ""
msgstr[1] ""
msgid "branch name" msgid "branch name"
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