Commit e7507134 authored by Lukas 'Eipi' Eipert's avatar Lukas 'Eipi' Eipert Committed by Kushal Pandya

Container Scanning: Messages and description

This improves messages and descriptions of container scanning. In order
to align with backend, we remove inherit copying of attributes with
spreading and explicitly build the same structure backend provides for
us
parent e699c439
...@@ -68,6 +68,7 @@ export default { ...@@ -68,6 +68,7 @@ export default {
}, },
[types.SET_MODAL_DATA](state, payload) { [types.SET_MODAL_DATA](state, payload) {
const { vulnerability } = payload; const { vulnerability } = payload;
const { location } = vulnerability;
Vue.set(state.modal, 'title', vulnerability.name); Vue.set(state.modal, 'title', vulnerability.name);
Vue.set(state.modal.data.description, 'value', vulnerability.description); Vue.set(state.modal.data.description, 'value', vulnerability.description);
...@@ -81,22 +82,38 @@ export default { ...@@ -81,22 +82,38 @@ export default {
'url', 'url',
vulnerability.project && vulnerability.project.full_path, vulnerability.project && vulnerability.project.full_path,
); );
Vue.set(
state.modal.data.file,
'value',
vulnerability.location &&
`${vulnerability.location.file}:${vulnerability.location.start_line}`,
);
Vue.set( Vue.set(
state.modal.data.identifiers, state.modal.data.identifiers,
'value', 'value',
vulnerability.identifiers.length && vulnerability.identifiers, vulnerability.identifiers.length && vulnerability.identifiers,
); );
Vue.set(
state.modal.data.className, if (location) {
'value', const {
vulnerability.location && vulnerability.location.class, file,
); start_line: startLine,
end_line: endLine,
image,
operating_system: namespace,
class: className,
} = location;
let lineSuffix = '';
if (startLine) {
lineSuffix += `:${startLine}`;
if (endLine && startLine !== endLine) {
lineSuffix += `-${endLine}`;
}
}
Vue.set(state.modal.data.className, 'value', className);
Vue.set(state.modal.data.file, 'value', `${file}${lineSuffix}`);
Vue.set(state.modal.data.image, 'value', image);
Vue.set(state.modal.data.namespace, 'value', namespace);
}
Vue.set(state.modal.data.severity, 'value', vulnerability.severity); Vue.set(state.modal.data.severity, 'value', vulnerability.severity);
Vue.set(state.modal.data.reportType, 'value', vulnerability.report_type); Vue.set(state.modal.data.reportType, 'value', vulnerability.report_type);
Vue.set(state.modal.data.confidence, 'value', vulnerability.confidence); Vue.set(state.modal.data.confidence, 'value', vulnerability.confidence);
......
...@@ -28,9 +28,11 @@ export default () => ({ ...@@ -28,9 +28,11 @@ export default () => ({
file: { text: s__('Vulnerability|File') }, file: { text: s__('Vulnerability|File') },
identifiers: { text: s__('Vulnerability|Identifiers') }, identifiers: { text: s__('Vulnerability|Identifiers') },
severity: { text: s__('Vulnerability|Severity') }, severity: { text: s__('Vulnerability|Severity') },
reportType: { text: s__('Vulnerability|Report Type') },
confidence: { text: s__('Vulnerability|Confidence') }, confidence: { text: s__('Vulnerability|Confidence') },
reportType: { text: s__('Vulnerability|Report Type') },
className: { text: s__('Vulnerability|Class') }, className: { text: s__('Vulnerability|Class') },
image: { text: s__('Vulnerability|Image') },
namespace: { text: s__('Vulnerability|Namespace') },
links: { text: s__('Vulnerability|Links') }, links: { text: s__('Vulnerability|Links') },
instances: { text: s__('Vulnerability|Instances') }, instances: { text: s__('Vulnerability|Instances') },
}, },
......
...@@ -34,7 +34,5 @@ export default { ...@@ -34,7 +34,5 @@ export default {
<modal-open-name :issue="issue" :status="status" /> <modal-open-name :issue="issue" :status="status" />
</div> </div>
<report-link v-if="issue.path" :issue="issue" />
</div> </div>
</template> </template>
...@@ -4,11 +4,11 @@ import { ...@@ -4,11 +4,11 @@ import {
parseSastIssues, parseSastIssues,
parseDependencyScanningIssues, parseDependencyScanningIssues,
filterByKey, filterByKey,
parseSastContainer,
parseDastIssues, parseDastIssues,
getUnapprovedVulnerabilities, getUnapprovedVulnerabilities,
findIssueIndex, findIssueIndex,
} from './utils'; } from './utils';
import { parseSastContainer } from './utils/container_scanning';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
export default { export default {
...@@ -118,11 +118,11 @@ export default { ...@@ -118,11 +118,11 @@ export default {
[types.RECEIVE_SAST_CONTAINER_REPORTS](state, reports) { [types.RECEIVE_SAST_CONTAINER_REPORTS](state, reports) {
if (reports.base && reports.head) { if (reports.base && reports.head) {
const headIssues = getUnapprovedVulnerabilities( const headIssues = getUnapprovedVulnerabilities(
parseSastContainer(reports.head.vulnerabilities, reports.enrichData), parseSastContainer(reports.head.vulnerabilities, reports.enrichData, reports.head.image),
reports.head.unapproved, reports.head.unapproved,
); );
const baseIssues = getUnapprovedVulnerabilities( const baseIssues = getUnapprovedVulnerabilities(
parseSastContainer(reports.base.vulnerabilities, reports.enrichData), parseSastContainer(reports.base.vulnerabilities, reports.enrichData, reports.base.image),
reports.base.unapproved, reports.base.unapproved,
); );
const filterKey = 'vulnerability'; const filterKey = 'vulnerability';
...@@ -135,7 +135,7 @@ export default { ...@@ -135,7 +135,7 @@ export default {
Vue.set(state.sastContainer, 'isLoading', false); Vue.set(state.sastContainer, 'isLoading', false);
} else if (reports.head && !reports.base) { } else if (reports.head && !reports.base) {
const newIssues = getUnapprovedVulnerabilities( const newIssues = getUnapprovedVulnerabilities(
parseSastContainer(reports.head.vulnerabilities, reports.enrichData), parseSastContainer(reports.head.vulnerabilities, reports.enrichData, reports.head.image),
reports.head.unapproved, reports.head.unapproved,
); );
...@@ -265,7 +265,8 @@ export default { ...@@ -265,7 +265,8 @@ export default {
Vue.set(state.modal.data.file, 'url', issue.urlPath); Vue.set(state.modal.data.file, 'url', issue.urlPath);
Vue.set(state.modal.data.className, 'value', issue.location && issue.location.class); Vue.set(state.modal.data.className, 'value', issue.location && issue.location.class);
Vue.set(state.modal.data.methodName, 'value', issue.location && issue.location.method); Vue.set(state.modal.data.methodName, 'value', issue.location && issue.location.method);
Vue.set(state.modal.data.namespace, 'value', issue.namespace); Vue.set(state.modal.data.image, 'value', issue.location && issue.location.image);
Vue.set(state.modal.data.namespace, 'value', issue.location && issue.location.operating_system);
if (issue.identifiers && issue.identifiers.length > 0) { if (issue.identifiers && issue.identifiers.length > 0) {
Vue.set(state.modal.data.identifiers, 'value', issue.identifiers); Vue.set(state.modal.data.identifiers, 'value', issue.identifiers);
......
...@@ -75,16 +75,26 @@ export default () => ({ ...@@ -75,16 +75,26 @@ export default () => ({
text: s__('ciReport|Description'), text: s__('ciReport|Description'),
isLink: false, isLink: false,
}, },
file: {
value: null,
url: null,
text: s__('ciReport|File'),
isLink: true,
},
identifiers: { identifiers: {
value: [], value: [],
text: s__('ciReport|Identifiers'), text: s__('ciReport|Identifiers'),
isLink: false, isLink: false,
}, },
file: { severity: {
value: null, value: null,
url: null, text: s__('ciReport|Severity'),
text: s__('ciReport|File'), isLink: false,
isLink: true, },
confidence: {
value: null,
text: s__('ciReport|Confidence'),
isLink: false,
}, },
className: { className: {
value: null, value: null,
...@@ -96,19 +106,14 @@ export default () => ({ ...@@ -96,19 +106,14 @@ export default () => ({
text: s__('ciReport|Method'), text: s__('ciReport|Method'),
isLink: false, isLink: false,
}, },
namespace: { image: {
value: null,
text: s__('ciReport|Namespace'),
isLink: false,
},
severity: {
value: null, value: null,
text: s__('ciReport|Severity'), text: s__('ciReport|Image'),
isLink: false, isLink: false,
}, },
confidence: { namespace: {
value: null, value: null,
text: s__('ciReport|Confidence'), text: s__('ciReport|Namespace'),
isLink: false, isLink: false,
}, },
links: { links: {
......
...@@ -43,8 +43,8 @@ export const findMatchingRemediations = (remediations, vulnerability) => { ...@@ -43,8 +43,8 @@ export const findMatchingRemediations = (remediations, vulnerability) => {
* @param {Object} vulnerability * @param {Object} vulnerability
* @param {Array} feedback * @param {Array} feedback
*/ */
function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) { export const enrichVulnerabilityWithfeedback = (vulnerability, feedback = []) =>
return feedback feedback
.filter(fb => fb.project_fingerprint === vulnerability.project_fingerprint) .filter(fb => fb.project_fingerprint === vulnerability.project_fingerprint)
.reduce((vuln, fb) => { .reduce((vuln, fb) => {
if (fb.feedback_type === 'dismissal') { if (fb.feedback_type === 'dismissal') {
...@@ -68,7 +68,6 @@ function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) { ...@@ -68,7 +68,6 @@ function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) {
} }
return vuln; return vuln;
}, vulnerability); }, vulnerability);
}
/** /**
* Generates url to repository file and highlight section between start and end lines. * Generates url to repository file and highlight section between start and end lines.
...@@ -198,62 +197,6 @@ export const parseDependencyScanningIssues = (report = [], feedback = [], path = ...@@ -198,62 +197,6 @@ export const parseDependencyScanningIssues = (report = [], feedback = [], path =
}); });
}; };
/**
* Parses Container Scanning results into a common format to allow to use the same Vue component.
* Container Scanning report is currently the straigh output from the underlying tool
* (clair scanner) hence the formatting happenning here.
*
* @param {Array} issues
* @param {Array} feedback
* @returns {Array}
*/
export const parseSastContainer = (issues = [], feedback = []) =>
issues.map(issue => {
const parsed = {
...issue,
category: 'container_scanning',
project_fingerprint: sha1(
`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`,
),
title: issue.vulnerability,
description: !_.isEmpty(issue.description)
? issue.description
: sprintf(s__('ciReport|%{namespace} is affected by %{vulnerability}.'), {
namespace: issue.namespace,
vulnerability: issue.vulnerability,
}),
path: issue.namespace,
identifiers: [
{
type: 'CVE',
name: issue.vulnerability,
value: issue.vulnerability,
url: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
},
],
};
// Generate solution
if (
!_.isEmpty(issue.fixedby) &&
!_.isEmpty(issue.featurename) &&
!_.isEmpty(issue.featureversion)
) {
Object.assign(parsed, {
solution: sprintf(s__('ciReport|Upgrade %{name} from %{version} to %{fixed}.'), {
name: issue.featurename,
version: issue.featureversion,
fixed: issue.fixedby,
}),
});
}
return {
...parsed,
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
/** /**
* Parses DAST into a common format to allow to use the same Vue component. * Parses DAST into a common format to allow to use the same Vue component.
* DAST report is currently the straigh output from the underlying tool (ZAProxy) * DAST report is currently the straigh output from the underlying tool (ZAProxy)
......
import { SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import { s__, sprintf } from '~/locale';
import sha1 from 'sha1';
import _ from 'underscore';
import { enrichVulnerabilityWithfeedback } from '../utils';
/*
Container scanning mapping utils
This file contains all functions for mapping container scanning vulnerabilities
to match the representation that we are building in the backend:
https://gitlab.com/gitlab-org/gitlab-ee/blob/bbcd07475f0334/ee/lib/gitlab/ci/parsers/security/container_scanning.rb
All these function can hopefully be removed as soon as we retrieve the data from the backend.
*/
export const formatContainerScanningDescription = ({
description,
namespace,
vulnerability,
featurename,
featureversion,
}) => {
if (!_.isEmpty(description)) {
return description;
}
let generated;
if (featurename && featureversion) {
generated = `${featurename}:${featureversion}`;
} else if (featurename) {
generated = featurename;
} else {
generated = namespace;
}
return sprintf(s__('ciReport|%{namespace} is affected by %{vulnerability}.'), {
namespace: generated,
vulnerability,
});
};
export const formatContainerScanningMessage = ({ vulnerability, featurename }) => {
if (featurename) {
return sprintf(s__('ciReport|%{vulnerability} in %{featurename}'), {
vulnerability,
featurename,
});
}
return vulnerability;
};
export const formatContainerScanningSolution = ({ fixedby, featurename, featureversion }) => {
if (!_.isEmpty(fixedby)) {
if (!_.isEmpty(featurename)) {
if (!_.isEmpty(featureversion)) {
return sprintf(s__('ciReport|Upgrade %{name} from %{version} to %{fixed}.'), {
name: featurename,
version: featureversion,
fixed: fixedby,
});
}
return sprintf(s__('ciReport|Upgrade %{name} to %{fixed}.'), {
name: featurename,
fixed: fixedby,
});
}
return sprintf(s__('ciReport|Upgrade to %{fixed}.'), {
fixed: fixedby,
});
}
return null;
};
export const parseContainerScanningSeverity = severity => {
if (severity === 'Defcon1') {
return SEVERITY_LEVELS.critical;
} else if (severity === 'Negligible') {
return SEVERITY_LEVELS.low;
}
return severity;
};
/**
* Parses Container Scanning results into a common format to allow to use the same Vue component.
* Container Scanning report is currently the straight output from the underlying tool
* (clair scanner) hence the formatting happening here.
*
* @param {Array} issues
* @param {Array} feedback
* @param {String} image name
* @returns {Array}
*/
export const parseSastContainer = (issues = [], feedback = [], image) =>
issues.map(issue => {
const message = formatContainerScanningMessage(issue);
/*
The following fields are copying the backend data structure, as can be found in:
https://gitlab.com/gitlab-org/gitlab-ee/blob/f8f5724bb47712df0a618ae0a447b69a6ef47c0c/ee/lib/gitlab/ci/parsers/security/container_scanning.rb#L42-72
*/
const parsed = {
category: 'container_scanning',
message,
description: formatContainerScanningDescription(issue),
cve: issue.vulnerability,
severity: parseContainerScanningSeverity(issue.severity),
confidence: SEVERITY_LEVELS.medium,
location: {
image,
operating_system: issue.namespace,
},
scanner: { id: 'clair', name: 'Clair' },
identifiers: [
{
type: 'CVE',
name: issue.vulnerability,
value: issue.vulnerability,
url: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
},
],
};
const solution = formatContainerScanningSolution(issue);
if (solution) {
parsed.solution = solution;
}
if (issue.featurename) {
const dependency = {
package: {
name: issue.featurename,
},
};
if (issue.featureversion) {
dependency.version = issue.featureversion;
}
parsed.location.dependency = dependency;
}
if (issue.link) {
parsed.links = [{ url: issue.link }];
}
/*
The following properties are set only created in the frontend.
This is done for legacy reasons and they should be made obsolete,
before switching to the Backend implementation
*/
const frontendOnly = {
project_fingerprint: sha1(
`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`,
),
title: message,
vulnerability: issue.vulnerability,
};
return {
...parsed,
...frontendOnly,
...enrichVulnerabilityWithfeedback(frontendOnly, feedback),
};
});
---
title: Enrich container scanning with more data on the frontend
merge_request: 10526
author:
type: changed
...@@ -280,6 +280,18 @@ describe('vulnerabilities module mutations', () => { ...@@ -280,6 +280,18 @@ describe('vulnerabilities module mutations', () => {
); );
}); });
it('should set the modal className', () => {
expect(state.modal.data.className.value).toEqual(vulnerability.location.class);
});
it('should set the modal image', () => {
expect(state.modal.data.image.value).toEqual(vulnerability.location.image);
});
it('should set the modal namespace', () => {
expect(state.modal.data.namespace.value).toEqual(vulnerability.location.operating_system);
});
it('should set the modal identifiers', () => { it('should set the modal identifiers', () => {
expect(state.modal.data.identifiers.value).toEqual(vulnerability.identifiers); expect(state.modal.data.identifiers.value).toEqual(vulnerability.identifiers);
}); });
......
...@@ -104,12 +104,6 @@ describe('Report issues', () => { ...@@ -104,12 +104,6 @@ describe('Report issues', () => {
dockerReportParsed.unapproved[0].title, dockerReportParsed.unapproved[0].title,
); );
}); });
it('renders namespace', () => {
expect(vm.$el.textContent.trim()).toContain(dockerReportParsed.unapproved[0].path);
expect(vm.$el.textContent.trim()).toContain('in');
});
}); });
describe('for dast issues', () => { describe('for dast issues', () => {
......
...@@ -104,12 +104,6 @@ describe('Report issue', () => { ...@@ -104,12 +104,6 @@ describe('Report issue', () => {
dockerReportParsed.unapproved[0].title, dockerReportParsed.unapproved[0].title,
); );
}); });
it('renders namespace', () => {
expect(vm.$el.textContent.trim()).toContain(dockerReportParsed.unapproved[0].path);
expect(vm.$el.textContent.trim()).toContain('in');
});
}); });
describe('for dast issue', () => { describe('for dast issue', () => {
......
...@@ -54,15 +54,4 @@ describe('sast container issue body', () => { ...@@ -54,15 +54,4 @@ describe('sast container issue body', () => {
expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.title); expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.title);
}); });
describe('path', () => {
it('renders path', () => {
vm = mountComponent(Component, {
issue: sastContainerIssue,
status,
});
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.path);
});
});
}); });
...@@ -682,7 +682,13 @@ export const parsedDependencyScanningBaseStore = [ ...@@ -682,7 +682,13 @@ export const parsedDependencyScanningBaseStore = [
export const parsedSastContainerBaseStore = [ export const parsedSastContainerBaseStore = [
{ {
category: 'container_scanning', category: 'container_scanning',
message: 'CVE-2014-8130',
description: 'debian:8 is affected by CVE-2014-8130.', description: 'debian:8 is affected by CVE-2014-8130.',
cve: 'CVE-2014-8130',
severity: 'Low',
confidence: 'Medium',
location: { image: 'registry.example.com/example/master:1234', operating_system: 'debian:8' },
scanner: { id: 'clair', name: 'Clair' },
identifiers: [ identifiers: [
{ {
name: 'CVE-2014-8130', name: 'CVE-2014-8130',
...@@ -691,10 +697,7 @@ export const parsedSastContainerBaseStore = [ ...@@ -691,10 +697,7 @@ export const parsedSastContainerBaseStore = [
value: 'CVE-2014-8130', value: 'CVE-2014-8130',
}, },
], ],
namespace: 'debian:8',
path: 'debian:8',
project_fingerprint: '20a19f706d82cec1c04d1c9a8858e89b142d602f', project_fingerprint: '20a19f706d82cec1c04d1c9a8858e89b142d602f',
severity: 'Negligible',
title: 'CVE-2014-8130', title: 'CVE-2014-8130',
vulnerability: 'CVE-2014-8130', vulnerability: 'CVE-2014-8130',
}, },
...@@ -716,6 +719,7 @@ export const allIssuesParsed = [ ...@@ -716,6 +719,7 @@ export const allIssuesParsed = [
]; ];
export const dockerReport = { export const dockerReport = {
image: 'registry.example.com/example/master:1234',
unapproved: ['CVE-2017-12944', 'CVE-2017-16232'], unapproved: ['CVE-2017-12944', 'CVE-2017-16232'],
vulnerabilities: [ vulnerabilities: [
{ {
...@@ -737,6 +741,7 @@ export const dockerReport = { ...@@ -737,6 +741,7 @@ export const dockerReport = {
}; };
export const dockerBaseReport = { export const dockerBaseReport = {
image: 'registry.example.com/example/master:1234',
unapproved: ['CVE-2017-12944', 'CVE-2014-8130'], unapproved: ['CVE-2017-12944', 'CVE-2014-8130'],
vulnerabilities: [ vulnerabilities: [
{ {
...@@ -759,11 +764,14 @@ export const dockerBaseReport = { ...@@ -759,11 +764,14 @@ export const dockerBaseReport = {
export const dockerNewIssues = [ export const dockerNewIssues = [
{ {
vulnerability: 'CVE-2017-16232', category: 'container_scanning',
namespace: 'debian:8', message: 'CVE-2017-16232',
severity: 'Negligible', description: 'debian:8 is affected by CVE-2017-16232.',
title: 'CVE-2017-16232', cve: 'CVE-2017-16232',
path: 'debian:8', severity: 'Low',
confidence: 'Medium',
location: { image: 'registry.example.com/example/master:1234', operating_system: 'debian:8' },
scanner: { id: 'clair', name: 'Clair' },
identifiers: [ identifiers: [
{ {
type: 'CVE', type: 'CVE',
...@@ -772,19 +780,22 @@ export const dockerNewIssues = [ ...@@ -772,19 +780,22 @@ export const dockerNewIssues = [
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232', url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
}, },
], ],
category: 'container_scanning',
project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408', project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408',
description: 'debian:8 is affected by CVE-2017-16232.', title: 'CVE-2017-16232',
vulnerability: 'CVE-2017-16232',
}, },
]; ];
export const dockerOnlyHeadParsed = [ export const dockerOnlyHeadParsed = [
{ {
vulnerability: 'CVE-2017-12944', category: 'container_scanning',
namespace: 'debian:8', message: 'CVE-2017-12944',
description: 'debian:8 is affected by CVE-2017-12944.',
cve: 'CVE-2017-12944',
severity: 'Medium', severity: 'Medium',
title: 'CVE-2017-12944', confidence: 'Medium',
path: 'debian:8', location: { image: 'registry.example.com/example/master:1234', operating_system: 'debian:8' },
scanner: { id: 'clair', name: 'Clair' },
identifiers: [ identifiers: [
{ {
type: 'CVE', type: 'CVE',
...@@ -793,16 +804,19 @@ export const dockerOnlyHeadParsed = [ ...@@ -793,16 +804,19 @@ export const dockerOnlyHeadParsed = [
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944', url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944',
}, },
], ],
category: 'container_scanning',
project_fingerprint: '0693a82ef93c5e9d98c23a35ddcd8ed2cbd047d9', project_fingerprint: '0693a82ef93c5e9d98c23a35ddcd8ed2cbd047d9',
description: 'debian:8 is affected by CVE-2017-12944.', title: 'CVE-2017-12944',
vulnerability: 'CVE-2017-12944',
}, },
{ {
vulnerability: 'CVE-2017-16232', category: 'container_scanning',
namespace: 'debian:8', message: 'CVE-2017-16232',
severity: 'Negligible', description: 'debian:8 is affected by CVE-2017-16232.',
title: 'CVE-2017-16232', cve: 'CVE-2017-16232',
path: 'debian:8', severity: 'Low',
confidence: 'Medium',
location: { image: 'registry.example.com/example/master:1234', operating_system: 'debian:8' },
scanner: { id: 'clair', name: 'Clair' },
identifiers: [ identifiers: [
{ {
type: 'CVE', type: 'CVE',
...@@ -811,9 +825,9 @@ export const dockerOnlyHeadParsed = [ ...@@ -811,9 +825,9 @@ export const dockerOnlyHeadParsed = [
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232', url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
}, },
], ],
category: 'container_scanning',
project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408', project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408',
description: 'debian:8 is affected by CVE-2017-16232.', title: 'CVE-2017-16232',
vulnerability: 'CVE-2017-16232',
}, },
]; ];
......
...@@ -396,11 +396,12 @@ describe('security reports mutations', () => { ...@@ -396,11 +396,12 @@ describe('security reports mutations', () => {
title: 'Arbitrary file existence disclosure in Action Pack', title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock', path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock', urlPath: 'path/Gemfile.lock',
namespace: 'debian:8',
location: { location: {
file: 'Gemfile.lock', file: 'Gemfile.lock',
class: 'User', class: 'User',
method: 'do_something', method: 'do_something',
image: 'https://example.org/docker/example:v1.2.3',
operating_system: 'debian:8',
}, },
links: [ links: [
{ {
...@@ -434,7 +435,8 @@ describe('security reports mutations', () => { ...@@ -434,7 +435,8 @@ describe('security reports mutations', () => {
expect(stateCopy.modal.data.file.url).toEqual(issue.urlPath); expect(stateCopy.modal.data.file.url).toEqual(issue.urlPath);
expect(stateCopy.modal.data.className.value).toEqual(issue.location.class); expect(stateCopy.modal.data.className.value).toEqual(issue.location.class);
expect(stateCopy.modal.data.methodName.value).toEqual(issue.location.method); expect(stateCopy.modal.data.methodName.value).toEqual(issue.location.method);
expect(stateCopy.modal.data.namespace.value).toEqual(issue.namespace); expect(stateCopy.modal.data.namespace.value).toEqual(issue.location.operating_system);
expect(stateCopy.modal.data.image.value).toEqual(issue.location.image);
expect(stateCopy.modal.data.identifiers.value).toEqual(issue.identifiers); expect(stateCopy.modal.data.identifiers.value).toEqual(issue.identifiers);
expect(stateCopy.modal.data.severity.value).toEqual(issue.severity); expect(stateCopy.modal.data.severity.value).toEqual(issue.severity);
expect(stateCopy.modal.data.confidence.value).toEqual(issue.confidence); expect(stateCopy.modal.data.confidence.value).toEqual(issue.confidence);
...@@ -615,7 +617,6 @@ describe('security reports mutations', () => { ...@@ -615,7 +617,6 @@ describe('security reports mutations', () => {
describe('UPDATE_CONTAINER_SCANNING_ISSUE', () => { describe('UPDATE_CONTAINER_SCANNING_ISSUE', () => {
it('updates issue in the new issues list', () => { it('updates issue in the new issues list', () => {
// TODO pas dast
stateCopy.sastContainer.newIssues = dockerNewIssues; stateCopy.sastContainer.newIssues = dockerNewIssues;
stateCopy.sastContainer.resolvedIssues = []; stateCopy.sastContainer.resolvedIssues = [];
const updatedIssue = { const updatedIssue = {
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
findMatchingRemediations, findMatchingRemediations,
parseSastIssues, parseSastIssues,
parseDependencyScanningIssues, parseDependencyScanningIssues,
parseSastContainer,
parseDastIssues, parseDastIssues,
filterByKey, filterByKey,
getUnapprovedVulnerabilities, getUnapprovedVulnerabilities,
...@@ -12,6 +11,14 @@ import { ...@@ -12,6 +11,14 @@ import {
statusIcon, statusIcon,
countIssues, countIssues,
} from 'ee/vue_shared/security_reports/store/utils'; } from 'ee/vue_shared/security_reports/store/utils';
import {
formatContainerScanningDescription,
formatContainerScanningMessage,
formatContainerScanningSolution,
parseContainerScanningSeverity,
parseSastContainer,
} from 'ee/vue_shared/security_reports/store/utils/container_scanning';
import { SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import { import {
oldSastIssues, oldSastIssues,
sastIssues, sastIssues,
...@@ -226,13 +233,93 @@ describe('security reports utils', () => { ...@@ -226,13 +233,93 @@ describe('security reports utils', () => {
}); });
}); });
describe('container scanning utils', () => {
describe('formatContainerScanningSolution', () => {
it('should return false if there is no data', () => {
expect(formatContainerScanningSolution({})).toBe(null);
});
it('should return the correct sentence', () => {
expect(formatContainerScanningSolution({ fixedby: 'v9000' })).toBe('Upgrade to v9000.');
expect(
formatContainerScanningSolution({ fixedby: 'v9000', featurename: 'Dependency' }),
).toBe('Upgrade Dependency to v9000.');
expect(
formatContainerScanningSolution({
fixedby: 'v9000',
featurename: 'Dependency',
featureversion: '1.0-beta',
}),
).toBe('Upgrade Dependency from 1.0-beta to v9000.');
});
});
describe('formatContainerScanningMessage', () => {
it('should return concatenated message if vulnerability and featurename are provided', () => {
expect(
formatContainerScanningMessage({ vulnerability: 'CVE-124', featurename: 'grep' }),
).toBe('CVE-124 in grep');
});
it('should return vulnerability if only that is provided', () => {
expect(formatContainerScanningMessage({ vulnerability: 'Foo' })).toBe('Foo');
});
});
describe('formatContainerScanningDescription', () => {
it('should return description', () => {
expect(formatContainerScanningDescription({ description: 'Foobar' })).toBe('Foobar');
});
it('should build description from available fields', () => {
const featurename = 'Dependency';
const featureversion = '1.0';
const namespace = 'debian:8';
const vulnerability = 'CVE-123';
expect(
formatContainerScanningDescription({
featurename,
featureversion,
namespace,
vulnerability,
}),
).toBe('Dependency:1.0 is affected by CVE-123.');
expect(formatContainerScanningDescription({ featurename, namespace, vulnerability })).toBe(
'Dependency is affected by CVE-123.',
);
expect(formatContainerScanningDescription({ namespace, vulnerability })).toBe(
'debian:8 is affected by CVE-123.',
);
});
});
describe('parseContainerScanningSeverity', () => {
it('should return `Critical` for `Defcon1`', () => {
expect(parseContainerScanningSeverity('Defcon1')).toBe(SEVERITY_LEVELS.critical);
});
it('should return `Low` for `Negligible`', () => {
expect(parseContainerScanningSeverity('Negligible')).toBe('Low');
});
it('should not touch other severities', () => {
expect(parseContainerScanningSeverity('oxofrmbl')).toBe('oxofrmbl');
expect(parseContainerScanningSeverity('Medium')).toBe('Medium');
expect(parseContainerScanningSeverity('High')).toBe('High');
});
});
});
describe('parseSastContainer', () => { describe('parseSastContainer', () => {
it('parses sast container issues', () => { it('parses sast container issues', () => {
const parsed = parseSastContainer(dockerReport.vulnerabilities)[0]; const parsed = parseSastContainer(dockerReport.vulnerabilities)[0];
const issue = dockerReport.vulnerabilities[0]; const issue = dockerReport.vulnerabilities[0];
expect(parsed.title).toEqual(issue.vulnerability); expect(parsed.title).toEqual(issue.vulnerability);
expect(parsed.path).toEqual(issue.namespace);
expect(parsed.identifiers).toEqual([ expect(parsed.identifiers).toEqual([
{ {
type: 'CVE', type: 'CVE',
......
...@@ -11756,12 +11756,18 @@ msgstr "" ...@@ -11756,12 +11756,18 @@ msgstr ""
msgid "Vulnerability|Identifiers" msgid "Vulnerability|Identifiers"
msgstr "" msgstr ""
msgid "Vulnerability|Image"
msgstr ""
msgid "Vulnerability|Instances" msgid "Vulnerability|Instances"
msgstr "" msgstr ""
msgid "Vulnerability|Links" msgid "Vulnerability|Links"
msgstr "" msgstr ""
msgid "Vulnerability|Namespace"
msgstr ""
msgid "Vulnerability|Project" msgid "Vulnerability|Project"
msgstr "" msgstr ""
...@@ -12416,6 +12422,9 @@ msgstr "" ...@@ -12416,6 +12422,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error" msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr "" msgstr ""
msgid "ciReport|%{vulnerability} in %{featurename}"
msgstr ""
msgid "ciReport|(errors when loading results)" msgid "ciReport|(errors when loading results)"
msgstr "" msgstr ""
...@@ -12503,6 +12512,9 @@ msgstr "" ...@@ -12503,6 +12512,9 @@ msgstr ""
msgid "ciReport|Identifiers" msgid "ciReport|Identifiers"
msgstr "" msgstr ""
msgid "ciReport|Image"
msgstr ""
msgid "ciReport|Implement this solution by creating a merge request" msgid "ciReport|Implement this solution by creating a merge request"
msgstr "" msgstr ""
...@@ -12603,6 +12615,12 @@ msgstr "" ...@@ -12603,6 +12615,12 @@ msgstr ""
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}." msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "" msgstr ""
msgid "ciReport|Upgrade %{name} to %{fixed}."
msgstr ""
msgid "ciReport|Upgrade to %{fixed}."
msgstr ""
msgid "ciReport|Used by %{packagesString}" msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}" msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] "" msgstr[0] ""
......
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