Skip to content

Commit

Permalink
Vulnerability dashboard: update priority patch progress calculation. (#…
Browse files Browse the repository at this point in the history
…21348)

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.
  • Loading branch information
eashaw authored Aug 15, 2024
1 parent ade1d06 commit 58e62ba
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -369,74 +369,11 @@ module.exports = {
// console.log(realDataForGraphs);


// ┌─┐┌─┐┌┬┐ ┌─┐┬─┐┬┌─┐┬─┐┬┌┬┐┬ ┬ ┌─┐┬ ┬┌─┐ ┌─┐┬─┐┌─┐┌─┐┬─┐┌─┐┌─┐┌─┐
// │ ┬├┤ │ ├─┘├┬┘││ │├┬┘│ │ └┬┘ │ └┐┌┘├┤ ├─┘├┬┘│ ││ ┬├┬┘├┤ └─┐└─┐
// └─┘└─┘ ┴ ┴ ┴└─┴└─┘┴└─┴ ┴ ┴ └─┘ └┘ └─┘ ┴ ┴└─└─┘└─┘┴└─└─┘└─┘└─┘

// Get the JSON array of Priority CVE IDs from the platform record.
let platformRecord = await Platform.find({}).limit(1);
let priorityVulnerabilities = platformRecord[0].priorityCveIds;
let priorityVulnerabilitiesThatExistInTheDatabase = await Vulnerability.find({isPriority: true});
let cveIdsThatDontExistInTheDatabase = _.difference(priorityVulnerabilities, _.pluck(priorityVulnerabilitiesThatExistInTheDatabase, 'cveId'));
let priorityVulnPatchProgress = [];

// Get patch progress for priority CVE IDs that only exist in the Platform record.
for(let cve of cveIdsThatDontExistInTheDatabase){
// Trim whitespace from the CVE ID. https://github.com/fleetdm/fleet/issues/14904
let trimmedCveId = _.trim(cve);
// Check to see if a Vulnerability record has been created for this CVE ID.
let vulnRecordForThisCveExists = await Vulnerability.findOne({cveId: trimmedCveId, isPriority: false});
if(vulnRecordForThisCveExists){// If we found a Vulnerability record that matches a CVE ID, we'll update it to have `isPriority: true`.
let updatedVulnRecord = await Vulnerability.updateOne({id: vulnRecordForThisCveExists.id}).set({isPriority: true});
priorityVulnerabilitiesThatExistInTheDatabase.push(updatedVulnRecord);
} else {// Otherwise, we'll add 100% patch progress for this CVE.
let patchProgress = {
cveId: trimmedCveId,
patchProgressPercentage: 100,
additionalDetailsUrl: 'https://nvd.nist.gov/vuln/detail/'+ encodeURIComponent(cve),
};
priorityVulnPatchProgress.push(patchProgress);
}
}

// Get patch progress for CVEs we have records for.
for(let vuln of priorityVulnerabilitiesThatExistInTheDatabase) {
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'));

let resolvedInstallsForThisVuln = [];
let affectedSoftwareForThisVulnerability = [];
vulnPatchProgress.numberOfHostsAffected = uniqueAffectedHosts.length;
for(let install of installsForThisVulnerability) {
// If the install has a non-zero uninstalledAt value, then it has been uninstalled.
if(install.uninstalledAt !== 0) {
// If this is a resolved install, we'll check the other installs for this vulnerability to see if there is an unresolved VulnerabilityInstall for this host.
if(!_.find(installsForThisVulnerability, {host: install.host, uninstalledAt: 0})){
// If an unresolved vulnerabilityInstall record affecting this host is found, we won't count this vulnerability as resolved for this host.
resolvedInstallsForThisVuln.push(install);
}
}
affectedSoftwareForThisVulnerability.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.
let uniqNumberOfResolvedInstallsForThisVuln = _.uniq(resolvedInstallsForThisVuln, 'host').length;
vulnPatchProgress.affectedSoftware = _.uniq(affectedSoftwareForThisVulnerability, 'url');
// 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);
}//∞

// Sort the priority vulnerabilities by CVE ID.
priorityVulnPatchProgress = _.sortBy(priorityVulnPatchProgress, 'cveId');

return {
realDataForGraphs: {
priorityVulnPatchProgress,
priorityVulnPatchProgress: [],// This information is gathered after the initial page load.
remediationTimeline: realDataForGraphs.remediationTimeline,
timelineDatasets: realDataForGraphs.timelineDatasets,
newPublishedVulnerabilities:realDataForGraphs.newPublishedVulnerabilities,//last 48 hours
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
module.exports = {


friendlyName: 'Get priority vulnerabilities',


description: 'Returns information about priority CVEs.',
extendedDescription: 'This code was previously in the view action for the dashboard page, but was moved to a separate action to reduce inital page laoding time.',

exits: {
success: {
outputType: [{}],
},
},


fn: async function () {
// ┌─┐┌─┐┌┬┐ ┌─┐┬─┐┬┌─┐┬─┐┬┌┬┐┬ ┬ ┌─┐┬ ┬┌─┐ ┌─┐┬─┐┌─┐┌─┐┬─┐┌─┐┌─┐┌─┐
// │ ┬├┤ │ ├─┘├┬┘││ │├┬┘│ │ └┬┘ │ └┐┌┘├┤ ├─┘├┬┘│ ││ ┬├┬┘├┤ └─┐└─┐
// └─┘└─┘ ┴ ┴ ┴└─┴└─┘┴└─┴ ┴ ┴ └─┘ └┘ └─┘ ┴ ┴└─└─┘└─┘┴└─└─┘└─┘└─┘

// Get the JSON array of Priority CVE IDs from the platform record.
let platformRecord = await Platform.find({}).limit(1);
let priorityVulnerabilities = platformRecord[0].priorityCveIds;
let priorityVulnerabilitiesThatExistInTheDatabase = await Vulnerability.find({isPriority: true});
let cveIdsThatDontExistInTheDatabase = _.difference(priorityVulnerabilities, _.pluck(priorityVulnerabilitiesThatExistInTheDatabase, 'cveId'));
let priorityVulnPatchProgress = [];

// Get patch progress for priority CVE IDs that only exist in the Platform record.
for(let cve of cveIdsThatDontExistInTheDatabase){
// Trim whitespace from the CVE ID. https://github.com/fleetdm/fleet/issues/14904
let trimmedCveId = _.trim(cve);
// Check to see if a Vulnerability record has been created for this CVE ID.
let vulnRecordForThisCveExists = await Vulnerability.findOne({cveId: trimmedCveId, isPriority: false});
if(vulnRecordForThisCveExists){// If we found a Vulnerability record that matches a CVE ID, we'll update it to have `isPriority: true`.
let updatedVulnRecord = await Vulnerability.updateOne({id: vulnRecordForThisCveExists.id}).set({isPriority: true});
priorityVulnerabilitiesThatExistInTheDatabase.push(updatedVulnRecord);
} else {// Otherwise, we'll add 100% patch progress for this CVE.
let patchProgress = {
cveId: trimmedCveId,
patchProgressPercentage: 100,
additionalDetailsUrl: 'https://nvd.nist.gov/vuln/detail/'+ encodeURIComponent(cve),
};
priorityVulnPatchProgress.push(patchProgress);
}
}

// Get patch progress for CVEs we have records for.
for(let vuln of priorityVulnerabilitiesThatExistInTheDatabase) {
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 have been 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);
}//∞

// Sort the priority vulnerabilities by CVE ID.
priorityVulnPatchProgress = _.sortBy(priorityVulnPatchProgress, 'cveId');

return priorityVulnPatchProgress;

}


};
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,17 @@ module.exports = {
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'));

let resolvedInstallsForThisVuln = [];
let affectedSoftwareForThisVulnerability = [];
vulnPatchProgress.numberOfHostsAffected = uniqueAffectedHosts.length;
for(let install of installsForThisVulnerability) {
// If the install has a non-zero uninstalledAt value, then it has been uninstalled.
if(install.uninstalledAt !== 0) {
// If this is a resolved install, we'll check the other installs for this vulnerability to see if there is an unresolved VulnerabilityInstall for this host.
if(!_.find(installsForThisVulnerability, {host: install.host, uninstalledAt: 0})){
// If an unresolved vulnerabilityInstall record affecting this host is found, we won't count this vulnerability as resolved for this host.
resolvedInstallsForThisVuln.push(install);
}
}
affectedSoftwareForThisVulnerability.push({name: install.softwareName, version: install.versionName, url: sails.config.custom.fleetBaseUrl+'/software/'+install.fleetApid });
}//∞

// 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.
let uniqNumberOfResolvedInstallsForThisVuln = _.uniq(resolvedInstallsForThisVuln, 'host').length;
vulnPatchProgress.affectedSoftware = _.uniq(affectedSoftwareForThisVulnerability, 'url');
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);
Expand Down
2 changes: 1 addition & 1 deletion ee/vulnerability-dashboard/assets/js/cloud.setup.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ parasails.registerPage('welcome', {
}]
};
await this.drawGraphsOnPage();
await this.getPriorityCves();
},

// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
Expand All @@ -56,7 +57,6 @@ parasails.registerPage('welcome', {
methods: {
clickOpenEditModal: function () {
this.formData.priorityCves = _.pluck(this.dataForGraphs.priorityVulnPatchProgress, 'cveId');
console.log(this.formData);
this.modal = 'priority-cves';
},
handleSubmittingPriorityCveForm: async function(argins) {
Expand All @@ -79,6 +79,12 @@ parasails.registerPage('welcome', {
this.syncing = false;
this.modal = '';
},
getPriorityCves: async function () {
this.syncing = true;
let priorityCveProgress = await Cloud.getPriorityVulnerabilities();
this.dataForGraphs.priorityVulnPatchProgress = priorityCveProgress;
this.syncing = false;
},
drawGraphsOnPage: async function(){
new Chart('average-remediation-time', {
type: 'line',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
text-align: center;
}
}
[purpose='loading-indicator'] {
.loader(@brand);
}
[purpose='vuln-patch-progress-bar'] {
width: 300px;
height: 16px;
Expand Down
2 changes: 1 addition & 1 deletion ee/vulnerability-dashboard/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ module.exports.routes = {
'GET /entrance/signup-okta-user-or-redirect': { action: 'entrance/signup-okta-user-or-redirect' },
// 'POST /api/v1/deliver-contact-form-message': { action: 'deliver-contact-form-message' },
// 'POST /api/v1/observe-my-session': { action: 'observe-my-session', hasSocketFeatures: true },

'GET /api/v1/get-priority-vulnerabilities': { action: 'get-priority-vulnerabilities' },
'GET /api/v1/get-vulnerabilities': { action: 'get-vulnerabilities', hasSocketFeatures: true },
'GET /api/v1/get-remediation-timeline': { action: 'get-remediation-timeline', hasSocketFeatures: true },
'GET /download-vulnerabilities-csv': { action: 'download-vulnerabilities-csv'},
Expand Down
Loading

0 comments on commit 58e62ba

Please sign in to comment.