Skip to content

Commit

Permalink
Add base OS info to image view (#7)
Browse files Browse the repository at this point in the history
* Fix image name placement in image view

* Add base OS info to image view
  • Loading branch information
Starttoaster authored Apr 27, 2024
1 parent d2b1542 commit f6e143f
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 77 deletions.
7 changes: 5 additions & 2 deletions internal/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,11 @@ func imageHandler(w http.ResponseWriter, r *http.Request) {

// Get vulnerability list that matches filters
view := views.ImageVulnerabilityView{
Name: imageName,
Digest: imageDigest,
Name: imageName,
Digest: imageDigest,
OSFamily: v.OSFamily,
OSVersion: v.OSVersion,
OSEndOfServiceLife: v.OSEndOfServiceLife,
}
for id, vuln := range v.Vulnerabilities {
// filter by severity in query param
Expand Down
50 changes: 50 additions & 0 deletions internal/web/views/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package views

import "sort"

// SortImageVulnerabilityView sorts the provided ImageVulnerabilityView's data slice
func SortImageVulnerabilityView(v ImageVulnerabilityView) ImageVulnerabilityView {
// Sort by vulnerability severity separately
// Because sometimes low or other tier vulnerabilities also have high scores
var (
crit []ImageVulnerabilityData
high []ImageVulnerabilityData
med []ImageVulnerabilityData
low []ImageVulnerabilityData
)
for _, data := range v.Data {
switch data.Severity {
case "Critical":
crit = append(crit, data)
case "High":
high = append(high, data)
case "Medium":
med = append(med, data)
case "Low":
low = append(low, data)
}
}

// Sort each severity tier by score separately
sort.SliceStable(crit, func(i, j int) bool {
return crit[i].Score > crit[j].Score
})
sort.SliceStable(high, func(i, j int) bool {
return high[i].Score > high[j].Score
})
sort.SliceStable(med, func(i, j int) bool {
return med[i].Score > med[j].Score
})
sort.SliceStable(low, func(i, j int) bool {
return low[i].Score > low[j].Score
})

// Combine now to one mega slice
var data []ImageVulnerabilityData
data = append(data, crit...)
data = append(data, high...)
data = append(data, med...)
data = append(data, low...)
v.Data = data
return v
}
77 changes: 25 additions & 52 deletions internal/web/views/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,10 @@ func GetImagesView(data *scraper.ScrapeData) ImagesView {
if gauge.Key == TrivyImageVulnerabilityMetricName {
// TODO -- grab each label into variables individually and check that they're not empty
// Construct all data types from metric data
var image Image
if gauge.Labels["image_registry"] == "index.docker.io" {
// If Docker Hub, trim the registry prefix for readability
// Also trims `library/` from the prefix of the image name, which is a hidden username for Docker Hub official images
image.Name = fmt.Sprintf("%s:%s", strings.TrimPrefix(gauge.Labels["image_repository"], "library/"), gauge.Labels["image_tag"])
} else {
image.Name = fmt.Sprintf("%s/%s:%s", gauge.Labels["image_registry"], gauge.Labels["image_repository"], gauge.Labels["image_tag"])
image := Image{
Name: getImageNameFromLabels(gauge),
Digest: gauge.Labels["image_digest"],
}
image.Digest = gauge.Labels["image_digest"]
podData := PodMetadata{
Pod: gauge.Labels["resource_name"],
Namespace: gauge.Labels["namespace"],
Expand Down Expand Up @@ -85,6 +80,22 @@ func GetImagesView(data *scraper.ScrapeData) ImagesView {
}
}

for _, gauge := range data.Gauges {
if gauge.Key == TrivyImageInfoMetricName {
image := Image{
Name: getImageNameFromLabels(gauge),
Digest: gauge.Labels["image_digest"],
}

if data, ok := i.Images[image]; ok {
data.OSFamily = gauge.Labels["image_os_family"]
data.OSVersion = gauge.Labels["image_os_name"]
data.OSEndOfServiceLife = gauge.Labels["image_os_eosl"]
i.Images[image] = data
}
}
}

i = setImagesViewVulnerabilityCounters(i)
i = sortImagesView(i)

Expand Down Expand Up @@ -135,49 +146,11 @@ func sortImagesView(i ImagesView) ImagesView {
return i
}

// SortImageVulnerabilityView sorts the provided ImageVulnerabilityView's data slice
func SortImageVulnerabilityView(v ImageVulnerabilityView) ImageVulnerabilityView {
// Sort by vulnerability severity separately
// Because sometimes low or other tier vulnerabilities also have high scores
var (
crit []ImageVulnerabilityData
high []ImageVulnerabilityData
med []ImageVulnerabilityData
low []ImageVulnerabilityData
)
for _, data := range v.Data {
switch data.Severity {
case "Critical":
crit = append(crit, data)
case "High":
high = append(high, data)
case "Medium":
med = append(med, data)
case "Low":
low = append(low, data)
}
func getImageNameFromLabels(gauge scraper.PrometheusGaugeMetric) string {
if gauge.Labels["image_registry"] == "index.docker.io" {
// If Docker Hub, trim the registry prefix for readability
// Also trims `library/` from the prefix of the image name, which is a hidden username for Docker Hub official images
return fmt.Sprintf("%s:%s", strings.TrimPrefix(gauge.Labels["image_repository"], "library/"), gauge.Labels["image_tag"])
}

// Sort each severity tier by score separately
sort.SliceStable(crit, func(i, j int) bool {
return crit[i].Score > crit[j].Score
})
sort.SliceStable(high, func(i, j int) bool {
return high[i].Score > high[j].Score
})
sort.SliceStable(med, func(i, j int) bool {
return med[i].Score > med[j].Score
})
sort.SliceStable(low, func(i, j int) bool {
return low[i].Score > low[j].Score
})

// Combine now to one mega slice
var data []ImageVulnerabilityData
data = append(data, crit...)
data = append(data, high...)
data = append(data, med...)
data = append(data, low...)
v.Data = data
return v
return fmt.Sprintf("%s/%s:%s", gauge.Labels["image_registry"], gauge.Labels["image_repository"], gauge.Labels["image_tag"])
}
17 changes: 12 additions & 5 deletions internal/web/views/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ type Image struct {

// ImageData contains data about image vulnerabilities and metadata about the Pods running those images
type ImageData struct {
Name string
Digest string
Pods map[PodMetadata]struct{}
Vulnerabilities map[string]Vulnerability
Name string // name of the image
Digest string // sha digest of the image
OSFamily string // distro name like "debian" or "alpine"
OSVersion string // distro version like "12.6"
OSEndOfServiceLife string // end of service life data
Pods map[PodMetadata]struct{} // data about Pods using this image
Vulnerabilities map[string]Vulnerability // keys of CVE IDs with vulnerability data values
CriticalVulnerabilities int
HighVulnerabilities int
MediumVulnerabilities int
Expand Down Expand Up @@ -51,7 +54,11 @@ type ImageVulnerabilityView struct {
// Name is the name of the image containing vulnerabilities
Name string
// Digest is the string image hash
Digest string
Digest string
OSFamily string // distro name like "debian" or "alpine"
OSVersion string // distro version like "12.6"
OSEndOfServiceLife string // end of service life data

// Data contains a slice of all the vulnerabilities for the given image
Data []ImageVulnerabilityData
}
Expand Down
19 changes: 14 additions & 5 deletions static/css/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -574,11 +574,6 @@ video {
z-index: 40;
}

.mx-auto {
margin-left: auto;
margin-right: auto;
}

.mb-5 {
margin-bottom: 1.25rem;
}
Expand Down Expand Up @@ -714,6 +709,10 @@ video {
border-radius: 0.25rem;
}

.rounded-full {
border-radius: 9999px;
}

.rounded-lg {
border-radius: 0.5rem;
}
Expand Down Expand Up @@ -1128,6 +1127,11 @@ video {
color: rgb(219 234 254 / var(--tw-text-opacity));
}

.dark\:text-blue-300 {
--tw-text-opacity: 1;
color: rgb(147 197 253 / var(--tw-text-opacity));
}

.dark\:text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
Expand All @@ -1143,6 +1147,11 @@ video {
color: rgb(254 226 226 / var(--tw-text-opacity));
}

.dark\:text-red-300 {
--tw-text-opacity: 1;
color: rgb(252 165 165 / var(--tw-text-opacity));
}

.dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
Expand Down
36 changes: 23 additions & 13 deletions static/image.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@
<a href="/" class="flex items-center ps-2.5 mb-5">
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">Trivy Operator Explorer</span>
</a>
<ul class="space-y-2 font-medium">
<li>
<a href="/" class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
<svg class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 21">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
</svg>
<span class="ms-3">Images</span>
</a>
</li>
</ul>
<ul class="space-y-2 font-medium">
<li>
<a href="/" class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
<svg class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 21">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z"/>
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z"/>
</svg>
<span class="ms-3">Images</span>
</a>
</li>
</ul>
</div>
</aside>

<!-- Image name top bar -->
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<nav class="sm:ml-64 bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap justify-between p-4">
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
<li>
Expand All @@ -38,6 +38,16 @@
<li>
<span class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-400 md:p-0 dark:text-white md:dark:text-blue-500" aria-current="page">{{ .Digest }}</span>
</li>
{{ if .OSFamily }}
<li>
<span class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-blue-900 dark:text-blue-300">{{ .OSFamily }} {{ .OSVersion }}</span>
</li>
{{ end }}
{{ if .OSEndOfServiceLife }}
<li>
<span class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-red-900 dark:text-red-300">EoSL</span>
</li>
{{ end }}
</ul>
</div>
</div>
Expand Down

0 comments on commit f6e143f

Please sign in to comment.