spartan-shield
Version:
nodejs project to package and configure common security middleware.
257 lines (253 loc) • 10.7 kB
JavaScript
var commander = require('commander')
const text = require('cfonts')
var { spawn, spawnSync } = require('child_process')
var chalk = require('chalk')
var hash
var p = require('./policy')
var a = p.read(__dirname +'/answers.json')
var pkg = p.read(__dirname +'/package.json')
var short = require(__dirname + '/question').nq
var long = require(__dirname + '/question').lnq
var confirmDelete = require(__dirname + '/question').confirmDelete
var confirmSettings = require(__dirname + '/question').confirmSettings
var confirmDeleteForce = require(__dirname + '/question').confirmDeleteForce
var restoreDefault = require(__dirname + '/question').restoreDefault
var inquirer = require('inquirer')
var bp = require(__dirname + '/boilerplate')
var all = ['snyk', 'bcrypt', 'passport', 'rbac', 'cors', 'winston', 'mime-types', 'js-cookie', 'cookie-parser', 'helmet', 'mongodb', 'csurf', 'validator', 'joi', 'redis', 'forms']
async function ask (q) {
var answers = await inquirer.prompt(q)
return answers
}
/**
* @name integrity
* @description generates a sha-384 hash of the file indicated by the parameter
* @param {Object} p policy location
* @returns {String} hash string
*/
function integrity (p) {
try {
const sha = spawn('shasum', ['-b', '-a', '384', p])
const xxd = spawn('xxd', ['-r', '-p'])
const b = spawn('base64')
sha.stdout.pipe(xxd.stdin)
xxd.stdout.pipe(b.stdin)
b.stdout.on('data', (data) => {
console.log((`SHA-384 hash of ${chalk.yellow(p)}: ${chalk.magenta(data)}`))
hash = data
return hash
})
b.stderr.on('data', (data) => {
console.error(`Error hashing security.json: ${data}`)
})
} catch (e) {
throw new Error(`Could not calculate hash of security.json, ${e}`)
}
}
function nextSteps (modules) {
var npmCommand = chalk.bold.yellow(`npm install ${modules}`)
var url = chalk.green(`https://docs.spartan-security.io/`)
var conf = chalk.bold.cyan(`javascriptEnabled: false`)
var whatsNext = `Next steps: \n\t1. Install necessary packages (copy/paste at command prompt inside project directory): \n\t\t\`${npmCommand}\n\t\t${chalk.cyan.dim('Psst! If you haven\'t already, install eslint-plugin-security to prevent vulnerabilties from being written into your code')}\`\n\t2.Disable Javascript execution in Mongo. \n\t\tAdd the following line inside the ${chalk.red('security section')} to \`${chalk.red.underline('mongod.conf')}\`: ${conf}\n\t\t${chalk.red.dim('Psst! Be sure to save the file and restart mongod!')}\n\t3.Wire in \`security.js\` components to your app. \n\t\tCheck ${url} for additional information\n`
return whatsNext
}
/**
* @name begin
* @description processes given to the commander module
* @param {String} cmd
* @param {String} [opt=[]]
* @return {void} files are written to disk
*/
async function begin (cmd, opt = []) {
// default
if ((cmd === 'init' && opt === 'y') || (cmd === 'init' && opt === 'Y') || cmd === 'default') {
// if commander.init & commander options are yes/Yes or the user types 'default' a default policy will be created
try{
var basic = await p.create('default') // creates security.json in the current directory with default settings
console.log(basic[1]) // success message
integrity(basic[2]) // generates a sha hash of the policy file indicated by this input
var boiler = await bp.writeBoilerplate(basic[0])
console.log(boiler.message + '\n')
integrity(boiler.pathToFile)
await console.log(nextSteps(boiler.modules))
} catch (e){
console.log('problem writing files ' + e)
}
} else if (cmd === 'update') { // update
if (opt === 'L') {
var l = await ask(long)
console.log(l)
var cl = await ask(confirmSettings)
if (cl.settingsConfirm) { p.create(cmd, l) } else { begin(cmd, 'L') }
} else {
var k = await ask(short)
// p.create(cmd, k);
console.log(k)
var ck = await ask(confirmSettings)
if (ck.settingsConfirm) {
var up = await p.create(cmd, k)
console.log(up[1])
integrity(up[2])
var upBp = await bp.writeBoilerplate(up[0])
integrity(upBp.pathToFile)
} else { begin(cmd) }
}
} else if (cmd === 'force') { // force
try {
var finishedPolicy = p.read('./security.json')
var f = await bp.writeBoilerplate(finishedPolicy)
console.log('The following modules should be installed as a result of the force command:')
console.log(chalk.yellow(bp.matches(all, f.modules)))
// removeModules(oldModules);
console.log('The following modules should be removed as a result of the force command: ')
console.log(chalk.red(bp.diff(all, f.modules)))
integrity('security.js')
} catch (e) {
console.log('No policy file found. Please run `_spartan init` to build your policy first.')
}
} else if (cmd === 'delete') { // delete
if (opt === 'F') {
var forceConfirm = await ask(confirmDeleteForce)
if (forceConfirm.deleteForceConfirm) {
p.deletePolicy()
bp.removeModules()
} else { console.log('Policy Not deleted\n') }
} else {
var d = await ask(confirmDelete)
if (d.deleteConfirm) { p.deletePolicy() } else { console.log('Policy Not deleted\n') }
}
} else if (cmd === 'no-overwrite') { // no-overwrite
try {
var nope = await ask(short)
console.log(nope)
var bop = await ask(confirmSettings)
if (bop.settingsConfirm) {
p.create(cmd, a)
} else {
begin(cmd)
}
} catch (e) {
throw new Error('Could not create a separate policy file')
}
} else if (cmd === 'set-as-default') { // set-as-default
try {
var newPolicy = p.strip(p.read('./security.json'))
p.wp(newPolicy, `${__dirname}/security-default.json`)
var successMessage = 'Successfully replaced default policy'
integrity(`${__dirname}/security-default.json`)
console.log(successMessage)
return successMessage
} catch (e) {
console.error('No policy file found. Please run `_spartan init` to build your policy first.')
}
} else if (cmd === 'integrity') {
integrity('./security.json')
integrity('./security.js')
} else if (cmd === 'resetDefault') {
var r = await ask(restoreDefault)
if (r.restore) {
const downloadUrl = 'https://raw.githubusercontent.com/darkmsph1t/_spartan-factory-default/master/security-default.json'
const checkHashUrl = 'https://github.com/darkmsph1t/_spartan-factory-default'
console.log(`Restoring security-default.json from ${chalk.yellow(downloadUrl)}...`)
try {
var reset = spawnSync('wget', [downloadUrl, '-O', `${__dirname}/security-default.json`], { stdio: 'pipe' })
console.log(reset.output[2].toString())
integrity(__dirname + '/security-default.json')
console.log(`Default file has been restored. Check ${chalk.yellow(checkHashUrl)} to validate integrity of the file before proceeding`)
} catch (err) {
console.log(`There was a problem restoring the default policy to factory settings, ${err}. Download the policy directly from ${downloadUrl}`)
}
}
} else if (cmd === 'deploy') {
console.log('So you\'re ready to deploy your app! Awesome! This feature is under development, but check back soon!')
} else {
var i = await ask(short)
console.log(i)
var z = await ask(confirmSettings)
if (!z.settingsConfirm) {
begin('init')
} else {
var full = p.create('init', i)
console.log(full[1])
integrity(full[2])
var fullBp = await bp.writeBoilerplate(full[0])
// console.log(`The following modules were installed: \n`);
// for (var fin = 0; fin < (fullBp.modules).length; fin ++){
// console.log(`${chalk.yellow(fullBp.modules[fin])}`);
// }
console.log(fullBp.message + '\n')
integrity(fullBp.pathToFile)
console.log(nextSteps(fullBp.modules))
}
}
}
text.say('_spartan', {
font: 'simple',
align: 'left',
colors: ['red'],
space: false
})
text.say('by @darkmsph1t', {
font: 'console',
align: 'center',
colors: ['cyan'],
space: false
})
commander
.version(pkg.version, '-v, --version')
.option('init, [y][Y][L]', 'Initialize a new policy. Use y | Y for defaults. Use L for long-form questions\n')
.option('-D, --default', 'Builds a preconfigured, default security policy and security.js installed modules\n')
.option('-u, --update [L]', 'Update the existing policy. Use the L flag to update using long-form questions\n')
.option('-f, --force ', 'Force a complete regeneration of the boilerplate code defined in security.js. \n' +
'\t\t\t Typically used after making a manual adjustment to the security.json file.\n')
.option('--no-overwrite [L]', 'Creates a new policy without overwriting the existing policy. \n' +
'\t\t\t Use L for long-form questions\n')
.option('--del, --delete [F]', 'Remove the policy and boilerplate code. Use F option to remove any installed modules\n')
.option('--set-as-default', 'Sets the current policy as the default policy\n')
.option('-R, --reset-default', 'Restores the default policy to factory settings. Requires wget\n')
.option('-i, --integrity', 'SHA-384 hash of existing policy')
.option('--deploy', 'Deploys the app using the specification from security.json')
.parse(process.argv)
if (commander.version && pkg === undefined) {
pkg.version = '0.0.1'
console.log('Couldn\'t find package.json file. Have you already run `npm init`?')
}
if (commander.init) {
if (commander.init === 'y' || commander.init === 'Y' || commander.init === 'L') {
begin('init', commander.init)
} else {
begin('init')
}
} else if (commander.default) {
begin('default')
} else if (commander.update) {
if (!p.read('./security.json')) {
throw new Error('No policy file found')
} else {
begin('update')
}
} else if (commander.force) {
begin('force')
} else if (!commander.overwrite) {
begin('no-overwrite')
} else if (commander.delete) {
begin('delete')
if (commander.delete[0] === 'F') {
begin('delete', 'F')
}
} else if (commander.setAsDefault) {
begin('set-as-default')
} else if (commander.integrity) {
begin('integrity')
} else if (commander.resetDefault) {
begin('resetDefault')
} else if (commander.deploy) {
begin('deploy')
} else if (commander.args.length === 0) { commander.help() } else {
console.log('That is not an avaiable option')
}
module.exports.ask = ask
module.exports.begin = begin
module.exports.integrity = integrity