fleet/ee/vulnerability-dashboard/api/controllers/download-vulnerabilities-csv.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

182 lines
7.1 KiB
JavaScript

module.exports = {
friendlyName: 'Download vulnerabilities csv',
description: 'Download vulnerabilities csv file (returning a stream).',
inputs: {
minSeverity: {
description: 'Optional filter to only get vulnerabilities whose `severity` is >= the specified value.',
type: 'number',
defaultsTo: 0,
},
maxSeverity: {
description: 'Optional filter to only get vulnerabilities whose `severity` is <= the specified value.',
type: 'number',
defaultsTo: 10,
},
sortBy: {
description: 'An optional facet to sort vulnerabilities by.',
type: 'string',
isIn: [
'cveId',
'severity',
'hasKnownExploit',
'publishedAt',
'resolvedAt',
],
defaultsTo: 'publishedAt'
},
sortDirection: {
type: 'string',
isIn: [
'ASC',
'DESC',
],
defaultsTo: 'DESC'
},
page: {
description: 'The zero-indexed page number.',
type: 'number',
defaultsTo: 0
},
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',
},
pageSize: {
description: 'The number of vulnerabilities to export',
type: 'number',
defaultsTo: 9999,
},
exportType: {
description: 'The type of CSV export this will be.',
type: 'string',
isIn: [
'resolvedAndVulnerableInstalls',
'overview'
],
},
},
exits: {
success: {
outputFriendlyName: 'File',
outputDescription: 'The streaming bytes of the file.',
outputType: 'ref'
},
emptyExport: {
description: 'No matching vulnerabilities could be found with the provided filters',
responseType: 'notFound',
}
},
fn: async function ({minSeverity, maxSeverity, sortBy, sortDirection, page, pageSize, teamApid, exportType}) {
// Generate a random room name.
let roomId = await sails.helpers.strings.random();
if(this.req.isSocket) {
// Add the requesting socket to the room.
sails.sockets.join(this.req, roomId);
}
let includeResolvedInstalls = false;
// If the export type is resolvedAndVulnerableInstalls, we'll ask the get-vulnerabilities helper to include resolved install information in the affectedInstalls array.
if(exportType === 'resolvedAndVulnerableInstalls'){
includeResolvedInstalls = true;
}
// Get a report from the vulnerability helper
let report = await sails.helpers.getVulnerabilities.with({minSeverity, maxSeverity, sortBy, sortDirection, page, pageSize, teamApid, includeResolvedInstalls})
.intercept('noMatchingVulnerabilities', ()=>{
return 'emptyExport';
});
let stream = require('stream');
let csvString = '';
// Create a writeable stream we'll use to create the csvString.
let writableStream = new stream.Writable({//[?]: https://nodejs.org/api/stream.html#writable_writechunk-encoding-callback
write(chunk, encoding, callback) {
csvString += chunk.toString();
callback();
}
});
let csv = require('fast-csv');
let generatingCsv = csv.format({headers: true});
// Pass the writableStream into generatingCsv.
generatingCsv.pipe(writableStream);// [?]: https://c2fo.github.io/fast-csv/docs/formatting/methods#write
// Now build the CSV report.
for (let vulnerability of report.entries) {
// If the export type is overview, we'll add a row for every CVE in the report.
if (exportType === 'overview') {
let csvRowForThisVulnerability = {};
csvRowForThisVulnerability['CVE ID'] = vulnerability.cveId;
csvRowForThisVulnerability['Severity'] = vulnerability.severity;
csvRowForThisVulnerability['Has known exploit'] = !!vulnerability.hasKnownExploit;
csvRowForThisVulnerability['CVE description'] = vulnerability.cveDescription ? vulnerability.cveDescription : 'N/A';
csvRowForThisVulnerability['Publish date'] = new Date(vulnerability.publishedAt);
csvRowForThisVulnerability['Resolved Date'] = vulnerability.resolvedAt !== 0 ? new Date(vulnerability.resolvedAt) : 'N/A';
csvRowForThisVulnerability['Number of affected hosts'] = vulnerability.numAffectedHosts;
generatingCsv.write(csvRowForThisVulnerability);
} else if (exportType === 'resolvedAndVulnerableInstalls') {
// If the export type is resolvedAndVulnerableInstalls, we'll add a row for every software install included the report.
// To do this, we'll iterate through the hosts affected by this vulnerability, and add a row for each vulnerable install record that is affected by this CVE on this host.
for (let host of vulnerability.affectedHosts) {
let installsForThisHost = vulnerability.affectedInstalls.filter((install) => {
return install.affectedHost === host.id;
});
for (let install of installsForThisHost) {
let csvRowForThisInstall = {};
csvRowForThisInstall['CVE ID'] = vulnerability.cveId;
csvRowForThisInstall['Severity'] = vulnerability.severity;
csvRowForThisInstall['Has known exploit'] = !!vulnerability.hasKnownExploit;
csvRowForThisInstall['CVE description'] = vulnerability.cveDescription ? vulnerability.cveDescription : 'N/A';
csvRowForThisInstall['Publish date'] = new Date(vulnerability.publishedAt);
csvRowForThisInstall['Resolved Date'] = install.uninstalledAt !== 0 ? new Date(install.uninstalledAt) : 'N/A';
csvRowForThisInstall['Affected software name'] = install.name;
csvRowForThisInstall['Affected software version'] = install.version;
csvRowForThisInstall['Resolved in version'] = install.resolvedInVersion ? install.resolvedInVersion : 'N/A';
csvRowForThisInstall['Affected software URL'] = install.url;
csvRowForThisInstall['Vulnerable software detected on'] = new Date(install.installedAt);
csvRowForThisInstall['Host Fleet URL'] = sails.config.custom.fleetBaseUrl + '/hosts/' + encodeURIComponent(host.fleetApid);
csvRowForThisInstall['Host display name'] = host.displayName;
csvRowForThisInstall['Host team'] = host.teamDisplayName;
csvRowForThisInstall['Host serial number'] = host.hardwareSerialNumber;
csvRowForThisInstall['Host UUID'] = host.uuid;
csvRowForThisInstall['Host team ID'] = host.teamApid !== 0 ? host.teamApid : 'N/A';
generatingCsv.write(csvRowForThisInstall);
}//∞
}//∞
}
}//∞
generatingCsv.end();
// After the the csvString has been generated by the writableStream, broadcast the csvString to the requesting user's socket.
writableStream.on('finish', () => {
if(this.req.isSocket){
// Note: we're sending the cveId with the cvsString, this is so we can set the filename in our frontend code.
sails.sockets.broadcast(roomId, 'csvExportDone', csvString);
// Unsubscribe the socket from the room.
sails.sockets.leave(this.req, roomId);
} else {
return csvString;
}
});
}
};