fleet/ee/vulnerability-dashboard/api/controllers/get-remediation-timeline.js
Eric b1945b2128
Add fleet-vulnerability-dashboard repo to ee/ folder (#17428)
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
2024-03-13 13:06:11 -05:00

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
};
}
};