mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 08:28:52 +00:00
Closes: https://github.com/fleetdm/confidential/issues/4057 Changes: - Added the contents of the fleet-vulnerability-dashboard repo to ee/vulnerability-dashboard - Added a github workflow to deploy the vulnerability dashboard on Heroku - Added a github workflow to test changes to the vulnerability-dashboard - Updated the website's custom configuration to enable auto-approvals/review requests to files in the ee/vulnerability-dashboard folder
157 lines
7.9 KiB
JavaScript
157 lines
7.9 KiB
JavaScript
module.exports = {
|
|
|
|
|
|
friendlyName: 'Get remediation timeline',
|
|
|
|
|
|
description: 'Get snapshots of progress toward resolving the specified vulnerability.',
|
|
|
|
|
|
inputs: {
|
|
|
|
vulnerabilityId: {
|
|
description: 'The vulnerability whose resolution progress we\'re examining.',
|
|
type: 'number',
|
|
required: true,
|
|
},
|
|
|
|
teamApid: {
|
|
description: 'The ID of the Team to filter by, or 0 to only include hosts with no team, or undefined to not filter by any team.',
|
|
type: 'number',
|
|
}
|
|
|
|
},
|
|
|
|
|
|
exits: {
|
|
|
|
success: {
|
|
outputType: [{}],
|
|
},
|
|
},
|
|
|
|
|
|
fn: async function ({vulnerabilityId, teamApid}) {
|
|
|
|
// Find the database record for this vulnerability, populating hosts and installs
|
|
let vulnerabilityRecord = await Vulnerability.findOne({id: vulnerabilityId}).populate('hosts').populate('installs');
|
|
let vulnInstalls = vulnerabilityRecord.installs;
|
|
// Create a filtered array of currently installed vulnerable software
|
|
let currentVulnInstalls = _.filter(vulnerabilityRecord.installs, (install)=>{
|
|
return install.uninstalledAt === 0;
|
|
});
|
|
// Create a filtered array of the hosts with software affected by this vulnerability currently installed to use in the remediation timeline modal.
|
|
// Filtering out hosts that have vulnerable software currently installed.
|
|
let affectedHosts = _.filter(vulnerabilityRecord.hosts, (host)=>{
|
|
return _.contains(_.pluck(currentVulnInstalls, 'host'), host.id);
|
|
});
|
|
|
|
// If we're filtering results to a specific team, we'll filter the results to only track vulnerable installs/uninstalls for hosts on the selected team.
|
|
if(teamApid !== undefined) {
|
|
let affectedFilteredHosts = _.filter(affectedHosts, { 'teamApid': teamApid });
|
|
affectedHosts = affectedFilteredHosts;
|
|
let hostIdsInThisTeam = _.pluck(_.filter(vulnerabilityRecord.hosts, { 'teamApid': teamApid }), 'id');
|
|
vulnInstalls = vulnInstalls.filter((install)=>{
|
|
return _.contains(hostIdsInThisTeam, install.host);
|
|
});
|
|
}
|
|
|
|
// Add an array containing information about every software item affected by this vulnerability a host has installed.
|
|
// We'll use this to show every vulnerable software affected by this vulnerability
|
|
for(let host of affectedHosts){
|
|
host.installsAffectedByThisVulnerability = [];
|
|
let installsForThisHost = _.filter(currentVulnInstalls, (install)=>{
|
|
return host.id === install.host && install.uninstalledAt === 0;
|
|
});
|
|
for(let install of installsForThisHost){
|
|
host.installsAffectedByThisVulnerability.push({
|
|
softwareNameAndVersion: `${install.softwareName} @ ${install.versionName}`, // Build a string containing the software name and version name for this affected software.
|
|
fleetApid: install.fleetApid, // Add the fleetApid so we can build a link to this software item in the Fleet instance.
|
|
});
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Build the remediation timeline for this vulnerability.
|
|
// We'll do this by building an array of objects representing all changes to software installations affected by this vulnerability
|
|
// e.g., [{ timestamp: 1689271300000, change: 1, host: 5 },{ timestamp: 1689087600000, change: 1, host: 6 },{ timestamp: 1689087600000, change: 1, host: 12 }]
|
|
//
|
|
// We'll then group the changes by timestamp and track the number of hosts affected by this vulnerability of time.
|
|
// Note: because a vulnerability can affect multiple software versions, and hosts be affected by the same vulnerability multiple times, we'll only track software being installed/uninstalled
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
// Create an array that we will use to track a remediation timeline for all software affected by this vulnerability.
|
|
let installChangesOverTime = [];
|
|
let totalNumberOfVulnInstallsByHost = {};
|
|
|
|
// For each software install affected by this vulnerability, we'll add up to two objects to the installChangesOverTime array.
|
|
for(let install of vulnInstalls) {
|
|
// Add an object with the timestamp of when this software was detected with a change of 1 for this host.
|
|
installChangesOverTime.push({changedAt: install.installedAt, change: 1, host: install.host});
|
|
// If this software was uninstalled, add an object with the uninstalledAt timestamp and a change of -1.
|
|
if(install.uninstalledAt !== 0){
|
|
installChangesOverTime.push({changedAt: install.uninstalledAt, change: -1, host: install.host});
|
|
}
|
|
// Add this host to the totalNumberOfVulnInstallsByHost dictionary, this number will be incremented/subtracted when we iterate through the individual changes to software affected by this vulnerability.
|
|
totalNumberOfVulnInstallsByHost[install.host] = 0;
|
|
}//∞
|
|
// Instead of adding individual points on the graph for each software install/uninstall that share the same timestamp,
|
|
// we'll group the changes by changedAt values (rounded to the nearest 30 minutes), and add graphpoints representing the sum of the changes.
|
|
// The created object will look something like this: { '1689038373501': [ { changedAt: 1689038373501, change: 1 }, {} ], 1682300599332: [{...}] }
|
|
|
|
let changesSortedAndGroupedByChangedAt = _.groupBy(_.sortBy(installChangesOverTime, 'changedAt'), (change)=>{
|
|
// Round the changedAt values to the nearest 30 minutes and group them by that value.
|
|
return Math.round(change.changedAt / (30 * 60 * 1000)) * (30 * 60 * 1000);
|
|
});
|
|
|
|
|
|
let timeline = [];// « this is for our graph
|
|
let lastNumberOfAffectedHosts = 0;// « Set a variable to keep track of the number of hosts affected in the last graphpoint added.
|
|
// console.log(changesSortedAndGroupedByChangedAt);
|
|
// Now build our timeline.
|
|
// > Iterate through the top-level objects in the changesSortedAndGroupedByChangedAt object
|
|
for(let groupOfChanges in changesSortedAndGroupedByChangedAt) {
|
|
// console.log(groupOfChanges);
|
|
let timestampForThisGroup;
|
|
// Iterate through the array of grouped changes, applying the changes in affected hosts to totalNumberOfVulnInstallsByHost
|
|
for(let softwareChange of changesSortedAndGroupedByChangedAt[groupOfChanges]) {
|
|
totalNumberOfVulnInstallsByHost[softwareChange.host] += softwareChange.change;
|
|
timestampForThisGroup = softwareChange.changedAt;
|
|
}//∞
|
|
|
|
// Get the numebr of affected hosts during this time.
|
|
let affectedHostsDuringThisTime = _.filter(totalNumberOfVulnInstallsByHost, (installCount)=>{return installCount > 0;}).length;
|
|
// If the number of affected hosts is the same, we won't add a graph point (i.e., An affected host installs software that is affected by a vulnerability that is already present on the host)
|
|
if(affectedHostsDuringThisTime !== lastNumberOfAffectedHosts){
|
|
// create a graphpoint tracking the timestamp and the affectedHostsDuringThisTime
|
|
let graphPoint = {
|
|
timestamp: timestampForThisGroup,
|
|
numAffectedHosts: affectedHostsDuringThisTime,
|
|
};
|
|
// Add a graphpoint to the timeline for this software
|
|
timeline.push(graphPoint);
|
|
}
|
|
lastNumberOfAffectedHosts = affectedHostsDuringThisTime;
|
|
}//∞
|
|
|
|
// If this vulnerability currently still affects any hosts, we'll add a timeline object with the current number of affected hosts.
|
|
if(timeline.length >= 1 && timeline[timeline.length - 1].numAffectedHosts > 0) {
|
|
timeline.push({
|
|
timestamp: Date.now(),
|
|
numAffectedHosts: timeline[timeline.length - 1].numAffectedHosts,
|
|
});
|
|
}
|
|
|
|
let timelineForThisVulnerability = _.sortBy(timeline, 'timestamp');
|
|
|
|
return {
|
|
timelineForThisVulnerability,
|
|
affectedHosts
|
|
};
|
|
|
|
}
|
|
|
|
|
|
};
|