diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js
index 5cc4056a9d49be435674bffc529b0e64c71ee63e..791972fb247bd28d7c2cd0542cac6eaf102f1191 100644
--- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js
+++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/mutations.js
@@ -68,6 +68,7 @@ export default {
},
[types.SET_MODAL_DATA](state, payload) {
const { vulnerability } = payload;
+ const { location } = vulnerability;
Vue.set(state.modal, 'title', vulnerability.name);
Vue.set(state.modal.data.description, 'value', vulnerability.description);
@@ -81,22 +82,38 @@ export default {
'url',
vulnerability.project && vulnerability.project.full_path,
);
- Vue.set(
- state.modal.data.file,
- 'value',
- vulnerability.location &&
- `${vulnerability.location.file}:${vulnerability.location.start_line}`,
- );
+
Vue.set(
state.modal.data.identifiers,
'value',
vulnerability.identifiers.length && vulnerability.identifiers,
);
- Vue.set(
- state.modal.data.className,
- 'value',
- vulnerability.location && vulnerability.location.class,
- );
+
+ if (location) {
+ const {
+ 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.reportType, 'value', vulnerability.report_type);
Vue.set(state.modal.data.confidence, 'value', vulnerability.confidence);
diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js
index 2f93a3b015fab5bf45c3f5b4b159c2f507fb98b6..f8e90bd0795ec6f189fa4ef4e7ed19561c0f03fb 100644
--- a/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js
+++ b/ee/app/assets/javascripts/security_dashboard/store/modules/vulnerabilities/state.js
@@ -28,9 +28,11 @@ export default () => ({
file: { text: s__('Vulnerability|File') },
identifiers: { text: s__('Vulnerability|Identifiers') },
severity: { text: s__('Vulnerability|Severity') },
- reportType: { text: s__('Vulnerability|Report Type') },
confidence: { text: s__('Vulnerability|Confidence') },
+ reportType: { text: s__('Vulnerability|Report Type') },
className: { text: s__('Vulnerability|Class') },
+ image: { text: s__('Vulnerability|Image') },
+ namespace: { text: s__('Vulnerability|Namespace') },
links: { text: s__('Vulnerability|Links') },
instances: { text: s__('Vulnerability|Instances') },
},
diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/components/sast_container_issue_body.vue b/ee/app/assets/javascripts/vue_shared/security_reports/components/sast_container_issue_body.vue
index 7b8554395924b26e0136680ea45f84db5740f8c2..abcf0b2a4b70863f374057106a2cbebf853e6a5f 100644
--- a/ee/app/assets/javascripts/vue_shared/security_reports/components/sast_container_issue_body.vue
+++ b/ee/app/assets/javascripts/vue_shared/security_reports/components/sast_container_issue_body.vue
@@ -34,7 +34,5 @@ export default {
-
-
diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/store/mutations.js b/ee/app/assets/javascripts/vue_shared/security_reports/store/mutations.js
index 227be687d0b760affc11c206f7667a025fb18031..75eaf2151f0f1a06120d89849e88d2b281766609 100644
--- a/ee/app/assets/javascripts/vue_shared/security_reports/store/mutations.js
+++ b/ee/app/assets/javascripts/vue_shared/security_reports/store/mutations.js
@@ -4,11 +4,11 @@ import {
parseSastIssues,
parseDependencyScanningIssues,
filterByKey,
- parseSastContainer,
parseDastIssues,
getUnapprovedVulnerabilities,
findIssueIndex,
} from './utils';
+import { parseSastContainer } from './utils/container_scanning';
import { visitUrl } from '~/lib/utils/url_utility';
export default {
@@ -118,11 +118,11 @@ export default {
[types.RECEIVE_SAST_CONTAINER_REPORTS](state, reports) {
if (reports.base && reports.head) {
const headIssues = getUnapprovedVulnerabilities(
- parseSastContainer(reports.head.vulnerabilities, reports.enrichData),
+ parseSastContainer(reports.head.vulnerabilities, reports.enrichData, reports.head.image),
reports.head.unapproved,
);
const baseIssues = getUnapprovedVulnerabilities(
- parseSastContainer(reports.base.vulnerabilities, reports.enrichData),
+ parseSastContainer(reports.base.vulnerabilities, reports.enrichData, reports.base.image),
reports.base.unapproved,
);
const filterKey = 'vulnerability';
@@ -135,7 +135,7 @@ export default {
Vue.set(state.sastContainer, 'isLoading', false);
} else if (reports.head && !reports.base) {
const newIssues = getUnapprovedVulnerabilities(
- parseSastContainer(reports.head.vulnerabilities, reports.enrichData),
+ parseSastContainer(reports.head.vulnerabilities, reports.enrichData, reports.head.image),
reports.head.unapproved,
);
@@ -265,7 +265,8 @@ export default {
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.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) {
Vue.set(state.modal.data.identifiers, 'value', issue.identifiers);
diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/store/state.js b/ee/app/assets/javascripts/vue_shared/security_reports/store/state.js
index 873f7611dd804512db8937936354c2e6a11fd758..3e5c08f2183b5f5e18b478add1038a5f9aa3387d 100644
--- a/ee/app/assets/javascripts/vue_shared/security_reports/store/state.js
+++ b/ee/app/assets/javascripts/vue_shared/security_reports/store/state.js
@@ -75,16 +75,26 @@ export default () => ({
text: s__('ciReport|Description'),
isLink: false,
},
+ file: {
+ value: null,
+ url: null,
+ text: s__('ciReport|File'),
+ isLink: true,
+ },
identifiers: {
value: [],
text: s__('ciReport|Identifiers'),
isLink: false,
},
- file: {
+ severity: {
value: null,
- url: null,
- text: s__('ciReport|File'),
- isLink: true,
+ text: s__('ciReport|Severity'),
+ isLink: false,
+ },
+ confidence: {
+ value: null,
+ text: s__('ciReport|Confidence'),
+ isLink: false,
},
className: {
value: null,
@@ -96,19 +106,14 @@ export default () => ({
text: s__('ciReport|Method'),
isLink: false,
},
- namespace: {
- value: null,
- text: s__('ciReport|Namespace'),
- isLink: false,
- },
- severity: {
+ image: {
value: null,
- text: s__('ciReport|Severity'),
+ text: s__('ciReport|Image'),
isLink: false,
},
- confidence: {
+ namespace: {
value: null,
- text: s__('ciReport|Confidence'),
+ text: s__('ciReport|Namespace'),
isLink: false,
},
links: {
diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/store/utils.js b/ee/app/assets/javascripts/vue_shared/security_reports/store/utils.js
index 1d942fa3fda3c5f70e1d05d90cc085b659d8c8ac..46ca8f8c89ce077d6a60f437d04375c40c7ddeac 100644
--- a/ee/app/assets/javascripts/vue_shared/security_reports/store/utils.js
+++ b/ee/app/assets/javascripts/vue_shared/security_reports/store/utils.js
@@ -43,8 +43,8 @@ export const findMatchingRemediations = (remediations, vulnerability) => {
* @param {Object} vulnerability
* @param {Array} feedback
*/
-function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) {
- return feedback
+export const enrichVulnerabilityWithfeedback = (vulnerability, feedback = []) =>
+ feedback
.filter(fb => fb.project_fingerprint === vulnerability.project_fingerprint)
.reduce((vuln, fb) => {
if (fb.feedback_type === 'dismissal') {
@@ -68,7 +68,6 @@ function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) {
}
return vuln;
}, vulnerability);
-}
/**
* Generates url to repository file and highlight section between start and end lines.
@@ -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.
* DAST report is currently the straigh output from the underlying tool (ZAProxy)
diff --git a/ee/app/assets/javascripts/vue_shared/security_reports/store/utils/container_scanning.js b/ee/app/assets/javascripts/vue_shared/security_reports/store/utils/container_scanning.js
new file mode 100644
index 0000000000000000000000000000000000000000..e8376ef301bb51c48dac2f9c3ef6f8cc82cd772b
--- /dev/null
+++ b/ee/app/assets/javascripts/vue_shared/security_reports/store/utils/container_scanning.js
@@ -0,0 +1,168 @@
+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),
+ };
+ });
diff --git a/ee/changelogs/unreleased/5528-container-scanning-fe.yml b/ee/changelogs/unreleased/5528-container-scanning-fe.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f802abd1d63fab5ecc09ccf380a0deb4044779eb
--- /dev/null
+++ b/ee/changelogs/unreleased/5528-container-scanning-fe.yml
@@ -0,0 +1,5 @@
+---
+title: Enrich container scanning with more data on the frontend
+merge_request: 10526
+author:
+type: changed
diff --git a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js
index 3d4dbdeb732a6c95303f7e1d91fa81fa0fa279aa..1cba29f977ab8b69ebc9398a3e7e8ed2fa08055a 100644
--- a/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js
+++ b/ee/spec/javascripts/security_dashboard/store/vulnerabilities/mutations_spec.js
@@ -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', () => {
expect(state.modal.data.identifiers.value).toEqual(vulnerability.identifiers);
});
diff --git a/ee/spec/javascripts/vue_shared/components/reports/report_issues_spec.js b/ee/spec/javascripts/vue_shared/components/reports/report_issues_spec.js
index 30931023ea057e6bf7d07703f63565ad38eaa947..1dac472a5945e38a2082c36b93011745daaaa7f9 100644
--- a/ee/spec/javascripts/vue_shared/components/reports/report_issues_spec.js
+++ b/ee/spec/javascripts/vue_shared/components/reports/report_issues_spec.js
@@ -104,12 +104,6 @@ describe('Report issues', () => {
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', () => {
diff --git a/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js b/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js
index cd05bd968f31aa1aa74535937225e1c9a6c46ac7..fd0c0b5b322086a56d7177ece5594306821dc7f6 100644
--- a/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js
+++ b/ee/spec/javascripts/vue_shared/components/reports/report_item_spec.js
@@ -104,12 +104,6 @@ describe('Report issue', () => {
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', () => {
diff --git a/ee/spec/javascripts/vue_shared/security_reports/components/sast_container_issue_body_spec.js b/ee/spec/javascripts/vue_shared/security_reports/components/sast_container_issue_body_spec.js
index 72731e3c4d00b89b5e789cf079f572c7c9e6b04e..adee93a8b3737a977957ce9f4998d22e3a372d59 100644
--- a/ee/spec/javascripts/vue_shared/security_reports/components/sast_container_issue_body_spec.js
+++ b/ee/spec/javascripts/vue_shared/security_reports/components/sast_container_issue_body_spec.js
@@ -54,15 +54,4 @@ describe('sast container issue body', () => {
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);
- });
- });
});
diff --git a/ee/spec/javascripts/vue_shared/security_reports/mock_data.js b/ee/spec/javascripts/vue_shared/security_reports/mock_data.js
index 81645ee158f3107dbca5908d55d616f0054f567f..a03c927b7dadfdf5cbf6d79e977bb2c8db44d822 100644
--- a/ee/spec/javascripts/vue_shared/security_reports/mock_data.js
+++ b/ee/spec/javascripts/vue_shared/security_reports/mock_data.js
@@ -682,7 +682,13 @@ export const parsedDependencyScanningBaseStore = [
export const parsedSastContainerBaseStore = [
{
category: 'container_scanning',
+ message: '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: [
{
name: 'CVE-2014-8130',
@@ -691,10 +697,7 @@ export const parsedSastContainerBaseStore = [
value: 'CVE-2014-8130',
},
],
- namespace: 'debian:8',
- path: 'debian:8',
project_fingerprint: '20a19f706d82cec1c04d1c9a8858e89b142d602f',
- severity: 'Negligible',
title: 'CVE-2014-8130',
vulnerability: 'CVE-2014-8130',
},
@@ -716,6 +719,7 @@ export const allIssuesParsed = [
];
export const dockerReport = {
+ image: 'registry.example.com/example/master:1234',
unapproved: ['CVE-2017-12944', 'CVE-2017-16232'],
vulnerabilities: [
{
@@ -737,6 +741,7 @@ export const dockerReport = {
};
export const dockerBaseReport = {
+ image: 'registry.example.com/example/master:1234',
unapproved: ['CVE-2017-12944', 'CVE-2014-8130'],
vulnerabilities: [
{
@@ -759,11 +764,14 @@ export const dockerBaseReport = {
export const dockerNewIssues = [
{
- vulnerability: 'CVE-2017-16232',
- namespace: 'debian:8',
- severity: 'Negligible',
- title: 'CVE-2017-16232',
- path: 'debian:8',
+ category: 'container_scanning',
+ message: 'CVE-2017-16232',
+ description: 'debian:8 is affected by CVE-2017-16232.',
+ cve: 'CVE-2017-16232',
+ severity: 'Low',
+ confidence: 'Medium',
+ location: { image: 'registry.example.com/example/master:1234', operating_system: 'debian:8' },
+ scanner: { id: 'clair', name: 'Clair' },
identifiers: [
{
type: 'CVE',
@@ -772,19 +780,22 @@ export const dockerNewIssues = [
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
},
],
- category: 'container_scanning',
project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408',
- description: 'debian:8 is affected by CVE-2017-16232.',
+ title: 'CVE-2017-16232',
+ vulnerability: 'CVE-2017-16232',
},
];
export const dockerOnlyHeadParsed = [
{
- vulnerability: 'CVE-2017-12944',
- namespace: 'debian:8',
+ category: 'container_scanning',
+ message: 'CVE-2017-12944',
+ description: 'debian:8 is affected by CVE-2017-12944.',
+ cve: 'CVE-2017-12944',
severity: 'Medium',
- title: 'CVE-2017-12944',
- path: 'debian:8',
+ confidence: 'Medium',
+ location: { image: 'registry.example.com/example/master:1234', operating_system: 'debian:8' },
+ scanner: { id: 'clair', name: 'Clair' },
identifiers: [
{
type: 'CVE',
@@ -793,16 +804,19 @@ export const dockerOnlyHeadParsed = [
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944',
},
],
- category: 'container_scanning',
project_fingerprint: '0693a82ef93c5e9d98c23a35ddcd8ed2cbd047d9',
- description: 'debian:8 is affected by CVE-2017-12944.',
+ title: 'CVE-2017-12944',
+ vulnerability: 'CVE-2017-12944',
},
{
- vulnerability: 'CVE-2017-16232',
- namespace: 'debian:8',
- severity: 'Negligible',
- title: 'CVE-2017-16232',
- path: 'debian:8',
+ category: 'container_scanning',
+ message: 'CVE-2017-16232',
+ description: 'debian:8 is affected by CVE-2017-16232.',
+ cve: 'CVE-2017-16232',
+ severity: 'Low',
+ confidence: 'Medium',
+ location: { image: 'registry.example.com/example/master:1234', operating_system: 'debian:8' },
+ scanner: { id: 'clair', name: 'Clair' },
identifiers: [
{
type: 'CVE',
@@ -811,9 +825,9 @@ export const dockerOnlyHeadParsed = [
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
},
],
- category: 'container_scanning',
project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408',
- description: 'debian:8 is affected by CVE-2017-16232.',
+ title: 'CVE-2017-16232',
+ vulnerability: 'CVE-2017-16232',
},
];
diff --git a/ee/spec/javascripts/vue_shared/security_reports/store/mutations_spec.js b/ee/spec/javascripts/vue_shared/security_reports/store/mutations_spec.js
index 146edd14f0140176bce15af7166083e285abaff8..15494da404e306230ef373b893e4d17f2800fbf7 100644
--- a/ee/spec/javascripts/vue_shared/security_reports/store/mutations_spec.js
+++ b/ee/spec/javascripts/vue_shared/security_reports/store/mutations_spec.js
@@ -396,11 +396,12 @@ describe('security reports mutations', () => {
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
- namespace: 'debian:8',
location: {
file: 'Gemfile.lock',
class: 'User',
method: 'do_something',
+ image: 'https://example.org/docker/example:v1.2.3',
+ operating_system: 'debian:8',
},
links: [
{
@@ -434,7 +435,8 @@ describe('security reports mutations', () => {
expect(stateCopy.modal.data.file.url).toEqual(issue.urlPath);
expect(stateCopy.modal.data.className.value).toEqual(issue.location.class);
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.severity.value).toEqual(issue.severity);
expect(stateCopy.modal.data.confidence.value).toEqual(issue.confidence);
@@ -615,7 +617,6 @@ describe('security reports mutations', () => {
describe('UPDATE_CONTAINER_SCANNING_ISSUE', () => {
it('updates issue in the new issues list', () => {
- // TODO pas dast
stateCopy.sastContainer.newIssues = dockerNewIssues;
stateCopy.sastContainer.resolvedIssues = [];
const updatedIssue = {
diff --git a/ee/spec/javascripts/vue_shared/security_reports/store/utils_spec.js b/ee/spec/javascripts/vue_shared/security_reports/store/utils_spec.js
index 28ea872ac088b9747e8ed0a01ceda3ecde071d06..c8469fd867cd83a4fad3f2c1649fd8909b2fb691 100644
--- a/ee/spec/javascripts/vue_shared/security_reports/store/utils_spec.js
+++ b/ee/spec/javascripts/vue_shared/security_reports/store/utils_spec.js
@@ -4,7 +4,6 @@ import {
findMatchingRemediations,
parseSastIssues,
parseDependencyScanningIssues,
- parseSastContainer,
parseDastIssues,
filterByKey,
getUnapprovedVulnerabilities,
@@ -12,6 +11,14 @@ import {
statusIcon,
countIssues,
} 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 {
oldSastIssues,
sastIssues,
@@ -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', () => {
it('parses sast container issues', () => {
const parsed = parseSastContainer(dockerReport.vulnerabilities)[0];
const issue = dockerReport.vulnerabilities[0];
expect(parsed.title).toEqual(issue.vulnerability);
- expect(parsed.path).toEqual(issue.namespace);
expect(parsed.identifiers).toEqual([
{
type: 'CVE',
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a1ae7bb25922d0ae0368b5f246704a13b45263a4..d0fcdd1a9af8dcc999a07f8b637e47b359750cde 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11747,12 +11747,18 @@ msgstr ""
msgid "Vulnerability|Identifiers"
msgstr ""
+msgid "Vulnerability|Image"
+msgstr ""
+
msgid "Vulnerability|Instances"
msgstr ""
msgid "Vulnerability|Links"
msgstr ""
+msgid "Vulnerability|Namespace"
+msgstr ""
+
msgid "Vulnerability|Project"
msgstr ""
@@ -12407,6 +12413,9 @@ msgstr ""
msgid "ciReport|%{reportType}: Loading resulted in an error"
msgstr ""
+msgid "ciReport|%{vulnerability} in %{featurename}"
+msgstr ""
+
msgid "ciReport|(errors when loading results)"
msgstr ""
@@ -12494,6 +12503,9 @@ msgstr ""
msgid "ciReport|Identifiers"
msgstr ""
+msgid "ciReport|Image"
+msgstr ""
+
msgid "ciReport|Implement this solution by creating a merge request"
msgstr ""
@@ -12594,6 +12606,12 @@ msgstr ""
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
+msgid "ciReport|Upgrade %{name} to %{fixed}."
+msgstr ""
+
+msgid "ciReport|Upgrade to %{fixed}."
+msgstr ""
+
msgid "ciReport|Used by %{packagesString}"
msgid_plural "ciReport|Used by %{packagesString}, and %{lastPackage}"
msgstr[0] ""