diff --git a/pkg/sbombastic-image-vulnerability-scanner/assets/img/neuvector-logo.svg b/pkg/sbombastic-image-vulnerability-scanner/assets/img/neuvector-logo.svg new file mode 100644 index 0000000..ac44612 --- /dev/null +++ b/pkg/sbombastic-image-vulnerability-scanner/assets/img/neuvector-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pkg/sbombastic-image-vulnerability-scanner/components/ImageDetails.vue b/pkg/sbombastic-image-vulnerability-scanner/components/ImageDetails.vue index a0583a7..712367a 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/components/ImageDetails.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/components/ImageDetails.vue @@ -241,10 +241,10 @@ import SortableTable from "@shell/components/SortableTable"; import { BadgeState } from '@components/BadgeState'; import Checkbox from '@components/Form/Checkbox'; -import ScoreBadge from '@sbombastic-image-vulnerability-scanner/components/common/ScoreBadge'; -import BarChart from '@sbombastic-image-vulnerability-scanner/components/common/BarChart'; -import { VULNERABILITY_DETAILS_TABLE } from "@sbombastic-image-vulnerability-scanner/config/table-headers"; -import { images } from "@sbombastic-image-vulnerability-scanner/data/sbombastic.rancher.io.image"; +import ScoreBadge from '@pkg/components/common/ScoreBadge'; +import BarChart from '@pkg/components/common/BarChart'; +import { VULNERABILITY_DETAILS_TABLE } from "@pkg/config/table-headers"; +import { images } from "@pkg/data/sbombastic.rancher.io.image"; export default { name: 'ImageDetails', diff --git a/pkg/sbombastic-image-vulnerability-scanner/components/ImageRiskAssessment.vue b/pkg/sbombastic-image-vulnerability-scanner/components/ImageRiskAssessment.vue index 1dd59f8..d373b14 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/components/ImageRiskAssessment.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/components/ImageRiskAssessment.vue @@ -4,7 +4,7 @@ {{ t('imageScanner.images.imageRiskAssessment.title') }}
- +
@@ -21,7 +21,11 @@ chartData: { type: Object, required: true - } + }, + filterFn: { + type: Function, + required: false + }, }, data() {} } diff --git a/pkg/sbombastic-image-vulnerability-scanner/components/StatusDistribution.vue b/pkg/sbombastic-image-vulnerability-scanner/components/StatusDistribution.vue index d0ac8ed..ac87f58 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/components/StatusDistribution.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/components/StatusDistribution.vue @@ -4,7 +4,7 @@ {{ t('imageScanner.registries.StatusDistribution.title') }}
- +
@@ -21,7 +21,11 @@ chartData: { type: Object, required: true - } + }, + filterFn: { + type: Function, + required: false + }, }, data() {} } diff --git a/pkg/sbombastic-image-vulnerability-scanner/components/TopRiskyImagesChart.vue b/pkg/sbombastic-image-vulnerability-scanner/components/TopRiskyImagesChart.vue index c7be328..538512e 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/components/TopRiskyImagesChart.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/components/TopRiskyImagesChart.vue @@ -4,14 +4,19 @@ Most affected images at risk
-
{{ image.imageName }}
+ + {{ image.imageName }} +
diff --git a/pkg/sbombastic-image-vulnerability-scanner/components/TopSevereVulnerabilitiesChart.vue b/pkg/sbombastic-image-vulnerability-scanner/components/TopSevereVulnerabilitiesChart.vue index 7813da2..7a8aa49 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/components/TopSevereVulnerabilitiesChart.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/components/TopSevereVulnerabilitiesChart.vue @@ -7,7 +7,6 @@ @@ -26,10 +25,6 @@ type: Array, required: true }, - eventHandler: { - type: Function, - default: null - } }, data() {} } diff --git a/pkg/sbombastic-image-vulnerability-scanner/components/common/BarChart.vue b/pkg/sbombastic-image-vulnerability-scanner/components/common/BarChart.vue index d7ea353..9ee0d2e 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/components/common/BarChart.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/components/common/BarChart.vue @@ -6,7 +6,7 @@
-
{{ t(`imageScanner.enum.${ colorPrefix }.${ key.toLowerCase() }`) }}
+
{{ t(`imageScanner.enum.${ colorPrefix }.${ key.toLowerCase() }`) }}
{{ value }}
@@ -34,11 +34,18 @@ export default { colorPrefix: { type: String, required: true - } + }, + filterFn: { + type: Function, + required: false + }, }, methods: { percentage(value) { return this.total > 0 ? (value / this.total) * 100 : 0; + }, + filterByCategory(category) { + this.filterFn && this.filterFn(category); } }, computed: { @@ -102,6 +109,8 @@ export default { width: 80px; align-items: right; gap: 12px; + text-decoration: underline; + cursor: pointer; } .severity-item-bar { diff --git a/pkg/sbombastic-image-vulnerability-scanner/components/common/SevereVulnerabilitiesItem.vue b/pkg/sbombastic-image-vulnerability-scanner/components/common/SevereVulnerabilitiesItem.vue index cb94e94..aa75959 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/components/common/SevereVulnerabilitiesItem.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/components/common/SevereVulnerabilitiesItem.vue @@ -10,7 +10,7 @@
@@ -20,6 +20,7 @@ diff --git a/pkg/sbombastic-image-vulnerability-scanner/config/sbombastic-image-vulnerability-scanner.ts b/pkg/sbombastic-image-vulnerability-scanner/config/sbombastic-image-vulnerability-scanner.ts index cefe2a9..9487d60 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/config/sbombastic-image-vulnerability-scanner.ts +++ b/pkg/sbombastic-image-vulnerability-scanner/config/sbombastic-image-vulnerability-scanner.ts @@ -1,16 +1,32 @@ -import { IPlugin } from "@shell/core/types"; import { PRODUCT_NAME, PAGE, RESOURCE } from "@pkg/types"; -export function init($plugin: IPlugin, store: any) { - const { product, virtualType, basicType } = $plugin.DSL(store, PRODUCT_NAME); +export function init($plugin: any, store: any) { + const { product, virtualType, basicType, weightType } = $plugin.DSL(store, PRODUCT_NAME); product({ - icon: "pod_security", - inStore: "cluster", + icon: "pod_security", + inStore: "cluster", + inExplorer: true, + removeable: false, + showNamespaceFilter: true + }); + + virtualType({ + labelKey: 'imageScanner.dashboard.title', + name: PAGE.DASHBOARD, + namespaced: false, + route: { + name: `c-cluster-${PRODUCT_NAME}-${PAGE.DASHBOARD}`, + params: { + product: PRODUCT_NAME + }, + meta: { pkg: PRODUCT_NAME, product: PRODUCT_NAME } + }, + overview: true }); virtualType({ @@ -39,19 +55,6 @@ export function init($plugin: IPlugin, store: any) { } }); - virtualType({ - labelKey: "imageScanner.dashboard.title", - name: PAGE.DASHBOARD, - namespaced: false, - route: { - name: `c-cluster-${PRODUCT_NAME}-${PAGE.DASHBOARD}`, - params: { - product: PRODUCT_NAME, - }, - meta: { pkg: PRODUCT_NAME, product: PRODUCT_NAME }, - }, - }); - virtualType({ labelKey: "imageScanner.vulnerabilities.title", name: PAGE.VULNERABILITY_OVERVIEW, @@ -65,19 +68,6 @@ export function init($plugin: IPlugin, store: any) { }, }); - virtualType({ - labelKey: "imageScanner.vexManagement.title", - name: PAGE.VEX_MANAGEMENT, - namespaced: false, - route: { - name: `c-cluster-${PRODUCT_NAME}-${PAGE.VEX_MANAGEMENT}`, - params: { - product: PRODUCT_NAME, - }, - meta: { pkg: PRODUCT_NAME, product: PRODUCT_NAME }, - }, - }); - virtualType({ label: "Components Demo", name: "demo", @@ -91,11 +81,14 @@ export function init($plugin: IPlugin, store: any) { }, }); + weightType(PAGE.DASHBOARD, 98, true); + weightType(PAGE.IMAGE_OVERVIEW, 97, true); + weightType(PAGE.VULNERABILITY_OVERVIEW, 96, true); + basicType([ PAGE.DASHBOARD, - PAGE.IMAGE_OVERVIEW, - PAGE.VULNERABILITY_OVERVIEW, - //"demo" + PAGE.IMAGE_OVERVIEW, + PAGE.VULNERABILITY_OVERVIEW, ]); // Prepend spaces on group name, as Rancher 2.12 render group name algin with sidemenu basicType([PAGE.REGISTRIES, PAGE.VEX_MANAGEMENT, RESOURCE.VEX_HUB], '    Advanced'); diff --git a/pkg/sbombastic-image-vulnerability-scanner/data/sbombastic.rancher.io.image.js b/pkg/sbombastic-image-vulnerability-scanner/data/sbombastic.rancher.io.image.js index ebd9380..546b3a6 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/data/sbombastic.rancher.io.image.js +++ b/pkg/sbombastic-image-vulnerability-scanner/data/sbombastic.rancher.io.image.js @@ -5,6 +5,8 @@ export const images = [ name: "struts-attacher:1.0", }, spec: { + isBaseImage: true, + hasAffectedPackages: true, repository: "coredns", registry: "Docker Hub", scanResult: { @@ -22,10 +24,12 @@ export const images = [ name: "imagemagick4.8.5613", }, spec: { + isBaseImage: false, + hasAffectedPackages: true, repository: "demo-cody-protected", registry: "demo.suse-security-ivs.io", scanResult: { - critical: 7, + critical: 0, high: 0, medium: 1028, low: 24, @@ -39,6 +43,8 @@ export const images = [ name: "centos7.7.1908", }, spec: { + isBaseImage: true, + hasAffectedPackages: false, repository: "kube-controller-manager", registry: "ecr.ap-southeast-emea.2", scanResult: { @@ -56,10 +62,12 @@ export const images = [ name: "nginx1.19.10", }, spec: { + isBaseImage: false, + hasAffectedPackages: true, repository: "kube-apiserver", registry: "Docker Hub", scanResult: { - critical: 150, + critical: 0, high: 5, medium: 850, low: 600, @@ -73,6 +81,8 @@ export const images = [ name: "docker-compose:1.29.2", }, spec: { + isBaseImage: true, + hasAffectedPackages: false, repository: "coredns", registry: "Docker Hub", scanResult: { @@ -90,12 +100,14 @@ export const images = [ name: "python3.9.7", }, spec: { + isBaseImage: false, + hasAffectedPackages: true, repository: "flask-app", registry: "pypi.org", scanResult: { - critical: 80, - high: 3, - medium: 1100, + critical: 0, + high: 0, + medium: 0, low: 50, none: 0 } @@ -107,6 +119,8 @@ export const images = [ name: "nodejs14.17.3", }, spec: { + isBaseImage: true, + hasAffectedPackages: true, repository: "web-server", registry: "npmjs.com", scanResult: { @@ -124,10 +138,12 @@ export const images = [ name: "redis5.0.7", }, spec: { + isBaseImage: false, + hasAffectedPackages: false, repository: "cache-service", registry: "Docker Hub", scanResult: { - critical: 58, + critical: 0, high: 0, medium: 700, low: 0, @@ -141,6 +157,8 @@ export const images = [ name: "mongodb4.4.1", }, spec: { + isBaseImage: true, + hasAffectedPackages: true, repository: "data-store", registry: "mongodb.com", scanResult: { @@ -158,13 +176,15 @@ export const images = [ name: "golang1.16.5", }, spec: { + isBaseImage: false, + hasAffectedPackages: false, repository: "api-gateway", registry: "demo.suse-security-ivs.io", scanResult: { - critical: 75, - high: 1, - medium: 1200, - low: 10, + critical: 0, + high: 0, + medium: 0, + low: 0, none: 18 } }, @@ -175,10 +195,12 @@ export const images = [ name: "ruby2.7.3", }, spec: { + isBaseImage: true, + hasAffectedPackages: false, repository: "web-application", registry: "rubygems.org", scanResult: { - critical: 60, + critical: 0, high: 1, medium: 500, low: 80, @@ -192,6 +214,8 @@ export const images = [ name: "elasticsearch7.10.0", }, spec: { + isBaseImage: false, + hasAffectedPackages: true, repository: "search-service", registry: "docker.elastic.co", scanResult: { @@ -209,10 +233,12 @@ export const images = [ name: "mysql8.0.25", }, spec: { + isBaseImage: true, + hasAffectedPackages: false, repository: "database-service", registry: "mysql.com", scanResult: { - critical: 70, + critical: 0, high: 0, medium: 850, low: 0, @@ -226,10 +252,12 @@ export const images = [ name: "php8.0.9", }, spec: { + isBaseImage: false, + hasAffectedPackages: true, repository: "web-backend", registry: "php.net", scanResult: { - critical: 55, + critical: 0, high: 0, medium: 400, low: 60, @@ -243,6 +271,8 @@ export const images = [ name: "postgresql13.3", }, spec: { + isBaseImage: true, + hasAffectedPackages: true, repository: "data-service", registry: "docker.io", scanResult: { @@ -260,6 +290,8 @@ export const images = [ name: "terraform1.0.0", }, spec: { + isBaseImage: false, + hasAffectedPackages: false, repository: "infrastructure-as-code", registry: "demo.suse-security-ivs.io", scanResult: { @@ -277,6 +309,8 @@ export const images = [ name: "ansible2.10.5", }, spec: { + isBaseImage: true, + hasAffectedPackages: false, repository: "automation-service", registry: "galaxy.ansible.com", scanResult: { @@ -294,6 +328,8 @@ export const images = [ name: "kafka2.8.0", }, spec: { + isBaseImage: false, + hasAffectedPackages: true, repository: "streaming-service", registry: "confluent.io", scanResult: { diff --git a/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml b/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml index 4844319..47e1f82 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml +++ b/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml @@ -30,6 +30,15 @@ imageScanner: title: Most affected images at risk viewAll: View all totalVulns: total vulnerabilities + appInstall: + title: Install SBOMBastic Image Vulnerability Scanner + description: Get a comprehensive view of your container image vulnerabilities and risk assessment across all your registries. + button: Install Image Vulnerability Scanner + reload: Unable to fetch SBOMBastic Helm chart - reload required. + checking: Checking... + versionError: + title: Chart Version not found. + message: Unable to determine the latest stable version of the SBOMBastic chart. Please make sure the Helm repository is configured correctly. registries: title: Registries configuration button: @@ -251,6 +260,7 @@ imageScanner: error: Error at: at schedule: Every {i} + reload: Reload typeLabel: sbombastic.rancher.io.registry: Registries configuration diff --git a/pkg/sbombastic-image-vulnerability-scanner/models/sbombastic.rancher.io.registry.js b/pkg/sbombastic-image-vulnerability-scanner/models/sbombastic.rancher.io.registry.js index be25608..5f27192 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/models/sbombastic.rancher.io.registry.js +++ b/pkg/sbombastic-image-vulnerability-scanner/models/sbombastic.rancher.io.registry.js @@ -46,9 +46,11 @@ export default class Registry extends SteveModel { message: e.message, }, { root: true }); } finally { + if (target.refreshFn instanceof Function) { setTimeout(() => { target.refreshFn(); }, 2000); + } } }, }; diff --git a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ComponentDemo.vue b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ComponentDemo.vue deleted file mode 100644 index 1e13144..0000000 --- a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ComponentDemo.vue +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Dashboard.vue b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Dashboard.vue index eaa0b1e..fbf2fb6 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Dashboard.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Dashboard.vue @@ -26,7 +26,7 @@ - +
@@ -70,7 +70,7 @@
- +
@@ -85,7 +85,6 @@
@@ -18,6 +19,7 @@ :options="filterImageOptions" :close-on-select="true" :multiple="false" + @selecting="changeImageFilter" />
@@ -35,7 +37,7 @@
- +
!image.spec.isBaseImage); + } else if (this.selectedImageFilter.value === "includeBaseImages" || this.selectedImageFilter === "includeBaseImages") { + filtered = filtered.filter(image => image.spec.isBaseImage); + } + if (this.selectedCveFilter.value === "affectingCvesOnly" || this.selectedCveFilter === "affectingCvesOnly") { + filtered = filtered.filter(image => image.spec.hasAffectedPackages); + } + this.preprocessedDataset = this.preprocessData(filtered); + }, + + changeImageFilter(selectedImageFilter) { + this.selectedImageFilter = selectedImageFilter; + this.applyFilters(); + }, + + changeCveFilter(selectedCveFilter) { + this.selectedCveFilter = selectedCveFilter; + this.applyFilters(); + }, + filterBySeverity(severity) { + this.preprocessedDataset.preprocessedImages = _.cloneDeep(this.preprocessedImagesBak); + if (severity) { + this.preprocessedDataset.preprocessedImages = this.preprocessedDataset.preprocessedImages.filter(image => (image.severity.toLowerCase() === severity.toLowerCase())); + } + }, onSelectionChange(selected) { - console.log("selected", selected) this.selectedRows = selected || []; }, preprocessData(images) { + console.log("images", images); const severityKeys = ['critical', 'high', 'medium', 'low', 'none']; const chartData = {}; const repoMap = new Map(); + const preprocessedImages = []; for (const key of severityKeys) { chartData[key] = 0; @@ -165,10 +211,14 @@ const topRiskyImages = images.map(image => { let repoRec = {}; + let imageSeverity = ""; const mapKey = `${image.spec.repository},${image.spec.registry}`; const currImageScanResult = {}; for (const key of severityKeys) { currImageScanResult[key] = image.spec.scanResult[key]; + if (!imageSeverity) { + imageSeverity = (image.spec.scanResult[key] || 0) > 0 ? key : ""; + } } if (repoMap.has(mapKey)) { const currRepo = repoMap.get(mapKey); @@ -198,8 +248,12 @@ repoMap.set(mapKey, repoRec); } for (const key of severityKeys) { - chartData[key] += image.spec.scanResult[key]; + chartData[key] += imageSeverity === key ? 1 : 0; } + preprocessedImages.push({ + ...image, + severity: imageSeverity || "none", + }); return { imageName: image.metadata.name, cveCnt: image.spec.scanResult, @@ -212,6 +266,7 @@ return 0; }).slice(0, 5); return { + preprocessedImages, topRiskyImages, chartData, rowsByRepo: Array.from(repoMap.values()) diff --git a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Installation.vue b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Installation.vue new file mode 100644 index 0000000..5b128ed --- /dev/null +++ b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Installation.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/RegistriesConfiguration.vue b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/RegistriesConfiguration.vue index 02804a4..413d8aa 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/RegistriesConfiguration.vue +++ b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/RegistriesConfiguration.vue @@ -34,7 +34,7 @@
- +
row.status === status); + this.registryStatusList = this.registryStatusList.filter(item => item.status === status); + this.statusSummary = this.statusSummary[status] || {}; + }, openAddEditRegistry() { this.$router.push({ name: `${ PRODUCT_NAME }-c-cluster-resource-create`, @@ -351,6 +357,7 @@ max-width: 900px; -webkit-box-orient: vertical; -webkit-line-clamp: 1; + line-clamp: 1; align-self: stretch; overflow: hidden; color: #717179; @@ -365,7 +372,7 @@ .state-date-time { overflow: hidden; -webkit-box-orient: vertical; - -webkit-line-clamp: 1; + line-clamp: 1; color: #717179; text-overflow: ellipsis; font-family: Lato; diff --git a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/index.vue b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/index.vue new file mode 100644 index 0000000..6a56d2b --- /dev/null +++ b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/index.vue @@ -0,0 +1,43 @@ + + + diff --git a/pkg/sbombastic-image-vulnerability-scanner/routes/sbombastic-image-vulnerability-scanner-routes.ts b/pkg/sbombastic-image-vulnerability-scanner/routes/sbombastic-image-vulnerability-scanner-routes.ts index 4c123b1..dc9b916 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/routes/sbombastic-image-vulnerability-scanner-routes.ts +++ b/pkg/sbombastic-image-vulnerability-scanner/routes/sbombastic-image-vulnerability-scanner-routes.ts @@ -1,12 +1,11 @@ import RegistryDetails from "@pkg/components/RegistryDetails.vue"; -import ComponentDemo from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ComponentDemo.vue"; -import Dashboard from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Dashboard.vue"; import ImageOverview from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue"; -import ImageDetails from "@sbombastic-image-vulnerability-scanner/components/ImageDetails.vue"; +import ImageDetails from "@pkg/components/ImageDetails.vue"; import RegistriesConfiguration from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/RegistriesConfiguration.vue"; import Vulnerabilities from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/Vulnerabilities.vue"; import CreateResource from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/_resource/create.vue"; import ListResource from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/_resource/index.vue"; +import Entry from "@pkg/pages/c/_cluster/sbombastic-image-vulnerability-scanner/index.vue"; import { PRODUCT_NAME, PAGE, @@ -14,9 +13,9 @@ import { const routes = [ { - name: `c-cluster-${PRODUCT_NAME}-${PAGE.DASHBOARD}`, - path: `/c/:cluster/${PRODUCT_NAME}/${PAGE.DASHBOARD}`, - component: Dashboard, + name: `c-cluster-${ PRODUCT_NAME }-${PAGE.DASHBOARD}`, + path: `/c/:cluster/${ PRODUCT_NAME }/${PAGE.DASHBOARD}`, + component: Entry, }, { name: `c-cluster-${PRODUCT_NAME}-${PAGE.IMAGE_OVERVIEW}`, @@ -33,21 +32,11 @@ const routes = [ path: `/c/:cluster/${PRODUCT_NAME}/${PAGE.VULNERABILITY_OVERVIEW}`, component: Vulnerabilities, }, - { - name: `c-cluster-${PRODUCT_NAME}-demo`, - path: `/c/:cluster/${PRODUCT_NAME}/demo`, - component: ComponentDemo, - }, { name: `c-cluster-${PRODUCT_NAME}-${PAGE.REGISTRIES}`, path: `/c/:cluster/${PRODUCT_NAME}/${PAGE.REGISTRIES}`, component: RegistriesConfiguration, }, - { - name: `c-cluster-${PRODUCT_NAME}-${PAGE.VEX_MANAGEMENT}`, - path: `/c/:cluster/${PRODUCT_NAME}/${PAGE.VEX_MANAGEMENT}`, - component: VexManagement, - }, { name: `c-cluster-${PRODUCT_NAME}-${PAGE.REGISTRIES}-id`, path: `/c/:cluster/${PRODUCT_NAME}/${PAGE.REGISTRIES}/:ns/:id`, diff --git a/pkg/sbombastic-image-vulnerability-scanner/types.ts b/pkg/sbombastic-image-vulnerability-scanner/types.ts index 0a5813f..c708f09 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/types.ts +++ b/pkg/sbombastic-image-vulnerability-scanner/types.ts @@ -2,3 +2,4 @@ export * from "./types/sbombastic-image-vulnerability-scanner"; export * from "./types/image"; export * from "./types/registry"; export * from "./types/vex"; +export * from "./types/chart"; diff --git a/pkg/sbombastic-image-vulnerability-scanner/types/chart.ts b/pkg/sbombastic-image-vulnerability-scanner/types/chart.ts new file mode 100644 index 0000000..4d00436 --- /dev/null +++ b/pkg/sbombastic-image-vulnerability-scanner/types/chart.ts @@ -0,0 +1,62 @@ +export interface Version { + name: string; + home?: string; + version: string; + description: string; + keywords?: string[]; + maintainers?: [ + { + name?: string; + email?: string; + url?: string; + } + ]; + icon?: string; + apiVersion: string; + appVersion: string; + annotations?: { [key: string]: string }; + kubeVersion: string; + dependencies?: [ + { + name?: string; + version?: string; + repository?: string; + condition?: string; + } + ]; + type: string; + urls?: string[]; + created?: string; + digest?: string; + key?: string; + repoType?: string; + repoName?: string; +} + +export interface Chart { + key: string; + type: string; + id: string; + certified?: string; + sideLabel?: null; + repoType: string; + repoName: string; + repoNameDisplay?: string; + certifiedSort?: number; + icon?: string; + color?: string; + chartType: string; + chartName: string; + chartNameDisplay?: string; + chartDescription?: string; + repoKey?: string; + versions?: Version[]; + categories?: string[]; + deprecated?: boolean; + hidden?: boolean; + targetNamespace?: string; + targetName?: string; + provides?: string[]; + windowsIncompatible?: boolean; + deploysOnWindows?: boolean; +} diff --git a/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts b/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts index b15b1fd..93e2c7a 100644 --- a/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts +++ b/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts @@ -1,4 +1,9 @@ export const PRODUCT_NAME = "imageScanner"; +export const SBOMBASTIC = { + CONTROLLER: "neuvector",//"sbombastic", + SERVICE: "sbombastic-service", + SCHEMA: "sbombastic.rancher.io.registry", +} export const RESOURCE = { REGISTRY: "sbombastic.rancher.io.registry", SCAN_JOB: "sbombastic.rancher.io.scanjob", diff --git a/pkg/sbombastic-image-vulnerability-scanner/utils/chart.ts b/pkg/sbombastic-image-vulnerability-scanner/utils/chart.ts new file mode 100644 index 0000000..02a7b5c --- /dev/null +++ b/pkg/sbombastic-image-vulnerability-scanner/utils/chart.ts @@ -0,0 +1,101 @@ +import { Chart } from "@pkg/types"; +import { handleGrowl } from "@pkg/utils/handle-growl"; +import isEmpty from 'lodash/isEmpty'; +import semver from 'semver'; + +export interface RefreshConfig { + store: any; + chartName: string; + retry?: number; + init?: boolean; +} + +export interface ReloadReady { + reloadReady: boolean; +} + +/** + * Asynchronously refreshes charts by dispatching actions to the store. It attempts to + * find a specific chart by its name and, if not found, dispatches actions to refresh + * the chart catalog. This method will retry the operation up to a maximum of three times + * based on the retry parameter and the presence of the chart. + * + * @param {RefreshConfig} config - The configuration object for the refresh operation. + * @param {any} config.store - The Vuex store instance used for state management. + * @param {string} config.chartName - The name of the chart to be refreshed. + * @param {number} [config.retry=0] - The current retry attempt count. Defaults to 0. + * @param {boolean} [config.init=false] - A flag indicating whether the initial load + * should prevent retries. Defaults to false. + * + * @returns {Promise} An object indicating whether the reload is ready. + * Currently, it always returns an object with `reloadReady` set to false. + * + * @example + * // Example usage: + * refreshCharts({ + * store: myStore, + * chartName: 'myChart', + * retry: 0, + * init: true + * }).then(result => { + * console.log(result.reloadReady); // false + * }); + */ +export async function refreshCharts( + config: RefreshConfig +): Promise { + const { store, chartName, init } = config; + let retry = config.retry ?? 0; + + while (retry < 3) { + const rawCharts = store.getters["catalog/rawCharts"]; + const chart = (Object.values(rawCharts) as Chart[])?.find( + (c) => c?.chartName === chartName + ); + + if (!chart) { + try { + // TODO: Add Custom VueX store for neuvector: { refreshingCharts: false } + // store.dispatch("neuvector/updateRefreshingCharts", true); + await store.dispatch("catalog/refresh"); + } catch (e) { + handleGrowl({ error: e as any, store }); + } finally { + // store.dispatch("neuvector/updateRefreshingCharts", false); + } + + if (retry < 2 && !init) { + retry++; + continue; + } + } + break; + } + + return { reloadReady: false }; +} + +export function getLatestStableVersion(versions: any[]): string | undefined { + const allVersions = versions.map(v => v.version); + const stableVersions = versions.filter(v => !v.version.includes('b')); + + if ( isEmpty(stableVersions) && !isEmpty(allVersions) ) { + return semver.rsort(allVersions)[0]; + } + + return stableVersions?.sort((a, b) => { + const versionA = a.version.split('.').map(Number); + const versionB = b.version.split('.').map(Number); + + for ( let i = 0; i < Math.max(versionA.length, versionB.length); i++ ) { + if ( versionA[i] === undefined || versionA[i] < versionB[i] ) { + return 1; + } + if ( versionB[i] === undefined || versionA[i] > versionB[i] ) { + return -1; + } + } + + return 0; + })[0]; +} diff --git a/pkg/sbombastic-image-vulnerability-scanner/utils/handle-growl.ts b/pkg/sbombastic-image-vulnerability-scanner/utils/handle-growl.ts new file mode 100644 index 0000000..bcd3957 --- /dev/null +++ b/pkg/sbombastic-image-vulnerability-scanner/utils/handle-growl.ts @@ -0,0 +1,27 @@ +export interface GrowlConfig { + error: { + data?: { + _statusText: String; + message: String; + }; + _statusText: String; + message: String; + }; + store?: any; + type?: String; +} + +export function handleGrowl(config: GrowlConfig): void { + const error = config.error?.data || config.error; + const type = config.type || "Error"; + + config.store.dispatch( + `growl/${type.toLowerCase()}`, + { + title: error._statusText || type, + message: error.message, + timeout: 5000, + }, + { root: true } + ); +}