mirror of
https://github.com/fleetdm/fleet
synced 2026-05-04 22:08:41 +00:00
443 lines
23 KiB
JavaScript
443 lines
23 KiB
JavaScript
|
|
module.exports = {
|
||
|
|
|
||
|
|
|
||
|
|
friendlyName: 'Get dashboard graph data',
|
||
|
|
|
||
|
|
|
||
|
|
description: '',
|
||
|
|
|
||
|
|
|
||
|
|
inputs: {
|
||
|
|
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
exits: {
|
||
|
|
success: {
|
||
|
|
outputType: {},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
fn: async function () {
|
||
|
|
|
||
|
|
let realDataForGraphs = {};
|
||
|
|
|
||
|
|
// ┌┐ ┬ ┬┬┬ ┌┬┐ ┌┐┌┌─┐┌┬┐┬┬ ┬┌─┐ ┌─┐ ┬ ┬┌─┐┬─┐┬┌─┐┌─┐
|
||
|
|
// ├┴┐│ │││ ││ │││├─┤ │ │└┐┌┘├┤ │─┼┐│ │├┤ ├┬┘│├┤ └─┐
|
||
|
|
// └─┘└─┘┴┴─┘─┴┘ ┘└┘┴ ┴ ┴ ┴ └┘ └─┘ └─┘└└─┘└─┘┴└─┴└─┘└─┘
|
||
|
|
|
||
|
|
// Create a SQL query to find Vulnerability records where all associated VulnerabilityInstall records have a non-zero uninstalledAt timestamp.
|
||
|
|
// Note: this query uses MYSQL specific syntax.
|
||
|
|
let resolvedVulnerabilitiesWithResolutionTimestampQuery = `
|
||
|
|
SELECT
|
||
|
|
vulnerability.id,
|
||
|
|
vulnerability.cveId,
|
||
|
|
vulnerability.fleetSoftwareItemUrl,
|
||
|
|
vulnerability.additionalDetailsUrl,
|
||
|
|
vulnerability.probabilityOfExploit,
|
||
|
|
vulnerability.severity,
|
||
|
|
vulnerability.hasKnownExploit,
|
||
|
|
vulnerability.publishedAt,
|
||
|
|
CASE
|
||
|
|
WHEN COUNT(vulnerabilityinstall.id) = 0 THEN NULL
|
||
|
|
WHEN MIN(vulnerabilityinstall.uninstalledAt) = 0 THEN NULL
|
||
|
|
ELSE MAX(vulnerabilityinstall.uninstalledAt)
|
||
|
|
END AS resolvedAt
|
||
|
|
FROM
|
||
|
|
vulnerability
|
||
|
|
LEFT JOIN
|
||
|
|
vulnerabilityinstall ON vulnerability.id = vulnerabilityinstall.vulnerability
|
||
|
|
GROUP BY
|
||
|
|
vulnerability.id, vulnerability.cveId, vulnerability.fleetSoftwareItemUrl, vulnerability.additionalDetailsUrl, vulnerability.probabilityOfExploit, vulnerability.severity, vulnerability.hasKnownExploit, vulnerability.publishedAt
|
||
|
|
HAVING
|
||
|
|
(COUNT(vulnerabilityinstall.id) = 0 OR MIN(vulnerabilityinstall.uninstalledAt) > 0);`;
|
||
|
|
|
||
|
|
// Create two more SQL queries, the first finds distinct hosts that are currently affected by a critical severity vulnerability, the second finds distinct hosts that are currently affected by a high severity vulnerability.
|
||
|
|
let hostsWithCriticalVulnsQuery = `
|
||
|
|
SELECT DISTINCT
|
||
|
|
Host.*
|
||
|
|
FROM
|
||
|
|
Host
|
||
|
|
WHERE
|
||
|
|
EXISTS (
|
||
|
|
SELECT 1
|
||
|
|
FROM VulnerabilityInstall
|
||
|
|
INNER JOIN Vulnerability ON VulnerabilityInstall.vulnerability = Vulnerability.id
|
||
|
|
WHERE
|
||
|
|
VulnerabilityInstall.host = Host.id
|
||
|
|
AND Vulnerability.severity >= 9
|
||
|
|
AND VulnerabilityInstall.uninstalledat = 0
|
||
|
|
);`;
|
||
|
|
let hostsWithHighVulnsQuery = `
|
||
|
|
SELECT DISTINCT
|
||
|
|
Host.*
|
||
|
|
FROM
|
||
|
|
Host
|
||
|
|
WHERE
|
||
|
|
EXISTS (
|
||
|
|
SELECT 1
|
||
|
|
FROM VulnerabilityInstall
|
||
|
|
INNER JOIN Vulnerability ON VulnerabilityInstall.vulnerability = Vulnerability.id
|
||
|
|
WHERE
|
||
|
|
VulnerabilityInstall.host = Host.id
|
||
|
|
AND Vulnerability.severity < 9
|
||
|
|
AND Vulnerability.severity >= 7
|
||
|
|
AND VulnerabilityInstall.uninstalledat = 0
|
||
|
|
);`;
|
||
|
|
|
||
|
|
// If this app is configured to use a Postgres DB, we'll change the native SQL queries to be compatible.
|
||
|
|
if(sails.config.datastores.default.adapter === 'sails-postgresql') {
|
||
|
|
resolvedVulnerabilitiesWithResolutionTimestampQuery = `
|
||
|
|
SELECT
|
||
|
|
"vulnerability".*,
|
||
|
|
CASE
|
||
|
|
WHEN COUNT("vulnerabilityinstall".id) = 0 THEN 0
|
||
|
|
WHEN MIN("vulnerabilityinstall"."uninstalledAt") = 0 THEN 0
|
||
|
|
ELSE MAX("vulnerabilityinstall"."uninstalledAt")
|
||
|
|
END AS "resolvedAt"
|
||
|
|
FROM
|
||
|
|
vulnerability
|
||
|
|
LEFT JOIN
|
||
|
|
vulnerabilityinstall ON vulnerability.id = vulnerabilityinstall."vulnerability"
|
||
|
|
GROUP BY
|
||
|
|
vulnerability.id, vulnerability."cveId", vulnerability."fleetSoftwareItemUrl", vulnerability."additionalDetailsUrl", vulnerability."probabilityOfExploit", vulnerability."severity", vulnerability."hasKnownExploit", vulnerability."publishedAt"
|
||
|
|
HAVING
|
||
|
|
(COUNT(vulnerabilityinstall.id) = 0 OR MIN(vulnerabilityinstall."uninstalledAt") > 0);`;
|
||
|
|
|
||
|
|
hostsWithCriticalVulnsQuery = `
|
||
|
|
SELECT DISTINCT
|
||
|
|
Host.*
|
||
|
|
FROM
|
||
|
|
Host
|
||
|
|
WHERE
|
||
|
|
EXISTS (
|
||
|
|
SELECT 1
|
||
|
|
FROM VulnerabilityInstall
|
||
|
|
INNER JOIN Vulnerability ON VulnerabilityInstall.vulnerability = Vulnerability.id
|
||
|
|
WHERE
|
||
|
|
VulnerabilityInstall.host = Host.id
|
||
|
|
AND Vulnerability.severity >= 9
|
||
|
|
AND VulnerabilityInstall."uninstalledAt" = 0
|
||
|
|
);`;
|
||
|
|
hostsWithHighVulnsQuery = `
|
||
|
|
SELECT DISTINCT
|
||
|
|
Host.*
|
||
|
|
FROM
|
||
|
|
Host
|
||
|
|
WHERE
|
||
|
|
EXISTS (
|
||
|
|
SELECT 1
|
||
|
|
FROM VulnerabilityInstall
|
||
|
|
INNER JOIN Vulnerability ON VulnerabilityInstall.vulnerability = Vulnerability.id
|
||
|
|
WHERE
|
||
|
|
VulnerabilityInstall.host = Host.id
|
||
|
|
AND Vulnerability.severity < 9
|
||
|
|
AND Vulnerability.severity >= 7
|
||
|
|
AND VulnerabilityInstall."uninstalledAt" = 0
|
||
|
|
);`;
|
||
|
|
}
|
||
|
|
// Performance notes:
|
||
|
|
// Hosted Postgres DB (4,989 Vulnerabilities, 12,232 hosts, and 395,728 Installs):
|
||
|
|
// - Waterline queries (all hosts w/ vulnerabilities populated & all Vulnerabilities w/ installs populated): 56.9s
|
||
|
|
// - Native SQL queries: 1.4s
|
||
|
|
// Local MYSQL DB (5,719 Vulnerabilities, 8,159 hosts, and 819,158 Installs):
|
||
|
|
// - Waterline queries (all hosts w/ vulnerabilities populated & all Vulnerabilities w/ installs populated): 24.9s
|
||
|
|
// - Native SQL queries: 4.1s
|
||
|
|
|
||
|
|
// ┌─┐┌─┐┌┬┐┬ ┬┌─┐┬─┐ ┬┌┐┌┌─┐┌─┐┬─┐┌┬┐┌─┐┌┬┐┬┌─┐┌┐┌ ┌─┐┬─┐┌─┐┌┬┐ ┌┬┐┌─┐┌┬┐┌─┐┌┐ ┌─┐┌─┐┌─┐
|
||
|
|
// │ ┬├─┤ │ ├─┤├┤ ├┬┘ ││││├┤ │ │├┬┘│││├─┤ │ ││ ││││ ├┤ ├┬┘│ ││││ ││├─┤ │ ├─┤├┴┐├─┤└─┐├┤
|
||
|
|
// └─┘┴ ┴ ┴ ┴ ┴└─┘┴└─ ┴┘└┘└ └─┘┴└─┴ ┴┴ ┴ ┴ ┴└─┘┘└┘ └ ┴└─└─┘┴ ┴ ─┴┘┴ ┴ ┴ ┴ ┴└─┘┴ ┴└─┘└─┘
|
||
|
|
|
||
|
|
// console.time('native queries');
|
||
|
|
let rawResultForResolvedVulnerabilities = await sails.sendNativeQuery(resolvedVulnerabilitiesWithResolutionTimestampQuery);
|
||
|
|
let rawResultForHostsWithHighVulns = await sails.sendNativeQuery(hostsWithHighVulnsQuery);
|
||
|
|
let rawResultForHostsWithCriticalVulns = await sails.sendNativeQuery(hostsWithCriticalVulnsQuery);
|
||
|
|
// console.timeEnd('native queries');
|
||
|
|
|
||
|
|
// console.time('waterline queries')
|
||
|
|
// let Tesvulnerabilities = await Vulnerability.find().populate('installs');
|
||
|
|
// let Teshosts = await Host.find().populate('vulnerabilities');
|
||
|
|
// console.timeEnd('waterline queries')
|
||
|
|
|
||
|
|
let resolvedVulnerabilities = rawResultForResolvedVulnerabilities.rows;
|
||
|
|
let hostsWithHighVulnerabilities = rawResultForHostsWithHighVulns.rows;
|
||
|
|
let hostsWithCriticalVulnerabilities = rawResultForHostsWithCriticalVulns.rows;
|
||
|
|
|
||
|
|
// Get all vulnerabilities, we'll use these to track the newly detected vulnerabilities and the total number of vulnerabilities by severity over time.
|
||
|
|
let vulnerabilities = await Vulnerability.find();
|
||
|
|
|
||
|
|
// Get the total number of hosts enrolled the fleet instance. This will be used as the denominiator in the Percentage of hosts with vulnerabiilities by severity graph.
|
||
|
|
let totalNumberOfHosts = await Host.count();
|
||
|
|
|
||
|
|
|
||
|
|
// * * * * * quick sanity check to make sure the results from native queries are the same as previous results * * * * *
|
||
|
|
// let criticalVulnerabilities = await Vulnerability.find({severity: {'>=': 9}}).populate('hosts').populate('installs');
|
||
|
|
|
||
|
|
// let currentCriticalVulnerabilities = criticalVulnerabilities.filter((vulnerability)=>{
|
||
|
|
// let vulnIsCurrentlyInstalled = _.some(vulnerability.installs, (install)=>{
|
||
|
|
// return install.uninstalledAt === 0;
|
||
|
|
// });
|
||
|
|
// return vulnIsCurrentlyInstalled;
|
||
|
|
// });
|
||
|
|
// let allAffectedInstallsFromCurrentVulnerabilities = _.pluck(currentCriticalVulnerabilities, 'installs')
|
||
|
|
// let affectedCriticalInstalls = [];
|
||
|
|
// for(let installs of allAffectedInstallsFromCurrentVulnerabilities) {
|
||
|
|
// let hostsAffectedByThisInstall = installs.filter((install)=>{
|
||
|
|
// return install.uninstalledAt === 0
|
||
|
|
// });
|
||
|
|
// affectedCriticalInstalls = affectedCriticalInstalls.concat(hostsAffectedByThisInstall);
|
||
|
|
// }
|
||
|
|
// let allHostsAffectedByCriticalVulnerabilities = [];
|
||
|
|
// for(let vuln of currentCriticalVulnerabilities){
|
||
|
|
// allHostsAffectedByCriticalVulnerabilities = allHostsAffectedByCriticalVulnerabilities.concat(vuln.hosts)
|
||
|
|
// };
|
||
|
|
// allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities = _.uniq(allHostsAffectedByCriticalVulnerabilities, 'id');
|
||
|
|
// // console.log(allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities);
|
||
|
|
// let differenceBetweenMethods = (_.pluck(rawResultForHostsWithCriticalVulns.rows, 'id').length === _.pluck(allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities, 'id').length);
|
||
|
|
// if(!differenceBetweenMethods){
|
||
|
|
// console.log('number of hosts with critical vulns (from nativequery): ',rawResultForHostsWithCriticalVulns.rows.length)
|
||
|
|
// console.log('Number of hosts with critical vulns (from sanity check): ',allUniqueHostDisplayNamesAffectedByCriticalVulnerabilities.length)
|
||
|
|
// console.log('number of critical vulns: ',criticalVulnerabilities.length);
|
||
|
|
// console.log('number of current critical vulns: ',currentCriticalVulnerabilities.length);
|
||
|
|
// throw new Error('The native query returned a different number of results');
|
||
|
|
// }
|
||
|
|
// // * * *
|
||
|
|
|
||
|
|
// ┌┐ ┬ ┬┬┬ ┌┬┐ ┌┬┐┌─┐┌┬┐┌─┐┌─┐┌─┐┌┬┐┌─┐ ┌─┐┌─┐┬─┐ ┌─┐┬─┐┌─┐┌─┐┬ ┬┌─┐
|
||
|
|
// ├┴┐│ │││ ││ ││├─┤ │ ├─┤└─┐├┤ │ └─┐ ├┤ │ │├┬┘ │ ┬├┬┘├─┤├─┘├─┤└─┐
|
||
|
|
// └─┘└─┘┴┴─┘─┴┘ ─┴┘┴ ┴ ┴ ┴ ┴└─┘└─┘ ┴ └─┘ └ └─┘┴└─ └─┘┴└─┴ ┴┴ ┴ ┴└─┘
|
||
|
|
|
||
|
|
// Create a filtered array of all critical vulnerabilities with no unresolved vulnerability installs.
|
||
|
|
let resolvedCriticalVulnerabilities = [];
|
||
|
|
|
||
|
|
for(let vuln of resolvedVulnerabilities){
|
||
|
|
// If the vulnerability has a lower than critical severity, we'll ignore it.
|
||
|
|
if(vuln.severity < 9){
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
// Add information about this resolved critical vulnerability to the resolvedCriticalVulnerabilities array.
|
||
|
|
let vulnerability = {
|
||
|
|
cveId: vuln.cveId,
|
||
|
|
resolvedAt: vuln.resolvedAt,
|
||
|
|
daysToFullyResolve: ( vuln.resolvedAt - vuln.createdAt) / (24 * 60 * 60 * 1000)
|
||
|
|
};
|
||
|
|
resolvedCriticalVulnerabilities.push(vulnerability);
|
||
|
|
}//∞
|
||
|
|
|
||
|
|
let newPublishedVulns = vulnerabilities.filter((vuln)=>{
|
||
|
|
return vuln.createdAt > Date.now() - (1000 * 60 * 60 * 48);
|
||
|
|
});
|
||
|
|
|
||
|
|
let newPublishedCriticalVulns = newPublishedVulns.filter((vuln)=>{
|
||
|
|
return vuln.severity >= 9;
|
||
|
|
});
|
||
|
|
|
||
|
|
let newPublishedHighVulns = newPublishedVulns.filter((vuln)=>{
|
||
|
|
return vuln.severity < 9 && vuln.severity >= 7;
|
||
|
|
});
|
||
|
|
|
||
|
|
realDataForGraphs.newPublishedVulnerabilities = {
|
||
|
|
totalNumberOfNewVulnerabilities: newPublishedVulns.length > 0 ? newPublishedVulns.length : 0,
|
||
|
|
numberOfNewCriticalVulnerabilities: newPublishedCriticalVulns.length > 0 ? newPublishedCriticalVulns.length : 0,
|
||
|
|
numberOfNewHighVulnerabilities: newPublishedHighVulns.length > 0 ? newPublishedHighVulns.length : 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
let percentageOfHostsWithHighVuln = Math.round((hostsWithHighVulnerabilities.length / totalNumberOfHosts * 100).toFixed(2));
|
||
|
|
let percentageOfHostsWithNoHighVuln = Math.round(100 - percentageOfHostsWithHighVuln);
|
||
|
|
let percentageOfHostsWithCriticalVuln = Math.round((hostsWithCriticalVulnerabilities.length / totalNumberOfHosts * 100).toFixed(2));
|
||
|
|
let percentageOfHostsWithNoCriticalVuln = Math.round(100 - percentageOfHostsWithCriticalVuln);
|
||
|
|
|
||
|
|
realDataForGraphs.criticalVulnerabilityPercentage = [percentageOfHostsWithCriticalVuln, percentageOfHostsWithNoCriticalVuln];
|
||
|
|
realDataForGraphs.highVulnerabilityPercentage = [percentageOfHostsWithHighVuln, percentageOfHostsWithNoHighVuln];
|
||
|
|
|
||
|
|
|
||
|
|
let criticalSeverity = vulnerabilities.filter((vuln)=>{
|
||
|
|
return vuln.severity >= 9;
|
||
|
|
});
|
||
|
|
let highSeverity = vulnerabilities.filter((vuln)=>{
|
||
|
|
return vuln.severity <= 8.9 && vuln.severity >= 7.0;
|
||
|
|
});
|
||
|
|
let mediumSeverity = vulnerabilities.filter((vuln)=>{
|
||
|
|
return vuln.severity <= 6.9 && vuln.severity >= 4.0;
|
||
|
|
});
|
||
|
|
let lowSeverity = vulnerabilities.filter((vuln)=>{
|
||
|
|
return vuln.severity >= 0 && vuln.severity <= 3.9;
|
||
|
|
});
|
||
|
|
let unknownSeverity = vulnerabilities.filter((vuln)=>{
|
||
|
|
return vuln.severity === 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
realDataForGraphs.numberOfVulnsBySeverity = [criticalSeverity.length, highSeverity.length, mediumSeverity.length, lowSeverity.length, unknownSeverity.length];
|
||
|
|
|
||
|
|
let timelineDatasets = {
|
||
|
|
critical: [],
|
||
|
|
high: [],
|
||
|
|
medium: [],
|
||
|
|
low: [],
|
||
|
|
unknown: [],
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
// Get a timestamp of midnight (UTC) previous the sunday.
|
||
|
|
let today = new Date();
|
||
|
|
let lastSunday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - today.getDay());
|
||
|
|
let lastSundayAt = Date.UTC(lastSunday.getFullYear(), lastSunday.getMonth(), lastSunday.getDate());
|
||
|
|
let NUMBER_OF_WEEKS_TO_BUILD_TIMELINE_FOR = 3;
|
||
|
|
|
||
|
|
// Create a JS timestamp of the sunday three weeks prior to our previous timestamp.
|
||
|
|
let threeWeeksFromLastSundayAt = lastSundayAt - (NUMBER_OF_WEEKS_TO_BUILD_TIMELINE_FOR * 7 * 24 * 60 * 60 * 1000);
|
||
|
|
|
||
|
|
let remediationDataset = [];
|
||
|
|
|
||
|
|
for(let i = 0; i <= NUMBER_OF_WEEKS_TO_BUILD_TIMELINE_FOR; i++) {
|
||
|
|
let weekToAdd = (i * (7 * 24 * 60 * 60 * 1000));
|
||
|
|
// Create a JS timestamp for the start and end timestamps we're checking for this iteration.
|
||
|
|
let weekStartsAt = threeWeeksFromLastSundayAt + weekToAdd;
|
||
|
|
let weekEndsAt = threeWeeksFromLastSundayAt + weekToAdd + (7 * 24 * 60 * 60 * 1000);
|
||
|
|
// Filter the array of fully resolved vulnerabilities to find vulnerabilities that were resolved during this time.
|
||
|
|
let criticalVulnerabilitiesFullyResolvedDuringThisTime = resolvedCriticalVulnerabilities.filter((resolvedVulnerability)=>{
|
||
|
|
return resolvedVulnerability.resolvedAt > weekStartsAt && resolvedVulnerability.resolvedAt < weekEndsAt;
|
||
|
|
});
|
||
|
|
// console.log(weekStartsAt+' - '+weekEndsAt,criticalVulnerabilitiesFullyResolvedDuringThisTime)
|
||
|
|
|
||
|
|
// Find the average number of days that it took to fully resolve the vulnerabilities that were resolved during this week.
|
||
|
|
let averageNumberOfCriticalVulnerabilitiesResolved = Math.round(_.sum(criticalVulnerabilitiesFullyResolvedDuringThisTime, 'daysToFullyResolve') / criticalVulnerabilitiesFullyResolvedDuringThisTime.length);
|
||
|
|
// console.log(criticalVulnerabilitiesFullyResolvedDuringThisTime.length, averageNumberOfCriticalVulnerabilitiesResolved);
|
||
|
|
// Create a datapoint for the remediation timeline that has the average number of vulnerabilities resolved, and a timestamp of the start of the week.
|
||
|
|
let remediationDatapoint = {
|
||
|
|
x: weekStartsAt,
|
||
|
|
y: averageNumberOfCriticalVulnerabilitiesResolved > 0 ? averageNumberOfCriticalVulnerabilitiesResolved : 'N/A',
|
||
|
|
};
|
||
|
|
remediationDataset.push(remediationDatapoint);
|
||
|
|
|
||
|
|
// Create a filtered array of all critical vulnerabilities that affected hosts during this time.
|
||
|
|
let criticalVulnerabilitiesThisWeek = _.filter(criticalSeverity, (vulnerability)=>{
|
||
|
|
return vulnerability.createdAt < weekStartsAt;
|
||
|
|
});
|
||
|
|
// Create a dataset for the graph for this week.
|
||
|
|
let criticalDatasetForThisWeek = {
|
||
|
|
x: weekStartsAt,
|
||
|
|
y: criticalVulnerabilitiesThisWeek.length
|
||
|
|
};
|
||
|
|
timelineDatasets.critical.push(criticalDatasetForThisWeek);
|
||
|
|
// High severity
|
||
|
|
let numberOfHighVulnerabilitiesThisWeek = _.filter(highSeverity, (vulnerability)=>{
|
||
|
|
return vulnerability.createdAt < weekStartsAt;
|
||
|
|
});
|
||
|
|
let highDatasetForThisWeek = {
|
||
|
|
x: weekStartsAt,
|
||
|
|
y: numberOfHighVulnerabilitiesThisWeek.length
|
||
|
|
};
|
||
|
|
timelineDatasets.high.push(highDatasetForThisWeek);
|
||
|
|
// Medium severity
|
||
|
|
let numberOfMediumVulnerabilitiesThisWeek = _.filter(mediumSeverity, (vulnerability)=>{
|
||
|
|
return vulnerability.createdAt < weekStartsAt;
|
||
|
|
});
|
||
|
|
let mediumDatasetForThisWeek = {
|
||
|
|
x: weekStartsAt,
|
||
|
|
y: numberOfMediumVulnerabilitiesThisWeek.length
|
||
|
|
};
|
||
|
|
timelineDatasets.medium.push(mediumDatasetForThisWeek);
|
||
|
|
// Low severity
|
||
|
|
let numberOfLowVulnerabilitiesThisWeek = _.filter(lowSeverity, (vulnerability)=>{
|
||
|
|
return vulnerability.createdAt < weekStartsAt;
|
||
|
|
});
|
||
|
|
let lowDatasetForThisWeek = {
|
||
|
|
x: weekStartsAt,
|
||
|
|
y: numberOfLowVulnerabilitiesThisWeek.length
|
||
|
|
};
|
||
|
|
timelineDatasets.low.push(lowDatasetForThisWeek);
|
||
|
|
// Unknown severity
|
||
|
|
let numberOfUnknownVulnerabilitiesThisWeek = _.filter(unknownSeverity, (vulnerability)=>{
|
||
|
|
return vulnerability.createdAt < weekStartsAt;
|
||
|
|
});
|
||
|
|
let unknownDatasetForThisWeek = {
|
||
|
|
x: weekStartsAt,
|
||
|
|
y: numberOfUnknownVulnerabilitiesThisWeek.length
|
||
|
|
};
|
||
|
|
timelineDatasets.unknown.push(unknownDatasetForThisWeek);
|
||
|
|
}//∞
|
||
|
|
|
||
|
|
// Add data points for the current state of the vulnerability timeline
|
||
|
|
timelineDatasets.critical.push({x: Date.now(), y: criticalSeverity.length});
|
||
|
|
timelineDatasets.high.push({x: Date.now(), y: highSeverity.length});
|
||
|
|
timelineDatasets.medium.push({x: Date.now(), y: mediumSeverity.length});
|
||
|
|
timelineDatasets.low.push({x: Date.now(), y: lowSeverity.length});
|
||
|
|
timelineDatasets.unknown.push({x: Date.now(), y: unknownSeverity.length});
|
||
|
|
|
||
|
|
realDataForGraphs.timelineDatasets = timelineDatasets;
|
||
|
|
realDataForGraphs.remediationTimeline = remediationDataset;
|
||
|
|
// 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 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');
|
||
|
|
|
||
|
|
// All done.
|
||
|
|
return {
|
||
|
|
remediationTimeline: realDataForGraphs.remediationTimeline,
|
||
|
|
timelineDatasets: realDataForGraphs.timelineDatasets,
|
||
|
|
newPublishedVulnerabilities:realDataForGraphs.newPublishedVulnerabilities,//last 48 hours
|
||
|
|
criticalVulnerabilityPercentage: realDataForGraphs.criticalVulnerabilityPercentage,
|
||
|
|
highVulnerabilityPercentage: realDataForGraphs.highVulnerabilityPercentage,
|
||
|
|
numberOfVulnsBySeverity: realDataForGraphs.numberOfVulnsBySeverity,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
};
|