2021-03-10 20:19:28 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* @module SecurityCheck
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
*/
|
|
|
|
|
|
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) {
|
2021-09-03 01:23:15 +02:00
|
|
|
|
this._logReport(report);
|
2021-03-10 20:19:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
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,
|
2021-09-03 01:23:15 +02:00
|
|
|
|
groups: [],
|
|
|
|
|
|
},
|
2021-03-10 20:19:28 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 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: [],
|
2021-09-03 01:23:15 +02:00
|
|
|
|
};
|
2021-03-10 20:19:28 +01:00
|
|
|
|
|
|
|
|
|
|
// 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
|
2021-09-03 01:23:15 +02:00
|
|
|
|
const log =
|
|
|
|
|
|
report.report.state == CheckState.success ? s => logger.info(s) : s => logger.warn(s);
|
2021-03-10 20:19:28 +01:00
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2021-09-03 01:23:15 +02:00
|
|
|
|
output += `\n- ${group.name}`;
|
2021-03-10 20:19:28 +01:00
|
|
|
|
|
|
|
|
|
|
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` +
|
2021-09-03 01:23:15 +02:00
|
|
|
|
`\n${
|
|
|
|
|
|
failedChecksCount > 0 ? 'Warning: ' : ''
|
|
|
|
|
|
}${failedChecksCount} weak security setting(s) found${failedChecksCount > 0 ? '!' : ''}` +
|
2021-03-10 20:19:28 +01:00
|
|
|
|
`\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) {
|
2021-09-03 01:23:15 +02:00
|
|
|
|
case CheckState.success:
|
|
|
|
|
|
return '✅';
|
|
|
|
|
|
case CheckState.fail:
|
|
|
|
|
|
return '❌';
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 'ℹ️';
|
2021-03-10 20:19:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 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;
|