mirror of
https://github.com/fleetdm/fleet
synced 2026-05-04 13:59:01 +00:00
Changes: - Optimized patch progress calculation - Moved patch progress calculation to a new action `get-priority-vulnerabilities` that is called after the dashboard page laods - Added a loading state to the patch progress section of the dashboard page.
129 lines
6.6 KiB
JavaScript
129 lines
6.6 KiB
JavaScript
module.exports = {
|
|
|
|
|
|
friendlyName: 'Update priority vulnerabilities',
|
|
|
|
|
|
description: 'Sets vulnerabilities to be priority and returns the patch progress of all priority vulnerabilities.',
|
|
|
|
|
|
inputs: {
|
|
newPriorityCveIds: {
|
|
type: ['string'],
|
|
description: 'An array containing strings of CVE IDs of vulnerabilities that will be tracked on the dashboard page.'// TODO: better description
|
|
}
|
|
},
|
|
|
|
|
|
exits: {
|
|
success: {
|
|
outputType: [{}],
|
|
},
|
|
|
|
invalidCve: {
|
|
responseType: 'badRequest',
|
|
description: 'No matching cve could be found'
|
|
},
|
|
},
|
|
|
|
|
|
fn: async function ({newPriorityCveIds}) {
|
|
|
|
|
|
// If the form was submitted without any priority CVEs then we'll clear out any that exist
|
|
if(!newPriorityCveIds){
|
|
await Vulnerability.update({isPriority: true}).set({isPriority: false});
|
|
return [];
|
|
}
|
|
|
|
let platformRecord = await Platform.find({}).limit(1);
|
|
|
|
// Check each CVE ID to make sure they are valid.
|
|
let cveIdsThatExistInTheDatabase = [];
|
|
let cveIdsNotFoundInDatabase = [];
|
|
let invalidCves = [];
|
|
|
|
for(let newCve of newPriorityCveIds) {
|
|
// Trim whitespace from the CVE ID and make sure it is upper case. https://github.com/fleetdm/fleet/issues/14904
|
|
let trimmedUpperCaseCveId = _.trim(newCve.toUpperCase());
|
|
// Check the CVE ID provided to see if it is valid.
|
|
let newCveIsInTheExpectedFormat = !! trimmedUpperCaseCveId.match(/^CVE-\d{4}-\d+$/gi);
|
|
let vulnExists = await Vulnerability.findOne({cveId: trimmedUpperCaseCveId});
|
|
if(vulnExists){// If we found a matching Vulnerability record in the database, we'll add it to the array of existing vulnerabilities
|
|
cveIdsThatExistInTheDatabase.push(trimmedUpperCaseCveId);
|
|
} else if(newCveIsInTheExpectedFormat){// If the CVE ID did not match a Vulnerability record, we'll add it to the array of CVE IDs not found in the database.
|
|
cveIdsNotFoundInDatabase.push(trimmedUpperCaseCveId);
|
|
} else if (!newCveIsInTheExpectedFormat){// If the CVE ID is invalid (does not follow the standard CVE-xxxx-xxxx format), we'll add the original provided cve id to the array of invalidCves.
|
|
invalidCves.push(newCve);
|
|
}
|
|
}
|
|
if(invalidCves.length > 0) {
|
|
// If an invalid CVE ID was provided, return an 'invalidCve' response.
|
|
throw {invalidCve: invalidCves};// Note: we're passing in the array of invalid CVE IDs so we can display them in an error message.
|
|
}
|
|
|
|
|
|
let existingPriorityCves = await Vulnerability.find({isPriority: true});
|
|
// Get a filtered array of CVE IDs that are no longer a priority.
|
|
let previousPriorityCveIds = _.pluck(existingPriorityCves, 'cveId');
|
|
// Find priority CVEs that have been removed from the list
|
|
let cvesThatAreNoLongerAPriority = _.difference(previousPriorityCveIds, newPriorityCveIds);
|
|
// update the vulnerabilities that we are no longer tracking
|
|
await Vulnerability.update({cveId: {in: cvesThatAreNoLongerAPriority}}).set({isPriority: false});
|
|
|
|
let vulnsToCheckPatchProgressFor = [];
|
|
// Update the vulnerability record for our new priority cves
|
|
for(let cve of cveIdsThatExistInTheDatabase){
|
|
let vuln = await Vulnerability.updateOne({cveId: cve}).set({isPriority: true});
|
|
// Add the updated record to the array of vulnerabilities we'll be checking patch progress for.
|
|
vulnsToCheckPatchProgressFor.push(vuln);
|
|
}//∞
|
|
|
|
let priorityVulnPatchProgress = [];
|
|
// Get patch progress for vulnerabilities that have been reported by the Fleet instance.
|
|
for(let vuln of vulnsToCheckPatchProgressFor) {
|
|
let vulnPatchProgress = _.clone(vuln);
|
|
vulnPatchProgress.affectedSoftware = [];
|
|
// Calculate how many host have been affected by this vulnerability, and how many hosts are currently affected by this vulnerability
|
|
let installsForThisVulnerability = await VulnerabilityInstall.find({vulnerability: vuln.id});
|
|
// This number will represent the number of hosts that are currently affected by the vulnerability
|
|
let uniqueAffectedHosts = _.uniq(_.pluck(installsForThisVulnerability, 'host'));
|
|
// Get a list of software that is currently installed and affected by this vulnerability.
|
|
let unresolvedInstallsForThisVuln = _.filter(installsForThisVulnerability, {uninstalledAt: 0});
|
|
let unresolvedHosts = _.uniq(_.pluck(unresolvedInstallsForThisVuln, 'host'));
|
|
let resolvedHosts = _.difference(uniqueAffectedHosts, unresolvedHosts);
|
|
let uniqNumberOfResolvedInstallsForThisVuln = resolvedHosts.length;
|
|
// Iterate through the installs for this vulnerability to build a list of software
|
|
await sails.helpers.flow.simultaneouslyForEach(_.uniq(installsForThisVulnerability, 'fleetApid'), (install)=>{
|
|
vulnPatchProgress.affectedSoftware.push({name: install.softwareName, version: install.versionName, url: sails.config.custom.fleetBaseUrl+'/software/'+install.fleetApid });
|
|
});
|
|
// Get the number of unique hosts who were previosuly affected by this vulnerability.
|
|
vulnPatchProgress.numberOfHostsAffected = uniqueAffectedHosts.length;
|
|
// To calculate the patch progress, we'll use the number of unique hosts who were previously affected by this vulnerability as the numerator and the number of unique hosts affected by the vulnerability as the denominator.
|
|
vulnPatchProgress.patchProgressPercentage = Math.floor((uniqNumberOfResolvedInstallsForThisVuln / vulnPatchProgress.numberOfHostsAffected) * 100);
|
|
priorityVulnPatchProgress.push(vulnPatchProgress);
|
|
}//∞
|
|
|
|
// After we get the patch progress for vulnerabilities we have records for, add patch progress for the CVEs that have not been reported by the Fleet instance.
|
|
for(let cveId of cveIdsNotFoundInDatabase) {
|
|
let patchProgress = {
|
|
cveId: cveId,
|
|
patchProgressPercentage: 100,// Since this vulnerability is not currently affecting any hosts, we'll display 100% for its patch progress.
|
|
additionalDetailsUrl: 'https://nvd.nist.gov/vuln/detail/'+ encodeURIComponent(cveId),
|
|
};
|
|
priorityVulnPatchProgress.push(patchProgress);
|
|
}
|
|
// Get an array of the new CVE IDs we'll be tracking as priority.
|
|
let updatedPatchProgressCveIds = _.pluck(priorityVulnPatchProgress, 'cveId');
|
|
// Update the platform record with the new array of priority CVE IDs.
|
|
await Platform.updateOne({id: platformRecord[0].id}).set({priorityCveIds: updatedPatchProgressCveIds});
|
|
// Deduplicate and sort the array of patch progress.
|
|
priorityVulnPatchProgress = _.sortBy(_.uniq(priorityVulnPatchProgress, 'cveId'), 'cveId');
|
|
|
|
// All done.
|
|
return priorityVulnPatchProgress;
|
|
|
|
}
|
|
|
|
|
|
};
|