Skip to content

Commit

Permalink
Add dynamic vulnerability and image comparison pages (#943)
Browse files Browse the repository at this point in the history
Signed-off-by: ltagliaferri <lisa.tagliaferri@gmail.com>
Signed-off-by: Jamon Camisso <jamonation+git@gmail.com>
Co-authored-by: ltagliaferri <lisa.tagliaferri@gmail.com>
  • Loading branch information
jamonation and ltagliaferri authored Aug 23, 2023
1 parent 31ef3cd commit 2638cfd
Show file tree
Hide file tree
Showing 46 changed files with 1,299 additions and 189 deletions.
13 changes: 0 additions & 13 deletions assets/js/rumble.js

This file was deleted.

44 changes: 44 additions & 0 deletions assets/js/rumble/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

// colour coding for sevrity on vulnerabilty info pages
const severityColours = {
Critical: "#F236F6",
High: "#3443F4",
Medium: "#1F2892",
Low: "#16C0D7",
Negligible: "#C5C5C5",
Unknown: "#8C8C8C"
}

// used on image comparison and vulnerability info page search fields
const searchFilter = document.querySelector("#filterInput");
const severityPicker = document.querySelector("#severity-picker");
if (searchFilter !== null) {
searchFilter.value = null;
searchFilter.addEventListener("keyup", () => {
let filter = document.getElementById("filterInput").value.toLowerCase();
filterTable("rumble-images-external", filter);
filterTable("rumble-images-chainguard", filter);
if (severityPicker != null) {
severityPicker.querySelector("label span").innerHTML = `<span>Severity<span class="bi-chevron-down" style="padding-left: 2rem;"></span></span>`;
}
});
}

// taken from https://www.delftstack.com/howto/javascript/javascript-filter-table/
function filterTable(tableId, filter) {
var table, tr, i, j;
table = document.getElementById(tableId);
tr = table.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
tr[i].style.display = "none";
const tdArray = tr[i].getElementsByTagName("td");
for (var j = 0; j < tdArray.length; j++) {
const cellValue = tdArray[j];
if (cellValue && cellValue.innerHTML.toLowerCase().indexOf(filter) > -1) {
tr[i].style.display = "";
break;
}
}
}
}
146 changes: 146 additions & 0 deletions assets/js/rumble/comparison.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// used on an image's vulnerability comparison page

const image = document.location.pathname.replace(/\/$/, "").split("/").pop();
console.log(image);
const data = await d3.csv(`https://storage.googleapis.com/chainguard-academy/cve-data/${image}.csv`);
const displayColumns = ["Package", "Version", "Vulnerability", "Severity"];
const dataColumns = ["package", "version", "vulnerability", "s"];
const dataSorted = sortData();

makeTable("#rumble-images-external", dataSorted.theirs, dataSorted.theirVulns);
makeTable("#rumble-images-chainguard", dataSorted.ours, dataSorted.ourVulns);

function sortData() {
let theirs = [],
ours = [];

data.forEach(function (row) {
if (row.image.startsWith("cgr.dev")) {
ours.push(row);
} else {
theirs.push(row);
}
});

let theirVulns = [];
let ourVulns = [];

theirs.forEach(function (row) {
if (!(theirVulns.includes(row.vulnerability, 0))) {
theirVulns.push(row.vulnerability)
}
})

ours.forEach(function (row) {
if (!(ourVulns.includes(row.vulnerability, 0))) {
ourVulns.push(row.vulnerability)
}
})

return { "theirs": theirs, "ours": ours, "theirVulns": theirVulns, "ourVulns": ourVulns }
};

function makeTable(id, sortedData, vulnIDs) {
var table = d3.select(id).append("table"),
thead = table.append("thead"),
tbody = table.append("tbody");

// append the header row
thead.append("tr")
.selectAll("th")
.data(function () {
return displayColumns.map(function (column) {
if (column == "Vulnerability") {
let count = vulnIDs.length;
column = `ID [${count} unique]`
}
return column;
})
})
.enter()
.append("th")
.text(function (column) { return column; });



// create a row for each object in the data
var rows = tbody.selectAll("tr")
.data(sortedData)
.enter()
.append("tr");

// create a cell in each row for each column
rows.selectAll("td")
.data(function (row) {
return dataColumns.map(function (column) {
let val = row[column];
if (column == "vulnerability") {
let isProd = getEnvUrl();
if (isProd) {
val = `<a href="/vulnerabilities/${val}">${val}</a>`
} else {
val = `<a href="/vulnerabilities/?id=${val}">${val}</a>`
}
}
if (column == "s") {
val = `<span style="color: ${severityColours[val]}; vertical-align: text-bottom; font-size: 8px; padding: 0.5rem; ">⬤</span><span style="vertical-align: text-bottom;">${val}</span>`
}
return { column: column, value: val };
});
})
.enter()
.append("td")
.html(function (d) { return d.value; });

if (vulnIDs.length == 0) {
document.querySelector(id).insertAdjacentHTML("beforeend", `<p style="margin: 0; padding: 24px 8px;">No vulnerabilities detected</p>`);
return;
}
};

// toggles between absolute and ?id= URLs for each page when rendering table links
function getEnvUrl() {
let host = document.location.host;
if (host.match(`.+netlify.com.+`)) {
return false
} else if (host.match(`localhost:1313`)) {
return false
} else {
return true
}
};

