Files
kami-parse-server/src/Security/CheckRunner.js

207 lines
6.3 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;