diff --git a/config_example.js b/config_example.js index c89d6c26b7..f4380f0b4f 100644 --- a/config_example.js +++ b/config_example.js @@ -11,6 +11,14 @@ module.exports = { // session_token: process.env.AWS_SESSION_TOKEN || '', // plugins_remediate: ['bucketEncryptionInTransit'] }, + aws_remediate: { + // OPTION 1: If using a credential JSON file, enter the path below + // credential_file: '/path/to/file.json', + // OPTION 2: If using hard-coded credentials, enter them below + // access_key: process.env.AWS_ACCESS_KEY_ID || '', + // secret_access_key: process.env.AWS_SECRET_ACCESS_KEY || '', + // session_token: process.env.AWS_SESSION_TOKEN || '', + }, azure: { // OPTION 1: If using a credential JSON file, enter the path below // credential_file: '/path/to/file.json', @@ -20,6 +28,15 @@ module.exports = { // directory_id: process.env.AZURE_DIRECTORY_ID || '', // subscription_id: process.env.AZURE_SUBSCRIPTION_ID || '' }, + azure_remediate: { + // OPTION 1: If using a credential JSON file, enter the path below + // credential_file: '/path/to/file.json', + // OPTION 2: If using hard-coded credentials, enter them below + // application_id: process.env.AZURE_APPLICATION_ID || '', + // key_value: process.env.AZURE_KEY_VALUE || '', + // directory_id: process.env.AZURE_DIRECTORY_ID || '', + // subscription_id: process.env.AZURE_SUBSCRIPTION_ID || '' + }, google: { // OPTION 1: If using a credential JSON file, enter the path below // credential_file: process.env.GOOGLE_APPLICATION_CREDENTIALS || '/path/to/file.json', diff --git a/engine.js b/engine.js index 63b793020b..939d2132d8 100644 --- a/engine.js +++ b/engine.js @@ -2,7 +2,17 @@ var async = require('async'); var exports = require('./exports.js'); var suppress = require('./postprocess/suppress.js'); var output = require('./postprocess/output.js'); - +var azureHelper = require('./helpers/azure/auth.js'); + +function runAuth(settings, remediateConfig, callback) { + if (settings.cloud && settings.cloud == 'azure') { + azureHelper.login(remediateConfig, function(err, loginData) { + if (err) return (callback(err)); + remediateConfig.token = loginData.token; + return callback(); + }); + } else callback(); +} /** * The main function to execute CloudSploit scans. * @param cloudConfig The configuration for the cloud provider. @@ -138,91 +148,102 @@ var engine = function(cloudConfig, settings) { console.log('INFO: Analysis complete. Scan report to follow...'); var maximumStatus = 0; - - async.mapValuesLimit(plugins, 10, function(plugin, key, pluginDone) { - if (skippedPlugins.indexOf(key) > -1) return pluginDone(null, 0); - - var postRun = function(err, results) { - if (err) return console.log(`ERROR: ${err}`); - if (!results || !results.length) { - console.log(`Plugin ${plugin.title} returned no results. There may be a problem with this plugin.`); - } else { - for (var r in results) { - // If we have suppressed this result, then don't process it - // so that it doesn't affect the return code. - if (suppressionFilter([key, results[r].region || 'any', results[r].resource || 'any'].join(':'))) { - continue; - } - - var complianceMsg = []; - if (settings.compliance && settings.compliance.length) { - settings.compliance.forEach(function(c) { - if (plugin.compliance && plugin.compliance[c]) { - complianceMsg.push(`${c.toUpperCase()}: ${plugin.compliance[c]}`); - } - }); - } - complianceMsg = complianceMsg.join('; '); - if (!complianceMsg.length) complianceMsg = null; - - // Write out the result (to console or elsewhere) - outputHandler.writeResult(results[r], plugin, key, complianceMsg); - - // Add this to our tracking for the worst status to calculate - // the exit code - maximumStatus = Math.max(maximumStatus, results[r].status); - // Remediation - if (settings.remediate && settings.remediate.length) { - if (settings.remediate.indexOf(key) > -1) { - if (results[r].status === 2) { - var resource = results[r].resource; - var event = {}; - event.region = results[r].region; - event['remediation_file'] = {}; - event['remediation_file'] = initializeFile(event['remediation_file'], 'execute', key, resource); - plugin.remediate(cloudConfig, collection, event, resource, (err, result) => { - if (err) return console.log(err); - return console.log(result); - }); + + function executePlugins(cloudRemediateConfig) { + async.mapValuesLimit(plugins, 10, function(plugin, key, pluginDone) { + if (skippedPlugins.indexOf(key) > -1) return pluginDone(null, 0); + + var postRun = function(err, results) { + if (err) return console.log(`ERROR: ${err}`); + if (!results || !results.length) { + console.log(`Plugin ${plugin.title} returned no results. There may be a problem with this plugin.`); + } else { + for (var r in results) { + // If we have suppressed this result, then don't process it + // so that it doesn't affect the return code. + if (suppressionFilter([key, results[r].region || 'any', results[r].resource || 'any'].join(':'))) { + continue; + } + + var complianceMsg = []; + if (settings.compliance && settings.compliance.length) { + settings.compliance.forEach(function(c) { + if (plugin.compliance && plugin.compliance[c]) { + complianceMsg.push(`${c.toUpperCase()}: ${plugin.compliance[c]}`); + } + }); + } + complianceMsg = complianceMsg.join('; '); + if (!complianceMsg.length) complianceMsg = null; + + // Write out the result (to console or elsewhere) + outputHandler.writeResult(results[r], plugin, key, complianceMsg); + + // Add this to our tracking for the worst status to calculate + // the exit code + maximumStatus = Math.max(maximumStatus, results[r].status); + // Remediation + if (settings.remediate && settings.remediate.length) { + if (settings.remediate.indexOf(key) > -1) { + if (results[r].status === 2) { + var resource = results[r].resource; + var event = {}; + event.region = results[r].region; + event['remediation_file'] = {}; + event['remediation_file'] = initializeFile(event['remediation_file'], 'execute', key, resource); + plugin.remediate(cloudRemediateConfig, collection, event, resource, (err, result) => { + if (err) return console.log(err); + return console.log(result); + }); + } } } } + } - + setTimeout(function() { pluginDone(err, maximumStatus); }, 0); + }; + + if (plugin.asl) { + console.log(`INFO: Using custom ASL for plugin: ${plugin.title}`); + // Inject APIs and resource maps + plugin.asl.apis = plugin.apis; + var aslConfig = require('./helpers/asl/config.json'); + var aslVersion = plugin.asl.version ? plugin.asl.version : aslConfig.current_version; + let aslRunner; + try { + aslRunner = require(`./helpers/asl/asl-${aslVersion}.js`); + + } catch (e) { + postRun('Error: ASL: Wrong ASL Version: ', e); + } + + aslRunner(collection, plugin.asl, resourceMap, postRun); + } else { + plugin.run(collection, settings, postRun); } - setTimeout(function() { pluginDone(err, maximumStatus); }, 0); - }; - - if (plugin.asl) { - console.log(`INFO: Using custom ASL for plugin: ${plugin.title}`); - // Inject APIs and resource maps - plugin.asl.apis = plugin.apis; - var aslConfig = require('./helpers/asl/config.json'); - var aslVersion = plugin.asl.version ? plugin.asl.version : aslConfig.current_version; - let aslRunner; - try { - aslRunner = require(`./helpers/asl/asl-${aslVersion}.js`); - - } catch (e) { - postRun('Error: ASL: Wrong ASL Version: ', e); + }, function(err) { + if (err) return console.log(err); + // console.log(JSON.stringify(collection, null, 2)); + outputHandler.close(); + if (settings.exit_code) { + // The original cloudsploit always has a 0 exit code. With this option, we can have + // the exit code depend on the results (useful for integration with CI systems) + console.log(`INFO: Exiting with exit code: ${maximumStatus}`); + process.exitCode = maximumStatus; } - - aslRunner(collection, plugin.asl, resourceMap, postRun); - } else { - plugin.run(collection, settings, postRun); - } - }, function(err) { - if (err) return console.log(err); - // console.log(JSON.stringify(collection, null, 2)); - outputHandler.close(); - if (settings.exit_code) { - // The original cloudsploit always has a 0 exit code. With this option, we can have - // the exit code depend on the results (useful for integration with CI systems) - console.log(`INFO: Exiting with exit code: ${maximumStatus}`); - process.exitCode = maximumStatus; - } - console.log('INFO: Scan complete'); - }); + console.log('INFO: Scan complete'); + }); + } + + if (settings.remediate && settings.remediate.length && cloudConfig.remediate) { + runAuth(settings, cloudConfig.remediate, function(err) { + if (err) return console.log(err); + executePlugins(cloudConfig.remediate); + }); + } else { + executePlugins(cloudConfig); + } }); }; diff --git a/index.js b/index.js index 8b08674bce..7b5d564fad 100755 --- a/index.js +++ b/index.js @@ -205,5 +205,42 @@ if (config.credentials.aws.credential_file) { process.exit(1); } +if (settings.remediate && settings.remediate.length) { + if (!config.credentials[`${settings.cloud}_remediate`]) { + console.error('ERROR: No credentials provided for remediation.'); + process.exit(1); + } + if (config.credentials.aws_remediate && config.credentials.aws_remediate.credential_file) { + cloudConfig.remediate = loadHelperFile(config.credentials.aws_remediate.credential_file); + if (!cloudConfig.remediate || !cloudConfig.remediate.accessKeyId || !cloudConfig.remediate.secretAccessKey) { + console.error('ERROR: AWS credential file for remediation does not have accessKeyId or secretAccessKey properties'); + process.exit(1); + } + } else if (config.credentials.aws_remediate && config.credentials.aws_remediate.access_key) { + checkRequiredKeys(config.credentials.aws_remediate, ['secret_access_key']); + cloudConfig.remediate = { + accessKeyId: config.credentials.aws_remediate.access_key, + secretAccessKey: config.credentials.aws_remediate.secret_access_key, + sessionToken: config.credentials.aws_remediate.session_token + }; + } else if (config.credentials.azure_remediate && config.credentials.azure_remediate.credential_file) { + cloudConfig.remediate = loadHelperFile(config.credentials.azure_remediate.credential_file); + if (!cloudConfig.remediate || !cloudConfig.remediate.ApplicationID || !cloudConfig.remediate.KeyValue || !cloudConfig.remediate.DirectoryID || !cloudConfig.remediate.SubscriptionID) { + console.error('ERROR: Azure credential file for remediation does not have ApplicationID, KeyValue, DirectoryID, or SubscriptionID'); + process.exit(1); + } + } else if (config.credentials.azure_remediate && config.credentials.azure_remediate.application_id) { + checkRequiredKeys(config.credentials.azure_remediate, ['key_value', 'directory_id', 'subscription_id']); + cloudConfig.remediate = { + ApplicationID: config.credentials.azure_remediate.application_id, + KeyValue: config.credentials.azure_remediate.key_value, + DirectoryID: config.credentials.azure_remediate.directory_id, + SubscriptionID: config.credentials.azure_remediate.subscription_id + }; + } else { + console.error('ERROR: Config file does not contain any valid credential configs for remediation.'); + process.exit(1); + } +} // Now execute the scans using the defined configuration information. engine(cloudConfig, settings);