207 lines
6.3 KiB
JavaScript
207 lines
6.3 KiB
JavaScript
import Utils from '../Utils';
|
||
import { CheckState } from './Check';
|
||
import * as CheckGroups from './CheckGroups/CheckGroups';
|
||
import logger from '../logger';
|
||
import { isArray, isBoolean } from 'lodash';
|
||
|
||
/**
|
||
* The security check runner.
|
||
* @memberof module:SecurityCheck
|
||
*/
|
||
class CheckRunner {
|
||
/**
|
||
* The security check runner.
|
||
* @param {Object} [config] The configuration options.
|
||
* @param {Boolean} [config.enableCheck=false] Is true if Parse Server should report weak security settings.
|
||
* @param {Boolean} [config.enableCheckLog=false] Is true if the security check report should be written to logs.
|
||
* @param {Object} [config.checkGroups] The check groups to run. Default are the groups defined in `./CheckGroups/CheckGroups.js`.
|
||
*/
|
||
constructor(config = {}) {
|
||
this._validateParams(config);
|
||
const { enableCheck = false, enableCheckLog = false, checkGroups = CheckGroups } = config;
|
||
this.enableCheck = enableCheck;
|
||
this.enableCheckLog = enableCheckLog;
|
||
this.checkGroups = checkGroups;
|
||
}
|
||
|
||
/**
|
||
* Runs all security checks and returns the results.
|
||
* @params
|
||
* @returns {Object} The security check report.
|
||
*/
|
||
async run({ version = '1.0.0' } = {}) {
|
||
// Instantiate check groups
|
||
const groups = Object.values(this.checkGroups)
|
||
.filter(c => typeof c === 'function')
|
||
.map(CheckGroup => new CheckGroup());
|
||
|
||
// Run checks
|
||
groups.forEach(group => group.run());
|
||
|
||
// Generate JSON report
|
||
const report = this._generateReport({ groups, version });
|
||
|
||
// If report should be written to logs
|
||
if (this.enableCheckLog) {
|
||
this._logReport(report);
|
||
}
|
||
return report;
|
||
}
|
||
|
||
/**
|
||
* Generates a security check report in JSON format with schema:
|
||
* ```
|
||
* {
|
||
* report: {
|
||
* version: "1.0.0", // The report version, defines the schema
|
||
* state: "fail" // The disjunctive indicator of failed checks in all groups.
|
||
* groups: [ // The check groups
|
||
* {
|
||
* name: "House", // The group name
|
||
* state: "fail" // The disjunctive indicator of failed checks in this group.
|
||
* checks: [ // The checks
|
||
* title: "Door locked", // The check title
|
||
* state: "fail" // The check state
|
||
* warning: "Anyone can enter your house." // The warning.
|
||
* solution: "Lock your door." // The solution.
|
||
* ]
|
||
* },
|
||
* ...
|
||
* ]
|
||
* }
|
||
* }
|
||
* ```
|
||
* @param {Object} params The parameters.
|
||
* @param {Array<CheckGroup>} params.groups The check groups.
|
||
* @param {String} params.version: The report schema version.
|
||
* @returns {Object} The report.
|
||
*/
|
||
_generateReport({ groups, version }) {
|
||
// Create report template
|
||
const report = {
|
||
report: {
|
||
version,
|
||
state: CheckState.success,
|
||
groups: [],
|
||
},
|
||
};
|
||
|
||
// Identify report version
|
||
switch (version) {
|
||
case '1.0.0':
|
||
default:
|
||
// For each check group
|
||
for (const group of groups) {
|
||
// Create group report
|
||
const groupReport = {
|
||
name: group.name(),
|
||
state: CheckState.success,
|
||
checks: [],
|
||
};
|
||
|
||
// Create check reports
|
||
groupReport.checks = group.checks().map(check => {
|
||
const checkReport = {
|
||
title: check.title,
|
||
state: check.checkState(),
|
||
};
|
||
if (check.checkState() == CheckState.fail) {
|
||
checkReport.warning = check.warning;
|
||
checkReport.solution = check.solution;
|
||
report.report.state = CheckState.fail;
|
||
groupReport.state = CheckState.fail;
|
||
}
|
||
return checkReport;
|
||
});
|
||
|
||
report.report.groups.push(groupReport);
|
||
}
|
||
}
|
||
return report;
|
||
}
|
||
|
||
/**
|
||
* Logs the security check report.
|
||
* @param {Object} report The report to log.
|
||
*/
|
||
_logReport(report) {
|
||
// Determine log level depending on whether any check failed
|
||
const log =
|
||
report.report.state == CheckState.success ? s => logger.info(s) : s => logger.warn(s);
|
||
|
||
// Declare output
|
||
const indent = ' ';
|
||
let output = '';
|
||
let checksCount = 0;
|
||
let failedChecksCount = 0;
|
||
let skippedCheckCount = 0;
|
||
|
||
// Traverse all groups and checks for compose output
|
||
for (const group of report.report.groups) {
|
||
output += `\n- ${group.name}`;
|
||
|
||
for (const check of group.checks) {
|
||
checksCount++;
|
||
output += `\n${indent}${this._getLogIconForState(check.state)} ${check.title}`;
|
||
|
||
if (check.state == CheckState.fail) {
|
||
failedChecksCount++;
|
||
output += `\n${indent}${indent}Warning: ${check.warning}`;
|
||
output += ` ${check.solution}`;
|
||
} else if (check.state == CheckState.none) {
|
||
skippedCheckCount++;
|
||
output += `\n${indent}${indent}Test did not execute, this is likely an internal server issue, please report.`;
|
||
}
|
||
}
|
||
}
|
||
|
||
output =
|
||
`\n###################################` +
|
||
`\n# #` +
|
||
`\n# Parse Server Security Check #` +
|
||
`\n# #` +
|
||
`\n###################################` +
|
||
`\n` +
|
||
`\n${
|
||
failedChecksCount > 0 ? 'Warning: ' : ''
|
||
}${failedChecksCount} weak security setting(s) found${failedChecksCount > 0 ? '!' : ''}` +
|
||
`\n${checksCount} check(s) executed` +
|
||
`\n${skippedCheckCount} check(s) skipped` +
|
||
`\n` +
|
||
`${output}`;
|
||
|
||
// Write log
|
||
log(output);
|
||
}
|
||
|
||
/**
|
||
* Returns an icon for use in the report log output.
|
||
* @param {CheckState} state The check state.
|
||
* @returns {String} The icon.
|
||
*/
|
||
_getLogIconForState(state) {
|
||
switch (state) {
|
||
case CheckState.success:
|
||
return '✅';
|
||
case CheckState.fail:
|
||
return '❌';
|
||
default:
|
||
return 'ℹ️';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Validates the constructor parameters.
|
||
* @param {Object} params The parameters to validate.
|
||
*/
|
||
_validateParams(params) {
|
||
Utils.validateParams(params, {
|
||
enableCheck: { t: 'boolean', v: isBoolean, o: true },
|
||
enableCheckLog: { t: 'boolean', v: isBoolean, o: true },
|
||
checkGroups: { t: 'array', v: isArray, o: true },
|
||
});
|
||
}
|
||
}
|
||
|
||
module.exports = CheckRunner;
|