Commit 7a677fe8 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '5528-container-scanning-fe' into 'master'

Enrich container scanning with more data on the frontend

Closes #5528

See merge request gitlab-org/gitlab-ee!10526
parents e699c439 e7507134
...@@ -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