diff --git a/package.json b/package.json index f660abb..d15581b 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "@rancher/components": "^0.3.0-alpha.1", "@rancher/shell": "3.0.5-rc.8", "file-saver": "^2.0.5", + "flat": "^6.0.1", + "papaparse": "^5.5.3", "vue": "^3.5.17", "vue-router": "^4.5.0", "vuex": "^4.1.0" diff --git a/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml b/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml index 86f94bf..839c4ef 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml +++ b/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml @@ -275,6 +275,8 @@ imageScanner: medium: Medium low: Low none: None + unknown: Unknown + suppressed: Suppressed status: pending: Pending scheduled: Scheduled diff --git a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue index 4884661..7b0a4ac 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue @@ -152,6 +152,8 @@ import { Checkbox } from '@components/Form/Checkbox'; import { RESOURCE } from "@pkg/types"; import { saveAs } from 'file-saver'; + import Papa from 'papaparse'; + import { flatten } from 'flat'; export default { name: 'imageOverview', @@ -189,6 +191,36 @@ label: this.t('imageScanner.images.filters.image.includeBaseImages') } ]; + const severityOptions = [ + { + value: "any", + label: this.t('imageScanner.imageDetails.any') + }, + { + value: "critical", + label: this.t('imageScanner.enum.cve.critical') + }, + { + value: "high", + label: this.t('imageScanner.enum.cve.high') + }, + { + value: "medium", + label: this.t('imageScanner.enum.cve.medium') + }, + { + value: "low", + label: this.t('imageScanner.enum.cve.low') + }, + { + value: "unknown", + label: this.t('imageScanner.enum.cve.unknown') + }, + { + value: "suppressed", + label: this.t('imageScanner.enum.cve.suppressed') + } + ] return { rows: [], REPO_BASED_TABLE: REPO_BASED_TABLE, @@ -200,14 +232,16 @@ preprocessedImagesBak: [], filterCveOptions, filterImageOptions, + severityOptions, selectedCveFilter: filterCveOptions[0], selectedImageFilter: filterImageOptions[0], filters: { imageSearch: '', - severitySearch: 'any', - repositorySearch: 'any', - registrySearch: 'any', + severitySearch: severityOptions[0], + repositorySearch: 'Any', + registrySearch: 'Any', }, + registryCrds: [], } }, computed: { @@ -228,7 +262,9 @@ icon: 'icon-download', enabled: true, bulkable: false, - invoke: async ({}, res = []) => {} + invoke: (_, res) => { + this.downloadCsv(res); + } }; const downloadJson = { action: 'downloadJson', @@ -236,7 +272,9 @@ icon: 'icon-download', enabled: true, bulkable: false, - invoke: async ({}, res = []) => {} + invoke: (_, res) => { + this.downloadJson(res); + } }; return [ downloadSbom, @@ -244,25 +282,49 @@ downloadCsv, downloadJson ]; + }, + repositoryOptions() { + const repoSet = new Set(); + this.registryCrds.forEach(reg => { + if (reg.spec.repositories && reg.spec.repositories.length) { + reg.spec.repositories.forEach(repo => repoSet.add(repo)); + } + }); + return ["Any", ...repoSet]; + }, + registryOptions() { + return ["Any", ...this.registryCrds.map(reg => `${reg.metadata.namespace}/${reg.metadata.name}`)]; } }, async fetch() { - await this.$store.dispatch('cluster/findAll', { type: RESOURCE.VULNERABILITY_REPORT }); - const vulReportCRD = this.$store.getters['cluster/all']?.(RESOURCE.VULNERABILITY_REPORT); + const vulReportCRD = await this.$store.dispatch('cluster/findAll', { type: RESOURCE.VULNERABILITY_REPORT }); this.preprocessData(vulReportCRD); this.preprocessedDataset = this.preprocessData(vulReportCRD); this.preprocessedImagesBak = _.cloneDeep(this.preprocessedDataset.preprocessedImages); console.log("this.preprocessedDataset", this.preprocessedDataset) + this.registryCrds = await this.$store.dispatch('cluster/findAll', { type: RESOURCE.REGISTRY }); }, methods: { async downloadSbom(res) { const target = (res && res.length ? res[0] : null); - console.log("target", target); const sbom = await this.$store.dispatch('cluster/find', { type: RESOURCE.SBOM, id: target.id }); const spdxString = JSON.stringify(sbom.spdx, null, 2); const sbomBlob = new Blob([spdxString], { type: 'application/json;charset=utf-8' }); saveAs(sbomBlob, `${sbom.metadata.name}-sbom.spdx.json`); }, + async downloadJson(res) { + const target = (res && res.length ? res[0] : null); + const vulReport = await this.$store.dispatch('cluster/find', { type: RESOURCE.VULNERABILITY_REPORT, id: target.id }); + const jsonBlob = new Blob([JSON.stringify(vulReport, null, 2)], { type: 'application/json;charset=utf-8' }); + saveAs(jsonBlob, `${target.id}-vul-report.json`); + }, + async downloadCsv(res) { + const target = (res && res.length ? res[0] : null); + const vulReport = await this.$store.dispatch('cluster/find', { type: RESOURCE.VULNERABILITY_REPORT, id: target.id }); + const csvString = JSON.stringify(vulReport.report.results[0].vulnerabilities.map(vul => flatten(vul))); + const csvBlob = new Blob([Papa.unparse(csvString)], { type: 'text/csv;charset=utf-8' }); + saveAs(csvBlob, `${target.id}-image-detail-report.csv`); + }, applyFilters() { let filtered = _.cloneDeep(this.preprocessedImagesBak); if (this.selectedImageFilter.value === "excludeBaseImages" || this.selectedImageFilter === "excludeBaseImages") { diff --git a/yarn.lock b/yarn.lock index b4a557e..d168d77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8433,6 +8433,11 @@ flat@^5.0.2: resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +flat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/flat/-/flat-6.0.1.tgz#09070cf918293b401577f20843edeadf4d3e8755" + integrity sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw== + flatted@^3.2.9: version "3.3.3" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" @@ -12089,6 +12094,11 @@ papaparse@^5.2.0: resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.5.2.tgz#fb67cc5a03ba8930cb435dc4641a25d6804bd4d7" integrity sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA== +papaparse@^5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.5.3.tgz#07f8994dec516c6dab266e952bed68e1de59fa9a" + integrity sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A== + param-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"