severityPicker.addEventListener("click", function (event) {
if (event.target.tagName == "INPUT" || event.target.tagName == "LABEL") {
return;
}
severityPicker.querySelector(".dropdown-content").visiblity = "hidden";
severityPicker.querySelector('input[type = "checkbox"]').checked = false;

let filter = event.target.dataset.severity.toLowerCase();
filterTable("rumble-images-external", filter);
filterTable("rumble-images-chainguard", filter);

if (event.target.dataset.severity == "") {
severityPicker.querySelector("label span").innerHTML = `<span>Severity<span class="bi-chevron-down" style="padding-left: 2rem;"></span></span>`;
return;
}

severityPicker.querySelector("label span").innerHTML = `<span data-severity="${event.target.dataset.severity}"><span class="severity sev-${event.target.dataset.severity.toLowerCase()}"
data-severity="${event.target.dataset.severity}">⬤</span>${event.target.dataset.severity}</span>`;
searchFilter.value = null;
});
severityPicker.addEventListener("mouseleave", function (event) {
// severityPicker.querySelector(".dropdown-content").visiblity = "hidden";
severityPicker.querySelector('input[type = "checkbox"]').checked = false;
});

const tds = document.querySelectorAll("#rumble .tables table tbody tr td");
tds.forEach(function (td) {
let href = td.parentNode.childNodes[2].childNodes[0].href;

td.addEventListener("click", function () {
document.location = href;
});

})
118 changes: 118 additions & 0 deletions assets/js/rumble/vulnerability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// fetches and renders information about individual vulnerabilities

const displayColumns = ["Image", "First Detected", "Last Detected", "Days Vulnerable"];
const dataColumns = ["image", "first_seen", "last_seen", "duration"];
const id = getVulnID();

const data = await getData(`https://storage.googleapis.com/chainguard-academy/vulnerability-info/${id}.json`);

showTitle();
showVuln();

function getVulnID() {
let id = "";
let host = document.location.host;
if (host.match(`.+netlify.app:443`) || host == "localhost:1313") {
id = new URLSearchParams(document.location.search).get("id");
} else {
id = document.location.pathname.split("/").pop();
}
return id;
}

async function getData(url) {
let res = await fetch(url);
if (res.ok) {
return await res.json();
} else {
return null;
}
}

function showTitle() {
let vuln = data;
const title = document.querySelector("#rumble-vuln h2#id");
title.innerHTML = `${id} <span style="color: ${severityColours[vuln.severity]}; vertical-align: text-bottom; font-size: 0.75rem; padding: 0.5rem; ">⬤</span><span style="vertical-align: text-bottom; font-size: 1rem;">${vuln.severity}</span>`;
title.hidden = false;
}

function showVuln() {
const div = document.querySelector("#vuln-details");
let vuln = data;

let desc = document.createElement("p");
desc.innerHTML = `<p>${vuln.description}</p>`

let sourceUrl = document.createElement("p");
sourceUrl.innerHTML = `<strong>Source</strong>: <a href="${vuln.url}">${vuln.url}</a>`

div.insertAdjacentElement("beforeEnd", desc);
div.insertAdjacentElement("beforeEnd", sourceUrl);
}

makeTable("#rumble-images-external", data.external_images);
makeTable("#rumble-images-chainguard", data.chainguard_images);

function makeTable(id, images) {

// sort alphabetically by image name
if (images !== null) {
images.sort(function (a, b) {
var keyA = a.image;
var keyB = b.image;
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
} else {
document.querySelector(id).innerHTML = `<p style="margin: 0; padding: 24px 8px;">No vulnerabilities detected</p>`;
return;
}

// assemble the table
var table = d3.select(id).append("table")
.attr("id", `${id}-1`),
thead = table.append("thead"),
tbody = table.append("tbody");

// append the header row
thead.append("tr")
.selectAll("th")
.data(displayColumns)
.enter()
.append("th")
.text(function (column) { return column; });

if (images === null) {
return;
}
// create a row for each object in the data
var rows = tbody.selectAll("tr")
.data(images)
.enter()
.append("tr");

// create a cell in each row for each column
rows.selectAll("td")
.data(function (row) {
return dataColumns.map(function (column) {
let val = row[column];
if (column == "first_seen") {
val = row.dates[0];
val = val.split("T", 1)[0];
}
if (column == "last_seen") {
val = row.dates[row.dates.length - 1];
val = val.split("T", 1)[0] // get rid of timestamps
}
if (column == "duration") {
val = row.dates.length;
}
return { column: column, value: val };
});
})
.enter()
.append("td")
.html(function (d) { return d.value; });
};

15 changes: 10 additions & 5 deletions assets/scss/common/_mobile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
display: none;
}

#sidebar-default > div {
#sidebar-default>div {
margin-top: 0 !important;
width: 100%;
}
Expand All @@ -78,6 +78,7 @@
}

@media (max-width: $MobileLayoutWidth) {

// Collapse leftnav on mobile width
.leftnav-container {
display: none;
Expand Down Expand Up @@ -119,9 +120,9 @@
display: none;
}

.content > .row,
.content > .row > .row,
.content > .row > .row > .col-md-12 {
.content>.row,
.content>.row>.row,
.content>.row>.row>.col-md-12 {
margin: 0 !important;
padding: 0 !important;
}
Expand Down Expand Up @@ -217,4 +218,8 @@
.sidebar-bottom {
background: inherit;
}
}

#severity-picker {
display: none;
}
}
Loading

0 comments on commit 2638cfd

Please sign in to comment.