Skip to content

Commit da9fe03

Browse files
lsongsusexingzhang-suse
authored andcommitted
Feat: Only show the vulnerability meta data on Vulnerability details page (github issue #155)
1 parent b4eec23 commit da9fe03

File tree

6 files changed

+259
-140
lines changed

6 files changed

+259
-140
lines changed

pkg/sbombastic-image-vulnerability-scanner/components/CveDetails.vue

Lines changed: 201 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default {
3131
PRODUCT_NAME,
3232
RESOURCE,
3333
PAGE,
34-
cveDetail: cveDetail,
34+
cveDetail: null,
3535
rows: images,
3636
images_table: VULNERABILITIES_DETAIL_IMAGE_LIST_TABLE,
3737
group_by_repository_table: VULNERABILITIES_DETAIL_GROUP_BY_REPOSITORY_TABLE,
@@ -49,12 +49,31 @@ export default {
4949
}
5050
},
5151
52-
async fetch() {
53-
this.preprocessedDataset = this.preprocessData(this.rows);
54-
console.log("this.preprocessedDataset", this.preprocessedDataset);
55-
},
5652
5753
methods: {
54+
async loadData() {
55+
this.preprocessedDataset = this.preprocessData(this.rows);
56+
console.log("this.preprocessedDataset", this.preprocessedDataset);
57+
58+
const vulReport = await this.$store.dispatch('cluster/findAll', {type: "storage.sbombastic.rancher.io.vulnerabilityreport"})
59+
60+
const cveId = this.$route.params.id;
61+
62+
this.affectedImages = [];
63+
64+
const {cveMetaData, affectedImages, totalScanned } = this.getCveMetaData(vulReport, cveId);
65+
66+
this.affectedImages = affectedImages;
67+
this.totalScanned = totalScanned;
68+
69+
this.cveDetail = {
70+
...cveMetaData,
71+
id: cveId,
72+
affectedImages: affectedImages.length,
73+
totalImages: totalScanned,
74+
};
75+
},
76+
5877
updateData(event) {
5978
console.log("isGrouped", this.isGrouped);
6079
},
@@ -89,7 +108,6 @@ export default {
89108
},
90109
showVendorPopup(vendor) {
91110
this.selectedVendor = vendor;
92-
this.showPopup = true;
93111
},
94112
95113
getSeverityColor(severity) {
@@ -104,7 +122,125 @@ export default {
104122
return colors[severity] || colors.none;
105123
},
106124
125+
getCveMetaData(vulReports, cveId) {
126+
let affectedImages = [];
127+
let cveMetaData = null;
128+
129+
vulReports.forEach(report => {
130+
const { imageMetadata, report: { results = [] } } = report;
131+
132+
results.forEach( result => {
133+
(result.vulnerabilities || []).forEach( vuln => {
134+
if (vuln.cve === cveId) {
135+
if(!cveMetaData) {
136+
cveMetaData = {
137+
score: this.getNvdV3Score(vuln.cvss),
138+
sources: this.convertCvssToSources(vuln.cvss),
139+
severity: vuln.severity,
140+
cvssScores: this.convertCvss(vuln.cvss),
141+
description: vuln.title,
142+
title: vuln.title,
143+
advisoryVendors: this.groupReferencesByDomain(vuln.references),
144+
}
145+
}
146+
affectedImages.push({
147+
image: `${imageMetadata.registryURI}/${imageMetadata.repository}:${imageMetadata.tag}`,
148+
repository: imageMetadata.repository,
149+
registry: imageMetadata.registry,
150+
packageName: vuln.packageName,
151+
packageVersion: vuln.installedVersion,
152+
fixedVersions: vuln.fixedVersions,
153+
severity: vuln.severity,
154+
path: result.target,
155+
cve: vuln.cve
156+
});
157+
}
158+
})
159+
})
160+
});
161+
162+
163+
const totalScanned = vulReports.length;
164+
165+
return {
166+
cve: cveId,
167+
cveMetaData,
168+
affectedImages,
169+
totalScanned
170+
};
171+
},
172+
173+
groupReferencesByDomain(urls){
174+
const vendorMap = new Map();
175+
urls.forEach(url => {
176+
const { hostname } = new URL(url);
177+
178+
//extract domain base (drop subdomains)
179+
let name = hostname.replace(/^www\./, '');
180+
const parts = name.split('.');
181+
if (parts.length > 2) {
182+
name = parts[parts.length - 2];
183+
}else{
184+
name = parts[0];
185+
}
186+
187+
//Capitalize first letter
188+
name = name.charAt(0).toUpperCase() + name.slice(1);
189+
if( !vendorMap.has(name) ){
190+
vendorMap.set( name, []);
191+
}
192+
vendorMap.get(name).push(url);
193+
});
194+
return Array.from(vendorMap.entries()).map(
195+
([name, references]) => ({
196+
name,
197+
references,
198+
})
199+
);
200+
},
201+
202+
convertCvss(cvssObj) {
203+
const cvssScores = [];
204+
Object.entries(cvssObj).forEach(([source, values]) => {
205+
Object.entries(values).forEach(([key, val]) => {
206+
if (key.toLowerCase().includes("score")) {
207+
cvssScores.push({
208+
source: `${source.charAt(0).toUpperCase() + source.slice(1)} ${key}`,
209+
score: val
210+
});
211+
}
212+
})
213+
});
214+
215+
return cvssScores;
216+
},
217+
218+
getNvdV3Score(cvss) {
219+
if (cvss.nvd && cvss.nvd.v3score) {
220+
return `${cvss.nvd.v3score} (v3)`;
221+
}
222+
return "N/A";
223+
},
224+
225+
convertCvssToSources(cvss){
226+
return Object.keys(cvss).map(key => {
227+
return {
228+
name: key.toUpperCase(),
229+
link: ""
230+
};
231+
});
232+
}
233+
107234
},
235+
236+
watch: {
237+
'$route.params.id': {
238+
immediate: true,
239+
handler() {
240+
this.loadData();
241+
}
242+
}
243+
}
108244
}
109245
</script>
110246
<template>
@@ -117,8 +253,8 @@ export default {
117253
{{ $route.params.id }}
118254
</span>
119255
<BadgeState
120-
:color="getSeverityColor(cveDetail.severity)"
121-
:label="t(`imageScanner.enum.cve.${cveDetail.severity}`)"
256+
:color="getSeverityColor(cveDetail.severity.toLowerCase())"
257+
:label="t(`imageScanner.enum.cve.${cveDetail.severity.toLowerCase()}`)"
122258
class="severity-badge"
123259
/>
124260
</div>
@@ -205,8 +341,8 @@ export default {
205341
:key="rIndex"
206342
class="ref-item"
207343
>
208-
<a :href="ref.url" target="_blank" class="ref-url">{{ ref.url }}</a>
209-
<div class="ref-title">{{ ref.title }}</div>
344+
<a :href="ref" target="_blank" class="ref-url">{{ ref }}</a>
345+
<!-- <div class="ref-title">{{ ref.title }}</div>-->
210346
</div>
211347
</div>
212348
</div>
@@ -224,59 +360,59 @@ export default {
224360
</div>
225361
</div>
226362
</div>
227-
<div class="table">
228-
<SortableTable
229-
:has-advanced-filtering="false"
230-
:namespaced="false"
231-
:row-actions="false"
232-
:table-actions="true"
233-
:force-update-live-and-delayed="0"
234-
:use-query-params-for-simple-filtering="true"
235-
:sub-expandable="isGrouped"
236-
:sub-rows="isGrouped"
237-
:sub-expand-column="isGrouped"
238-
:rows="isGrouped ? preprocessedDataset.rowsByRepo : rows"
239-
:headers="isGrouped ? group_by_repository_table : images_table"
240-
:key-field="'id'"
241-
@selection="onSelectionChange"
242-
>
243-
<template #header-left>
244-
<div class="table-top-left">
245-
<DownloadCustomReport
246-
class="table-btn"
247-
:selectedRows="selectedRows"
248-
:buttonName="t('imageScanner.images.buttons.downloadCustomReport')"
249-
/>
250-
</div>
251-
</template>
252-
<template #header-right>
253-
<div class="table-top-right">
254-
<Checkbox
255-
style="margin-top: 8px; width: 180px;"
256-
label-key="imageScanner.images.listTable.checkbox.groupByRepo"
257-
v-model:value="isGrouped"
258-
@update:value="updateData($event)"
259-
/>
260-
</div>
261-
</template>
262-
<template v-if="isGrouped" #sub-row="{ row, fullColspan }">
263-
<tr
264-
class="sub-row"
265-
>
266-
<td :colspan="fullColspan">
267-
<SortableTable
268-
class="sub-table"
269-
:rows="row.images"
270-
:headers="sub_images_table"
271-
:search="false"
272-
:row-actions="false"
273-
:table-actions="false"
274-
/>
275-
</td>
276-
</tr>
277-
</template>
278-
</SortableTable>
279-
</div>
363+
<!-- <div class="table">-->
364+
<!-- <SortableTable-->
365+
<!-- :has-advanced-filtering="false"-->
366+
<!-- :namespaced="false"-->
367+
<!-- :row-actions="false"-->
368+
<!-- :table-actions="true"-->
369+
<!-- :force-update-live-and-delayed="0"-->
370+
<!-- :use-query-params-for-simple-filtering="true"-->
371+
<!-- :sub-expandable="isGrouped"-->
372+
<!-- :sub-rows="isGrouped"-->
373+
<!-- :sub-expand-column="isGrouped"-->
374+
<!-- :rows="isGrouped ? preprocessedDataset.rowsByRepo : rows"-->
375+
<!-- :headers="isGrouped ? group_by_repository_table : images_table"-->
376+
<!-- :key-field="'id'"-->
377+
<!-- @selection="onSelectionChange"-->
378+
<!-- >-->
379+
<!-- <template #header-left>-->
380+
<!-- <div class="table-top-left">-->
381+
<!-- <DownloadCustomReport-->
382+
<!-- class="table-btn"-->
383+
<!-- :selectedRows="selectedRows"-->
384+
<!-- :buttonName="t('imageScanner.images.buttons.downloadCustomReport')"-->
385+
<!-- />-->
386+
<!-- </div>-->
387+
<!-- </template>-->
388+
<!-- <template #header-right>-->
389+
<!-- <div class="table-top-right">-->
390+
<!-- <Checkbox-->
391+
<!-- style="margin-top: 8px; width: 180px;"-->
392+
<!-- label-key="imageScanner.images.listTable.checkbox.groupByRepo"-->
393+
<!-- v-model:value="isGrouped"-->
394+
<!-- @update:value="updateData($event)"-->
395+
<!-- />-->
396+
<!-- </div>-->
397+
<!-- </template>-->
398+
<!-- <template v-if="isGrouped" #sub-row="{ row, fullColspan }">-->
399+
<!-- <tr-->
400+
<!-- class="sub-row"-->
401+
<!-- >-->
402+
<!-- <td :colspan="fullColspan">-->
403+
<!-- <SortableTable-->
404+
<!-- class="sub-table"-->
405+
<!-- :rows="row.images"-->
406+
<!-- :headers="sub_images_table"-->
407+
<!-- :search="false"-->
408+
<!-- :row-actions="false"-->
409+
<!-- :table-actions="false"-->
410+
<!-- />-->
411+
<!-- </td>-->
412+
<!-- </tr>-->
413+
<!-- </template>-->
414+
<!-- </SortableTable>-->
415+
<!-- </div>-->
280416
</div>
281417
</template>
282418

@@ -335,10 +471,10 @@ export default {
335471
.description {
336472
display: flex;
337473
max-width: 900px;
338-
height: 21px;
474+
max-height: calc(21px * 3);
339475
flex-direction: column;
340476
justify-content: center;
341-
overflow: hidden;
477+
overflow-y: auto;
342478
color: #717179;
343479
font-family: Lato;
344480
font-size: 14px;

pkg/sbombastic-image-vulnerability-scanner/components/ImageDetails.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ export default {
424424
getMockVulnerabilityDetails() {
425425
return [
426426
{
427-
cveId: 'CVE-2017-5337',
427+
cveId: 'CVE-2018-25032',
428428
score: '9.9 (v3)',
429429
package: 'tomcat-embed-jasper-9.1',
430430
fixAvailable: true,

pkg/sbombastic-image-vulnerability-scanner/constants/scan-interval-options.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@ export const SCAN_INTERVAL_OPTIONS = [
1717
];
1818

1919
export const REGISTRY_TYPE = {
20-
DOCKERHUB: 'dockerhub'
21-
}
22-
23-
export const REGISTRY_DEFAULT_URI = {
24-
DOCKERHUB: "index.docker.io"
20+
DOCKERHUB: 'dockerhub',
21+
NO_CATALOG: 'noCatalog'
2522
};
2623

2724
export const REGISTRY_TYPE_OPTIONS: {label: string; value: string }[] = [
28-
{ label: 'Docker Hub', value: REGISTRY_TYPE.DOCKERHUB }
25+
{ label: 'Docker Hub', value: REGISTRY_TYPE.DOCKERHUB },
26+
{ label: 'No Catalog', value: REGISTRY_TYPE.NO_CATALOG }
2927
];

0 commit comments

Comments
 (0)