├── .gitignore ├── .jshintrc ├── .npmignore ├── LICENSE.md ├── README.md ├── actions.js ├── cfnConfig.json ├── components ├── complianceTest │ └── tester │ │ ├── event.json │ │ ├── handler.js │ │ └── s-function.json ├── configRules │ ├── ec2CidrEgress │ │ ├── event.json │ │ ├── handler.js │ │ └── s-function.json │ ├── ec2CidrIngress │ │ ├── event.json │ │ ├── handler.js │ │ └── s-function.json │ ├── iamUserInlinePolicy │ │ ├── event.json │ │ ├── handler.js │ │ └── s-function.json │ ├── iamUserMFA │ │ ├── event.json │ │ ├── handler.js │ │ └── s-function.json │ └── iamUserManagedPolicy │ │ ├── event.json │ │ ├── handler.js │ │ └── s-function.json ├── lib │ ├── aws.js │ ├── config.js │ ├── ec2.js │ ├── global.js │ ├── iam.js │ ├── rules.js │ └── template.js └── package.json ├── examples └── pipelineIntegration │ ├── lib │ ├── context.js │ └── remoteRunner.js │ └── security-test.js ├── gulpfile.js ├── otherResources ├── config-rule-resources.json └── config-service-resources.json ├── package.json ├── pipeline └── handler.js ├── s-project.json ├── s-resources-cf.json ├── s-templates.json └── tests ├── ec2-tests.js ├── iam-tests.js ├── lib ├── context.js ├── runner.js └── utils.js └── tester-tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | dist 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 29 | node_modules 30 | 31 | #IDE Stuff 32 | **/.idea 33 | .vscode 34 | .history 35 | 36 | #OS STUFF 37 | .DS_Store 38 | .tmp 39 | 40 | #SERVERLESS STUFF 41 | admin.env 42 | .env 43 | _meta 44 | 45 | #BOOTSTRAP STUFF 46 | pipeline/bootstrap 47 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "latedef" : false, // true: Require variables/functions to be defined before being used 16 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 20 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 21 | "plusplus" : false, // true: Prohibit use of `++` and `--` 22 | "quotmark" : true, // Quotation mark consistency: 23 | // false : do nothing (default) 24 | // true : ensure whatever is used is consistent 25 | // "single" : require single quotes 26 | // "double" : require double quotes 27 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 28 | "unused" : true, // Unused variables: 29 | // true : all variables, last function parameter 30 | // "vars" : all variables only 31 | // "strict" : all variables, all function parameters 32 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 33 | "maxparams" : false, // {int} Max number of formal params allowed per function 34 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 35 | "maxstatements" : false, // {int} Max number statements per function 36 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 37 | "maxlen" : false, // {int} Max number of characters per line 38 | "varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed. 39 | 40 | // Relaxing 41 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 42 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 43 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 44 | "eqnull" : false, // true: Tolerate use of `== null` 45 | "esversion" : 6, // {int} Specify the ECMAScript version to which the code must adhere. 46 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 47 | // (ex: `for each`, multiple try/catch, function expression…) 48 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 49 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 50 | "funcscope" : false, // true: Tolerate defining variables inside control statements 51 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 52 | "iterator" : false, // true: Tolerate using the `__iterator__` property 53 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 54 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 55 | "laxcomma" : false, // true: Tolerate comma-first style coding 56 | "loopfunc" : false, // true: Tolerate functions being defined in loops 57 | "multistr" : false, // true: Tolerate multi-line strings 58 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 59 | "notypeof" : false, // true: Tolerate invalid typeof operator values 60 | "proto" : false, // true: Tolerate using the `__proto__` property 61 | "scripturl" : false, // true: Tolerate script-targeted URLs 62 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 63 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 64 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 65 | "validthis" : false, // true: Tolerate using this in a non-constructor function 66 | 67 | // Environments 68 | "browser" : true, // Web Browser (window, document, etc) 69 | "browserify" : false, // Browserify (node.js code in the browser) 70 | "couch" : false, // CouchDB 71 | "devel" : true, // Development/debugging (alert, confirm, etc) 72 | "dojo" : false, // Dojo Toolkit 73 | "jasmine" : false, // Jasmine 74 | "jquery" : false, // jQuery 75 | "mocha" : true, // Mocha 76 | "mootools" : false, // MooTools 77 | "node" : true, // Node.js 78 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 79 | "phantom" : false, // PhantomJS 80 | "prototypejs" : false, // Prototype and Scriptaculous 81 | "qunit" : false, // QUnit 82 | "rhino" : false, // Rhino 83 | "shelljs" : false, // ShellJS 84 | "typed" : false, // Globals for typed array constructions 85 | "worker" : false, // Web Workers 86 | "wsh" : false, // Windows Scripting Host 87 | "yui" : false, // Yahoo User Interface 88 | 89 | // Custom Globals 90 | "globals" : {} // additional predefined global variables 91 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | dist 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 29 | node_modules 30 | 31 | #IDE Stuff 32 | **/.idea 33 | 34 | #OS STUFF 35 | .DS_Store 36 | .tmp 37 | 38 | #SERVERLESS STUFF 39 | admin.env 40 | .env 41 | _meta -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2020 Stelligent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # config-rule-status 2 | 3 | ## What it Does 4 | - Setup resource monitoring using AWS Config. 5 | - Create security compliance rules using AWS Config Rules, including both AWS-managed and custom rules. 6 | - Create AWS Lambda functions that implement infrastructure security tests (for the custom rules). 7 | - Create an AWS Lambda function to aggregate the Config Rule compliance statuses and return an overall "PASS" or "FAIL". This function is designed to be used as a Security Integration Test as part of a CD pipeline. 8 | - Provide a CLI (via gulp) for creating and updating the associated resources in AWS. 9 | 10 | ## Installation 11 | **Prerequisites** 12 | ``` 13 | aws cli 14 | npm install --global serverless@0.5.5 15 | npm install --global gulp-cli 16 | 17 | ``` 18 | 19 | **Clone the source and set environment variables** 20 | ``` 21 | ==> git clone https://github.com/stelligent/config-rule-status.git 22 | ``` 23 | 24 | **Install packages and configure:** 25 | ``` 26 | ==> cd config-rule-status 27 | ==> npm install 28 | ``` 29 | 30 | **Initialize the project:** 31 | ``` 32 | ==> gulp init \ 33 | --region us-east-1 \ 34 | --stage prod \ 35 | --name config-rule-status \ 36 | --awsProfile yourProfileName \ 37 | --email user@company.com 38 | ``` 39 | 40 | **Build the project:** 41 | ``` 42 | ==> gulp build 43 | ``` 44 | 45 | ## Execution 46 | 47 | **Run Tests** 48 | ``` 49 | ==> gulp test:local 50 | ``` 51 | 52 | **Deploy to AWS** 53 | ``` 54 | ==> gulp deploy:lambda --stage prod --region us-east-1 55 | ==> gulp deploy:config --stage prod --region us-east-1 56 | ``` 57 | 58 | **Verify Deploy and/or Integrate into a CD pipeline** 59 | ``` 60 | ==> gulp verify --stage prod --region us-east-1 61 | ``` 62 | 63 | **View Lambda logs** 64 | ``` 65 | ==> gulp logs --stage prod --region us-east-1 --functionName cidrIngress --duration 1d 66 | ``` 67 | 68 | ## Modifying 69 | 70 | **Create Additional Stages, Regions, and Functions** 71 | 72 | Use the Serverless CLI to add new configurations and functionality: 73 | http://docs.serverless.com/docs/commands-overview 74 | -------------------------------------------------------------------------------- /actions.js: -------------------------------------------------------------------------------- 1 | /* jshint unused: false */ 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var gulp = require('gulp'); 6 | var istanbul = require('gulp-istanbul'); 7 | var gulpMocha = require('gulp-mocha'); 8 | var jshint = require('gulp-jshint'); 9 | var spawn = require('child_process').spawn; 10 | var exec = require('child_process').exec; 11 | var del = require('del'); 12 | var replace = require('gulp-replace'); 13 | var mergestream = require('merge-stream')(); 14 | var jsonTransform = require('gulp-json-transform'); 15 | var awsConfig = require('aws-config'); 16 | var slsPath = 'serverless'; 17 | var slsPathLocal = 'node_modules/serverless/bin/' + slsPath; 18 | var basePath = 'components/'; 19 | var libPath = basePath + 'lib/'; 20 | var modulePath = basePath + 'node_modules/'; 21 | var distPath = 'dist/'; 22 | var evalTargets = [basePath + '**/*.js', '!' + modulePath + '/**', '!node_modules/**', '!coverage/**']; 23 | 24 | var functionDirs = [ 25 | basePath + 'configRules/ec2CidrEgress', 26 | basePath + 'configRules/ec2CidrIngress', 27 | basePath + 'configRules/iamUserInlinePolicy', 28 | basePath + 'configRules/iamUserManagedPolicy', 29 | basePath + 'configRules/iamUserMFA', 30 | basePath + 'complianceTest/tester' 31 | ]; 32 | 33 | var options = { 34 | project: { 35 | init: ['project', 'init'] 36 | }, 37 | function: { 38 | deploy: ['function', 'deploy'], 39 | run: ['function', 'run', 'tester'], 40 | logs: ['function', 'logs'] 41 | }, 42 | resources: { 43 | deploy: ['resources', 'deploy'], 44 | remove: ['resources', 'remove'] 45 | }, 46 | configServiceResources: { 47 | deploy: ['synchronousResources', 'deploy', '-t', 'otherResources/config-service-resources.json', '-c', 'cfnConfig.json'], 48 | remove: ['synchronousResources', 'remove', '-t', 'otherResources/config-service-resources.json', '-c', 'cfnConfig.json'] 49 | }, 50 | configRuleResources: { 51 | deploy: ['synchronousResources', 'deploy', '-t', 'otherResources/config-rule-resources.json', '-c', 'cfnConfig.json'], 52 | remove: ['synchronousResources', 'remove', '-t', 'otherResources/config-rule-resources.json', '-c', 'cfnConfig.json'] 53 | } 54 | }; 55 | 56 | /* Lib Functions */ 57 | function _lookupCmd(component, action) { 58 | return options[component][action]; 59 | } 60 | 61 | function _runServerless(options, callback) { 62 | var sls; 63 | var cmd = _lookupCmd(options.component, options.action); 64 | 65 | if (options.localSls) { 66 | slsPath = slsPathLocal; 67 | } 68 | 69 | if (options.action === 'logs') { 70 | cmd.push(options.name); 71 | cmd.push('-d'); 72 | cmd.push(options.duration); 73 | } 74 | 75 | if (options.action === 'init') { 76 | cmd.push('-n'); 77 | cmd.push(options.name); 78 | cmd.push('-e'); 79 | cmd.push(options.email); 80 | if (options.awsProfile) { 81 | cmd.push('-p'); 82 | cmd.push(options.awsProfile); 83 | } 84 | } 85 | 86 | cmd.push('-s'); 87 | cmd.push(options.stage); 88 | cmd.push('-r'); 89 | cmd.push(options.region); 90 | 91 | var procOptions = {}; 92 | //run function actions from the dist folder 93 | if (options.component === 'function') { 94 | procOptions.cwd = 'dist'; 95 | } 96 | //set aws config if locally invoking a lambda 97 | if (options.action === 'run') { 98 | _setAWSEnv(options.stage, options.region); 99 | procOptions.env = process.env; 100 | } 101 | 102 | if (options.fromPipeline) { 103 | console.log(slsPath + 'serverless ' + cmd.join(' ')); 104 | exec(slsPath + 'serverless ' + cmd.join(' '), procOptions, function(error, stdout, stderr) { 105 | console.log('stdout: ' + stdout); 106 | callback(stdout); 107 | if (error !== null) { 108 | console.log('exec error: ' + error); 109 | callback(error); 110 | } 111 | }); 112 | } else { 113 | procOptions.stdio = 'inherit'; 114 | sls = spawn(slsPath, cmd, procOptions); 115 | sls.on('close', function(code) { 116 | callback(code); 117 | }); 118 | } 119 | 120 | } 121 | 122 | function _setAWSEnv(stage, region) { 123 | var config = _getAWSConfig(stage, region); 124 | } 125 | 126 | function _getAWSConfig(stage, region) { 127 | return awsConfig({ 128 | region: region, 129 | profile: _getAWSProfile(stage) 130 | }); 131 | } 132 | 133 | function _getAWSProfile(stage) { 134 | var fs = require('fs'); 135 | var credsFileContents = fs.readFileSync('admin.env', 'utf8'); 136 | var lines = credsFileContents.split('\n'); 137 | var aws_profile; 138 | lines.forEach(function(cv) { 139 | var kvPair = cv.split('='); 140 | var key = kvPair[0]; 141 | var val = kvPair[1]; 142 | if (key === 'AWS_' + stage.toUpperCase() + '_PROFILE') { 143 | aws_profile = val; 144 | } 145 | }); 146 | console.log("Using AWS Profile: " + aws_profile) 147 | return aws_profile; 148 | } 149 | 150 | /* Clean Functions */ 151 | module.exports.cleanNodeModules = function() { 152 | return del([ 153 | 'node_modules', 154 | 'components/node_modules' 155 | ]); 156 | }; 157 | 158 | module.exports.cleanMeta = function() { 159 | return del([ 160 | '_meta' 161 | ]); 162 | }; 163 | 164 | module.exports.cleanDist = function() { 165 | return del([ 166 | 'dist' 167 | ]); 168 | }; 169 | 170 | /* Init Functions */ 171 | module.exports.initWithoutProfile = function(name, email, stage, region, callback) { 172 | var options = { 173 | 'component': 'project', 174 | 'action': 'init', 175 | 'name': name, 176 | 'email': email, 177 | 'stage': stage, 178 | 'region': region 179 | }; 180 | _runServerless(options, callback); 181 | }; 182 | 183 | module.exports.initWithProfile = function(name, awsProfile, email, stage, region, callback) { 184 | var options = { 185 | 'component': 'project', 186 | 'action': 'init', 187 | 'name': name, 188 | 'awsProfile': awsProfile, 189 | 'email': email, 190 | 'stage': stage, 191 | 'region': region 192 | }; 193 | _runServerless(options, callback); 194 | }; 195 | 196 | /* Copy Functions */ 197 | module.exports.copyLib = function() { 198 | var cnt = 0; 199 | functionDirs.forEach(function(dir) { 200 | var dirTokens = dir.split('/'); 201 | mergestream.add(gulp.src(libPath + '*').pipe(gulp.dest(distPath + dirTokens[dirTokens.length - 1] + '/lib'))); 202 | mergestream.add(gulp.src(modulePath + '*').pipe(gulp.dest(distPath + dirTokens[dirTokens.length - 1] + '/node_modules'))); 203 | }); 204 | return mergestream.isEmpty() ? null : mergestream; 205 | }; 206 | 207 | module.exports.copyFunctions = function() { 208 | var cnt = 0; 209 | functionDirs.forEach(function(dir) { 210 | var dirTokens = dir.split('/'); 211 | var functionName = dirTokens[dirTokens.length - 1]; 212 | mergestream.add( 213 | gulp.src([dir + '/handler.js']) 214 | .pipe(replace('../../lib', './lib')) 215 | .pipe(gulp.dest(distPath + functionName)) 216 | ); 217 | 218 | mergestream.add( 219 | gulp.src([dir + '/event.json']) 220 | .pipe(gulp.dest(distPath + functionName)) 221 | ); 222 | 223 | mergestream.add( 224 | gulp.src(dir + '/s-function.json') 225 | .pipe(jsonTransform(function(data) { 226 | // if the function name starts with underscore then remove it 227 | data.name = data.name.startsWith('_') ? data.name.substring(1) : data.name; 228 | return data; 229 | }, 4)) 230 | .pipe(gulp.dest(distPath + functionName)) 231 | ); 232 | 233 | }); 234 | 235 | return mergestream.isEmpty() ? null : mergestream; 236 | }; 237 | 238 | /* Test Functions */ 239 | module.exports.lint = function() { 240 | return gulp.src(evalTargets) 241 | .pipe(jshint()) 242 | .pipe(jshint.reporter('default')); 243 | }; 244 | 245 | module.exports.preTest = function() { 246 | return gulp.src(evalTargets) 247 | // Covering files 248 | .pipe(istanbul()) 249 | // Force `require` to return covered files 250 | .pipe(istanbul.hookRequire()); 251 | }; 252 | 253 | module.exports.testLocal = function() { 254 | setTimeout(function() { 255 | return gulp.src(['tests/*-tests.js'], { 256 | read: false 257 | }) 258 | .pipe(gulpMocha({ 259 | reporter: 'spec' 260 | })) 261 | // Creating the reports after tests ran 262 | .pipe(istanbul.writeReports()) 263 | .pipe(istanbul.enforceThresholds({ 264 | thresholds: false 265 | })); 266 | }, 100); 267 | }; 268 | 269 | module.exports.testDeployed = function(stage, region, callback) { 270 | var options = { 271 | 'component': 'function', 272 | 'action': 'run', 273 | 'stage': stage, 274 | 'region': region 275 | }; 276 | _runServerless(options, callback); 277 | }; 278 | 279 | /* Deploy Functions */ 280 | module.exports.deployLambdaFunctions = function(stage, region, callback) { 281 | var options = { 282 | 'component': 'function', 283 | 'action': 'deploy', 284 | 'stage': stage, 285 | 'region': region 286 | }; 287 | _runServerless(options, callback); 288 | }; 289 | 290 | module.exports.deployLambdaResources = function(stage, region, callback) { 291 | var options = { 292 | 'component': 'resources', 293 | 'action': 'deploy', 294 | 'stage': stage, 295 | 'region': region 296 | }; 297 | _runServerless(options, callback); 298 | }; 299 | 300 | module.exports.removeLambdaResources = function(stage, region, callback) { 301 | var options = { 302 | 'component': 'resources', 303 | 'action': 'remove', 304 | 'stage': stage, 305 | 'region': region 306 | }; 307 | _runServerless(options, callback); 308 | }; 309 | 310 | module.exports.deployConfigServiceResources = function(stage, region, callback) { 311 | var options = { 312 | 'component': 'configServiceResources', 313 | 'action': 'deploy', 314 | 'stage': stage, 315 | 'region': region 316 | }; 317 | _runServerless(options, callback); 318 | }; 319 | 320 | module.exports.removeConfigServiceResources = function(stage, region, callback) { 321 | var options = { 322 | 'component': 'configServiceResources', 323 | 'action': 'remove', 324 | 'stage': stage, 325 | 'region': region 326 | }; 327 | _runServerless(options, callback); 328 | }; 329 | 330 | module.exports.deployConfigRuleResources = function(stage, region, callback) { 331 | var options = { 332 | 'component': 'configRuleResources', 333 | 'action': 'deploy', 334 | 'stage': stage, 335 | 'region': region 336 | }; 337 | _runServerless(options, callback); 338 | }; 339 | 340 | module.exports.removeConfigRuleResources = function(stage, region, callback) { 341 | var options = { 342 | 'component': 'configRuleResources', 343 | 'action': 'remove', 344 | 'stage': stage, 345 | 'region': region 346 | }; 347 | _runServerless(options, callback); 348 | }; 349 | 350 | module.exports.functionLogs = function(name, duration, stage, region, callback) { 351 | var options = { 352 | 'component': 'function', 353 | 'action': 'logs', 354 | 'name': name, 355 | 'duration': duration, 356 | 'stage': stage, 357 | 'region': region 358 | }; 359 | //FIXME: serverless can't find the log stream 360 | _runServerless(options, callback); 361 | }; 362 | -------------------------------------------------------------------------------- /cfnConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "bucket":"config-rule-status", 3 | "force": true 4 | } 5 | -------------------------------------------------------------------------------- /components/complianceTest/tester/event.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /components/complianceTest/tester/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Lambda Handler 4 | module.exports.handler = function(event, context) { 5 | var globLib = require('../../lib/global'); 6 | var configService = globLib.configService; 7 | 8 | var params = {}; 9 | configService.describeConfigRules(params, function(err, data) { 10 | var responseData = {}; 11 | if (err) { 12 | responseData = { 13 | 'Error': 'describeConfigRules call failed' 14 | }; 15 | console.error(responseData.Error + ':\n', err.code + ': ' + err.message); 16 | return context.fail(err); 17 | } else { 18 | var ruleNames = data.ConfigRules.map(function(el) { 19 | return el.ConfigRuleName; 20 | }); 21 | configService.describeComplianceByConfigRule({ 22 | 'ConfigRuleNames': ruleNames 23 | }, function(err, data) { 24 | if (err) { 25 | responseData = { 26 | Error: 'describeComplianceByConfigRule call failed' 27 | }; 28 | console.error(responseData.Error + ':\n', err.code + ': ' + err.message); 29 | return context.fail(err); 30 | } else { 31 | //get the overall result 32 | responseData = { 33 | 'result': 'UKNOWN', 34 | 'results': [] 35 | }; 36 | var nonCompliantCnt = data.ComplianceByConfigRules.map(function(el, idx) { 37 | responseData.results[idx] = { 38 | 'rule': el.ConfigRuleName, 39 | 'status': el.Compliance.ComplianceType, 40 | 'result': el.Compliance.ComplianceType === 'COMPLIANT' ? 'PASS' : 'FAIL' 41 | }; 42 | return el.Compliance.ComplianceType === 'COMPLIANT' ? 0 : 1; 43 | }).reduce(function(pv, cv) { 44 | return pv + cv; 45 | }); 46 | var testResult = nonCompliantCnt === 0 ? 'PASS' : 'FAIL'; 47 | responseData.result = testResult; 48 | responseData.timestamp = new Date().toISOString(); 49 | context.succeed(responseData); 50 | } 51 | }); 52 | } 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /components/complianceTest/tester/s-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_tester", 3 | "runtime": "nodejs4.3", 4 | "description": "Serverless Lambda function for project: config-rule-status", 5 | "customName": "$${functionName}", 6 | "customRole": false, 7 | "handler": "handler.handler", 8 | "timeout": 6, 9 | "memorySize": 128, 10 | "authorizer": {}, 11 | "custom": { 12 | "excludePatterns": [], 13 | "envVars": [] 14 | }, 15 | "endpoints": [], 16 | "events": [], 17 | "environment": { 18 | "SERVERLESS_PROJECT": "${project}", 19 | "SERVERLESS_STAGE": "${stage}", 20 | "SERVERLESS_REGION": "${region}", 21 | "AWS_REGION": "${region}" 22 | }, 23 | "vpc": { 24 | "securityGroupIds": [], 25 | "subnetIds": [] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /components/configRules/ec2CidrEgress/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-365fe04e\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 3 | "ruleParameters": "{}", 4 | "resultToken": "null", 5 | "eventLeftScope": false 6 | } -------------------------------------------------------------------------------- /components/configRules/ec2CidrEgress/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Serverless Module: Lambda Handler 5 | */ 6 | 7 | // Require Logic 8 | var template = require('../../lib/template'); 9 | 10 | // Lambda Handler 11 | module.exports.handler = function(event, context) { 12 | template.defineTest(event, context, 'EC2', 'SecurityGroup', 'CidrEgress'); 13 | }; 14 | -------------------------------------------------------------------------------- /components/configRules/ec2CidrEgress/s-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_ec2CidrEgress", 3 | "runtime": "nodejs4.3", 4 | "description": "Serverless Lambda function for project: config-rule-status", 5 | "customName": "$${functionName}", 6 | "customRole": false, 7 | "handler": "handler.handler", 8 | "timeout": 6, 9 | "memorySize": 128, 10 | "authorizer": {}, 11 | "custom": { 12 | "excludePatterns": [], 13 | "envVars": [] 14 | }, 15 | "endpoints": [], 16 | "events": [], 17 | "environment": { 18 | "SERVERLESS_PROJECT": "${project}", 19 | "SERVERLESS_STAGE": "${stage}", 20 | "SERVERLESS_REGION": "${region}" 21 | }, 22 | "vpc": { 23 | "securityGroupIds": [], 24 | "subnetIds": [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components/configRules/ec2CidrIngress/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-75f1790d\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 3 | "ruleParameters": "{}", 4 | "resultToken": "null", 5 | "eventLeftScope": false 6 | } -------------------------------------------------------------------------------- /components/configRules/ec2CidrIngress/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Serverless Module: Lambda Handler 5 | */ 6 | 7 | // Require Logic 8 | var template = require('../../lib/template'); 9 | 10 | // Lambda Handler 11 | module.exports.handler = function(event, context) { 12 | template.defineTest(event, context, 'EC2', 'SecurityGroup', 'CidrIngress'); 13 | }; 14 | -------------------------------------------------------------------------------- /components/configRules/ec2CidrIngress/s-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_ec2CidrIngress", 3 | "runtime": "nodejs4.3", 4 | "description": "Serverless Lambda function for project: config-rule-status", 5 | "customName": "$${functionName}", 6 | "customRole": false, 7 | "handler": "handler.handler", 8 | "timeout": 6, 9 | "memorySize": 128, 10 | "authorizer": {}, 11 | "custom": { 12 | "excludePatterns": [], 13 | "envVars": [] 14 | }, 15 | "endpoints": [], 16 | "events": [], 17 | "environment": { 18 | "SERVERLESS_PROJECT": "${project}", 19 | "SERVERLESS_STAGE": "${stage}", 20 | "SERVERLESS_REGION": "${region}" 21 | }, 22 | "vpc": { 23 | "securityGroupIds": [], 24 | "subnetIds": [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components/configRules/iamUserInlinePolicy/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-175c8e6f\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 3 | "ruleParameters": "{}", 4 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 5 | "eventLeftScope": false 6 | } -------------------------------------------------------------------------------- /components/configRules/iamUserInlinePolicy/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Serverless Module: Lambda Handler 5 | */ 6 | 7 | // Require Logic 8 | var template = require('../../lib/template'); 9 | 10 | // Lambda Handler 11 | module.exports.handler = function(event, context) { 12 | template.defineTest(event, context, 'IAM', 'User', 'InlinePolicy'); 13 | }; 14 | -------------------------------------------------------------------------------- /components/configRules/iamUserInlinePolicy/s-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_iamUserInlinePolicy", 3 | "runtime": "nodejs4.3", 4 | "description": "Serverless Lambda function for project: config-rule-status", 5 | "customName": "$${functionName}", 6 | "customRole": false, 7 | "handler": "handler.handler", 8 | "timeout": 6, 9 | "memorySize": 128, 10 | "authorizer": {}, 11 | "custom": { 12 | "excludePatterns": [], 13 | "envVars": [] 14 | }, 15 | "endpoints": [], 16 | "events": [], 17 | "environment": { 18 | "SERVERLESS_PROJECT": "${project}", 19 | "SERVERLESS_STAGE": "${stage}", 20 | "SERVERLESS_REGION": "${region}" 21 | }, 22 | "vpc": { 23 | "securityGroupIds": [], 24 | "subnetIds": [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components/configRules/iamUserMFA/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-175c8e6f\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 3 | "ruleParameters": "{}", 4 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 5 | "eventLeftScope": false 6 | } -------------------------------------------------------------------------------- /components/configRules/iamUserMFA/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Serverless Module: Lambda Handler 5 | */ 6 | 7 | // Require Logic 8 | var template = require('../../lib/template'); 9 | 10 | // Lambda Handler 11 | module.exports.handler = function(event, context) { 12 | template.defineTest(event, context, 'IAM', 'User', 'MFADevice'); 13 | }; 14 | -------------------------------------------------------------------------------- /components/configRules/iamUserMFA/s-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_iamUserMFA", 3 | "runtime": "nodejs4.3", 4 | "description": "Serverless Lambda function for project: config-rule-status", 5 | "customName": "$${functionName}", 6 | "customRole": false, 7 | "handler": "handler.handler", 8 | "timeout": 6, 9 | "memorySize": 128, 10 | "authorizer": {}, 11 | "custom": { 12 | "excludePatterns": [], 13 | "envVars": [] 14 | }, 15 | "endpoints": [], 16 | "events": [], 17 | "environment": { 18 | "SERVERLESS_PROJECT": "${project}", 19 | "SERVERLESS_STAGE": "${stage}", 20 | "SERVERLESS_REGION": "${region}" 21 | }, 22 | "vpc": { 23 | "securityGroupIds": [], 24 | "subnetIds": [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components/configRules/iamUserManagedPolicy/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-175c8e6f\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 3 | "ruleParameters": "{}", 4 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 5 | "eventLeftScope": false 6 | } -------------------------------------------------------------------------------- /components/configRules/iamUserManagedPolicy/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Serverless Module: Lambda Handler 5 | */ 6 | 7 | // Require Logic 8 | var template = require('../../lib/template'); 9 | 10 | // Lambda Handler 11 | module.exports.handler = function(event, context) { 12 | template.defineTest(event, context, 'IAM', 'User', 'ManagedPolicy'); 13 | }; 14 | -------------------------------------------------------------------------------- /components/configRules/iamUserManagedPolicy/s-function.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_iamUserManagedPolicy", 3 | "runtime": "nodejs4.3", 4 | "description": "Serverless Lambda function for project: config-rule-status", 5 | "customName": "$${functionName}", 6 | "customRole": false, 7 | "handler": "handler.handler", 8 | "timeout": 6, 9 | "memorySize": 128, 10 | "authorizer": {}, 11 | "custom": { 12 | "excludePatterns": [], 13 | "envVars": [] 14 | }, 15 | "endpoints": [], 16 | "events": [], 17 | "environment": { 18 | "SERVERLESS_PROJECT": "${project}", 19 | "SERVERLESS_STAGE": "${stage}", 20 | "SERVERLESS_REGION": "${region}" 21 | }, 22 | "vpc": { 23 | "securityGroupIds": [], 24 | "subnetIds": [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components/lib/aws.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var checkDefined = function(reference, referenceName) { 4 | if (!reference) { 5 | console.error('Error: ' + referenceName + ' is not defined'); 6 | throw referenceName; 7 | } 8 | return reference; 9 | }; 10 | 11 | var isApplicable = function(configurationItem, event) { 12 | checkDefined(configurationItem, 'configurationItem'); 13 | checkDefined(event, 'event'); 14 | var status = configurationItem.configurationItemStatus; 15 | var eventLeftScope = event.eventLeftScope; 16 | return ('OK' === status || 'ResourceDiscovered' === status) && false === eventLeftScope; 17 | }; 18 | 19 | module.exports.evaluate = function(event, context, evalFunction) { 20 | var globLib = require('./global'); 21 | var config = globLib.configService; 22 | var configLib = require('./config'); 23 | event = checkDefined(event, 'event'); 24 | var invokingEvent = JSON.parse(event.invokingEvent); 25 | var configurationItem = checkDefined(invokingEvent.configurationItem, 'invokingEvent.configurationItem'); 26 | if (isApplicable(invokingEvent.configurationItem, event)) { 27 | evalFunction(event, context, configurationItem); 28 | } else { 29 | var configurator = new configLib.configurator(event, context, config, configurationItem); 30 | configurator.setConfig('NOT_APPLICABLE'); 31 | } 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /components/lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.configurator = function(event, context, config, configurationItem) { 4 | this.event = event; 5 | this.context = context; 6 | this.config = config; 7 | this.configurationItem = configurationItem; 8 | 9 | this.setConfig = function(compliance) { 10 | var putEvaluationsRequest = {}; 11 | var ctx = this.context; 12 | putEvaluationsRequest.Evaluations = [{ 13 | ComplianceResourceType: this.configurationItem.resourceType, 14 | ComplianceResourceId: this.configurationItem.resourceId, 15 | ComplianceType: compliance, 16 | OrderingTimestamp: this.configurationItem.configurationItemCaptureTime 17 | }]; 18 | putEvaluationsRequest.ResultToken = this.event.resultToken; 19 | this.config.putEvaluations(putEvaluationsRequest, function(err, data) { 20 | if (err) { 21 | err.compliance = compliance; 22 | ctx.fail(err); 23 | } else { 24 | data.compliance = compliance; 25 | ctx.succeed(data); 26 | } 27 | }); 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /components/lib/ec2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.getFunctions = function() { 4 | var globLib = require('./global'); 5 | var ec2 = globLib.ec2; 6 | var config = globLib.configService; 7 | var configLib = require('./config'); 8 | return { 9 | evaluateEC2SecurityGroup: function(event, context, configurationItem, rule) { 10 | var params = { 11 | 'GroupIds': [configurationItem.resourceId] 12 | }; 13 | ec2.describeSecurityGroups(params, function(err, data) { 14 | var responseData = {}; 15 | if (err) { 16 | responseData = { 17 | Error: 'describeSecurityGroups call failed' 18 | }; 19 | console.error(responseData.Error + ':\n', err.code + ': ' + err.message); 20 | return context.fail(err); 21 | } else { 22 | var configurator = new configLib.configurator(event, context, config, configurationItem); 23 | rule.test(data.SecurityGroups[0], configurator); 24 | } 25 | }); 26 | } 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /components/lib/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AWS = require('aws-sdk'); 4 | module.exports.ec2 = new AWS.EC2(); 5 | module.exports.iam = new AWS.IAM(); 6 | module.exports.configService = new AWS.ConfigService(); 7 | -------------------------------------------------------------------------------- /components/lib/iam.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.getFunctions = function() { 4 | var globLib = require('./global'); 5 | var configLib = require('./config'); 6 | var iam = globLib.iam; 7 | var config = globLib.configService; 8 | return { 9 | evaluateIAMUser: function(event, context, configurationItem, rule) { 10 | var params = { 11 | 'UserName': configurationItem.configuration.userName 12 | }; 13 | iam.getUser(params, function(err, data) { 14 | var responseData = {}; 15 | if (err) { 16 | responseData = { 17 | Error: 'getUser call failed' 18 | }; 19 | console.error(responseData.Error + ':\n', err.code + ': ' + err.message); 20 | return context.fail(err); 21 | } else { 22 | var configurator = new configLib.configurator(event, context, config, configurationItem); 23 | rule.test(data.User, configurator); 24 | } 25 | 26 | }); 27 | } 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /components/lib/rules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.getRules = function() { 4 | var globLib = require('./global'); 5 | var iam = globLib.iam; 6 | return { 7 | 'IAM': { 8 | 'MFADevice': { 9 | test: function(user, configurator) { 10 | var compliance = 'NON_COMPLIANT'; 11 | var params = { 12 | 'UserName': user.UserName 13 | }; 14 | iam.listMFADevices(params, function(err, data) { 15 | var responseData = {}; 16 | if (err) { 17 | responseData = { 18 | Error: 'listMFADevices call failed' 19 | }; 20 | console.error(responseData.Error + ':\n', err.code + ': ' + err.message); 21 | } else { 22 | if (data.MFADevices.length >= 1) { 23 | compliance = 'COMPLIANT'; 24 | } 25 | console.info('compliance: ' + compliance); 26 | configurator.setConfig(compliance); 27 | } 28 | }); 29 | } 30 | }, 31 | 'InlinePolicy': { 32 | test: function(user, configurator) { 33 | var params = { 34 | 'UserName': user.UserName 35 | }; 36 | 37 | var compliance = 'UNKNOWN'; 38 | 39 | iam.listUserPolicies(params, function(err, data) { 40 | var responseData = {}; 41 | if (err) { 42 | responseData = { 43 | Error: 'listUserPolicies call failed' 44 | }; 45 | err = responseData.Error + ':\n' + err.code + ': ' + err.message; 46 | console.error(err); 47 | } else { 48 | if (data.PolicyNames.length === 0) { 49 | compliance = 'COMPLIANT'; 50 | } else { 51 | compliance = 'NON_COMPLIANT'; 52 | } 53 | console.info('compliance: ' + compliance); 54 | configurator.setConfig(compliance); 55 | 56 | } 57 | }); 58 | } 59 | }, 60 | 'ManagedPolicy': { 61 | test: function(user, configurator) { 62 | var params = { 63 | 'UserName': user.UserName 64 | }; 65 | var compliance = 'NON_COMPLIANT'; 66 | iam.listAttachedUserPolicies(params, function(err, data) { 67 | var responseData = {}; 68 | if (err) { 69 | responseData = { 70 | Error: 'listAttachedUserPolicies call failed' 71 | }; 72 | console.error(responseData.Error + ':\n', err.code + ': ' + err.message); 73 | } else { 74 | if (data.AttachedPolicies.length === 0) { 75 | compliance = 'COMPLIANT'; 76 | } 77 | console.info('compliance: ' + compliance); 78 | configurator.setConfig(compliance); 79 | } 80 | }); 81 | } 82 | } 83 | }, 84 | 'EC2': { 85 | 'CidrIngress': { 86 | test: function(secGrp, configurator) { 87 | var compliance; 88 | var nonCompCnt = 0; 89 | var cidrRangeRegex = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'; 90 | secGrp.IpPermissions.forEach(function(ipPerm) { 91 | ipPerm.IpRanges.forEach(function(ipRange) { 92 | //check if cidrIp is populated with a cidr or a security group 93 | if (ipRange.CidrIp.search(cidrRangeRegex) !== -1) { 94 | //if it's a cidr then make sure it's not open to the world 95 | if (ipRange.CidrIp === '0.0.0.0/0') { 96 | nonCompCnt++; 97 | } 98 | //make sure it applies to a single host 99 | if (ipRange.CidrIp.split('/')[1] !== '32') { 100 | nonCompCnt++; 101 | } 102 | } 103 | }); 104 | }); 105 | compliance = nonCompCnt === 0 ? 'COMPLIANT' : 'NON_COMPLIANT'; 106 | console.info('compliance: ' + compliance); 107 | configurator.setConfig(compliance); 108 | } 109 | }, 110 | 'CidrEgress': { 111 | test: function(secGrp, configurator) { 112 | var compliance; 113 | var nonCompCnt = 0; 114 | var cidrRangeRegex = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'; 115 | secGrp.IpPermissionsEgress.forEach(function(ipPerm) { 116 | ipPerm.IpRanges.forEach(function(ipRange) { 117 | //check if cidrIp is populated with a cidr or a security group 118 | if (ipRange.CidrIp.search(cidrRangeRegex) !== -1) { 119 | //if it's a cidr then make sure it's not open to the world 120 | if (ipRange.CidrIp === '0.0.0.0/0') { 121 | nonCompCnt++; 122 | } 123 | //make sure it applies to a single host 124 | if (ipRange.CidrIp.split('/')[1] !== '32') { 125 | nonCompCnt++; 126 | } 127 | } 128 | }); 129 | }); 130 | compliance = nonCompCnt === 0 ? 'COMPLIANT' : 'NON_COMPLIANT'; 131 | console.info('compliance: ' + compliance); 132 | configurator.setConfig(compliance); 133 | } 134 | } 135 | 136 | } 137 | }; 138 | }; 139 | -------------------------------------------------------------------------------- /components/lib/template.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.defineTest = function(event, context, resourceType, resourceGroup, ruleName) { 4 | var awsLib = require('./aws'); 5 | var resourceLib = require('./' + resourceType.toLowerCase()); 6 | var ruleLib = require('./rules'); 7 | var resourceFunction = 'evaluate' + resourceType + resourceGroup; 8 | var rule = ruleLib.getRules()[resourceType][ruleName]; 9 | awsLib.evaluate(event, context, function(event, context, configurationItem) { 10 | resourceLib.getFunctions()[resourceFunction](event, context, configurationItem, rule); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "configRules", 3 | "version": "0.0.1", 4 | "description": "configRules module dependencies", 5 | "author": "me", 6 | "license": "MIT", 7 | "private": true, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/" 11 | }, 12 | "keywords": [], 13 | "devDependencies": {}, 14 | "dependencies": { 15 | "aws-sdk": "^2.2.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/pipelineIntegration/lib/context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function guid() { 4 | function s4() { 5 | return Math.floor((1 + Math.random()) * 0x10000) 6 | .toString(16) 7 | .substring(1); 8 | } 9 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); 10 | } 11 | 12 | module.exports = function(lambdaName, cb) { 13 | cb = cb || function() {}; 14 | 15 | var id = guid(), 16 | name = lambdaName || 'test-lambda'; 17 | 18 | var succeed = function(result) { 19 | done(null, result); 20 | }; 21 | 22 | var fail = function(error) { 23 | done(error, null); 24 | }; 25 | 26 | var done = function(error, result) { 27 | if (error !== null) { 28 | return cb(error, null); 29 | } 30 | cb(null, result); 31 | }; 32 | return { 33 | awsRequestId: id, 34 | invokeid: id, 35 | logGroupName: '/aws/lambda/' + name, 36 | logStreamName: '2015/09/22/[HEAD]13370a84ca4ed8b77c427af260', 37 | functionVersion: 'HEAD', 38 | isDefaultFunctionVersion: true, 39 | 40 | functionName: name, 41 | memoryLimitInMB: '1024', 42 | 43 | succeed: succeed, 44 | fail: fail, 45 | done: cb, 46 | 47 | getRemainingTimeInMillis: function() { 48 | return 5000; 49 | } 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /examples/pipelineIntegration/lib/remoteRunner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ctx = require('./context.js'); 4 | var BbPromise = require('bluebird'); 5 | var AWS = require('aws-sdk'); 6 | 7 | module.exports.lambdaRunner = function(func, region, stage, evt) { 8 | var lambda = BbPromise.promisifyAll(new AWS.Lambda({'region': region}), { 9 | suffix: 'Promised' 10 | }); 11 | 12 | // create Promise wrapper for the lambda function 13 | var params = { 14 | FunctionName: func, 15 | ClientContext: JSON.stringify(ctx), 16 | InvocationType: 'RequestResponse', 17 | LogType: 'None', 18 | Payload: JSON.stringify(evt), 19 | Qualifier: stage 20 | }; 21 | var p = lambda.invokePromised(params) 22 | .then(function(result){ 23 | return JSON.parse(result.Payload); 24 | }); 25 | 26 | return p; 27 | }; 28 | -------------------------------------------------------------------------------- /examples/pipelineIntegration/security-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var chaiAsPromised = require('chai-as-promised'); 5 | var expect = chai.expect; 6 | var lambdaRunner = require('./lib/remoteRunner.js').lambdaRunner; 7 | chai.use(chaiAsPromised); 8 | 9 | describe('ConfigRuleStatus-tester', function() { 10 | it('should PASS', 11 | function() { 12 | var event = {}; 13 | var lambdaResult = lambdaRunner('ConfigRuleStatus-tester', 'us-east-1', 'beta', event); 14 | return expect(lambdaResult).to.eventually.have.property('result', 'PASS'); 15 | } 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* jshint unused: false */ 2 | 'use strict'; 3 | 4 | var actions = require('./actions'); 5 | var gulp = require('gulp'); 6 | var argv; 7 | 8 | function _getYargs(){ 9 | argv = require('yargs')(process.argv).argv; 10 | } 11 | 12 | function _prepEnv(args){ 13 | Object.keys(args).forEach(function(key){ 14 | process.argv.push('--' + key); 15 | process.argv.push(args[key]); 16 | }); 17 | } 18 | 19 | gulp.task('args', function(){ 20 | _getYargs(); 21 | }); 22 | 23 | /* Gulp Tasks */ 24 | gulp.task('initWithProfile', function(callback) { 25 | actions.initWithProfile(argv.name, argv.awsProfile, argv.email, argv.stage, argv.region, callback); 26 | }); 27 | 28 | gulp.task('initWithoutProfile', function(callback) { 29 | actions.initWithoutProfile(argv.name, argv.email, argv.stage, argv.region, callback); 30 | }); 31 | 32 | gulp.task('clean:node_modules', function() { 33 | return actions.cleanNodeModules(); 34 | }); 35 | 36 | gulp.task('clean:meta', function() { 37 | return actions.cleanMeta(); 38 | }); 39 | 40 | gulp.task('clean:dist', function() { 41 | return actions.cleanDist(); 42 | }); 43 | 44 | gulp.task('copy:lib', ['test'], function() { 45 | return actions.copyLib(); 46 | }); 47 | 48 | gulp.task('copy:functions', ['test'], function() { 49 | return actions.copyFunctions(); 50 | }); 51 | 52 | gulp.task('lint', function() { 53 | return actions.lint(); 54 | }); 55 | 56 | gulp.task('pre-test', ['lint'], function() { 57 | return actions.preTest(); 58 | }); 59 | 60 | gulp.task('test:local', ['pre-test'], function() { 61 | return actions.testLocal(); 62 | }); 63 | 64 | gulp.task('test:deployed', function(callback) { 65 | return actions.testDeployed(argv.stage, argv.region, callback); 66 | }); 67 | 68 | gulp.task('deploy:LambdaFunctions', ['deploy:LambdaResources'], function(callback) { 69 | return actions.deployLambdaFunctions(argv.stage, argv.region, callback); 70 | }); 71 | 72 | gulp.task('deploy:LambdaResources', function(callback) { 73 | return actions.deployLambdaResources(argv.stage, argv.region, callback); 74 | }); 75 | 76 | gulp.task('remove:LambdaResources', function(callback) { 77 | return actions.removeLambdaResources(argv.stage, argv.region, callback); 78 | }); 79 | 80 | gulp.task('deploy:ConfigServiceResources', function(callback) { 81 | return actions.deployConfigServiceResources(argv.stage, argv.region, callback); 82 | }); 83 | 84 | gulp.task('remove:ConfigServiceResources', function(callback) { 85 | return actions.removeConfigServiceResources(argv.stage, argv.region, callback); 86 | }); 87 | 88 | gulp.task('deploy:ConfigRuleResources', ['deploy:ConfigServiceResources'], function(callback) { 89 | return actions.deployConfigRuleResources(argv.stage, argv.region, callback); 90 | }); 91 | 92 | gulp.task('remove:ConfigRuleResources', function(callback) { 93 | return actions.removeConfigRuleResources(argv.stage, argv.region, callback); 94 | }); 95 | 96 | gulp.task('logs', function(callback) { 97 | return actions.functionLogs(argv.name, argv.duration, argv.stage, argv.region, callback); 98 | }); 99 | 100 | //Top Level Gulp Tasks 101 | gulp.task('default', ['test', 'build']); 102 | 103 | gulp.task('init', ['args', 'initWithProfile']); 104 | 105 | gulp.task('initFromPipeline', ['args', 'initWithoutProfile']); 106 | 107 | gulp.task('test', ['lint', 'test:local']); 108 | 109 | gulp.task('build', ['clean:dist', 'copy:lib', 'copy:functions']); 110 | 111 | gulp.task('deploy:lambda', ['args', 'deploy:LambdaResources', 'deploy:LambdaFunctions']); 112 | 113 | gulp.task('deploy:config', ['args', 'deploy:ConfigServiceResources', 'deploy:ConfigRuleResources']); 114 | 115 | gulp.task('remove:config', ['args', 'remove:ConfigServiceResources', 'remove:ConfigRuleResources']); 116 | 117 | gulp.task('verify', ['args', 'test:deployed']); 118 | 119 | 120 | //Top Level Gulp Task Wrapper Function 121 | module.exports.runTask = function(options, callback){ 122 | if(options.args){ 123 | _prepEnv(options.args); 124 | } 125 | gulp.start(options.task, function(){ 126 | callback(); 127 | }); 128 | }; 129 | -------------------------------------------------------------------------------- /otherResources/config-rule-resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Creates Config rules and permissions", 4 | "Parameters": { 5 | "LambdaStage": { 6 | "Type": "String", 7 | "Default": "prod" 8 | } 9 | }, 10 | "Mappings": { 11 | "LambdaArn": { 12 | "Base": { 13 | "Segment": "arn:aws:lambda:" 14 | }, 15 | "FunctionPrefix": { 16 | "Segment": ":function:ConfigRuleStatus" 17 | } 18 | } 19 | }, 20 | "Resources": { 21 | "ec2VPCRule": { 22 | "Type": "AWS::Config::ConfigRule", 23 | "Properties": { 24 | "ConfigRuleName": "ConfigRuleStatus-EC2-VPC-Rule", 25 | "Description": "Checks whether your EC2 instances belong to a virtual private cloud (VPC).", 26 | "Scope": { 27 | "ComplianceResourceTypes": [] 28 | }, 29 | "Source": { 30 | "Owner": "AWS", 31 | "SourceDetails": [], 32 | "SourceIdentifier": "INSTANCES_IN_VPC" 33 | } 34 | } 35 | }, 36 | "ec2SSHRule": { 37 | "Type": "AWS::Config::ConfigRule", 38 | "Properties": { 39 | "ConfigRuleName": "ConfigRuleStatus-EC2-SSH-Rule", 40 | "Description": "Checks whether security groups that are in use disallow unrestricted incoming SSH traffic.", 41 | "Scope": { 42 | "ComplianceResourceTypes": [] 43 | }, 44 | "Source": { 45 | "Owner": "AWS", 46 | "SourceDetails": [], 47 | "SourceIdentifier": "INCOMING_SSH_DISABLED" 48 | } 49 | } 50 | }, 51 | "ec2EncryptionRule": { 52 | "Type": "AWS::Config::ConfigRule", 53 | "Properties": { 54 | "ConfigRuleName": "ConfigRuleStatus-EC2-Encryption-Rule", 55 | "Description": "Checks whether EBS volumes that are in an attached state are encrypted.", 56 | "Scope": { 57 | "ComplianceResourceTypes": [] 58 | }, 59 | "Source": { 60 | "Owner": "AWS", 61 | "SourceDetails": [], 62 | "SourceIdentifier": "ENCRYPTED_VOLUMES" 63 | } 64 | } 65 | }, 66 | "iamMFARule": { 67 | "Type": "AWS::Config::ConfigRule", 68 | "Properties": { 69 | "ConfigRuleName": "ConfigRuleStatus-IAM-MFA-Rule", 70 | "Description": "Checks whether Users have an MFA Device configured.", 71 | "Scope": { 72 | "ComplianceResourceTypes": [ 73 | "AWS::IAM::User" 74 | ] 75 | }, 76 | "Source": { 77 | "Owner": "CUSTOM_LAMBDA", 78 | "SourceDetails": [{ 79 | "EventSource": "aws.config", 80 | "MessageType": "ConfigurationItemChangeNotification" 81 | }], 82 | "SourceIdentifier": { 83 | "Fn::Join": ["", [{ 84 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 85 | }, { 86 | "Fn::Join": [":", [{ 87 | "Ref": "AWS::Region" 88 | }, { 89 | "Ref": "AWS::AccountId" 90 | }]] 91 | }, { 92 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 93 | }, { 94 | "Fn::Join": [":", ["-iamUserMFA", { 95 | "Ref": "LambdaStage" 96 | }]] 97 | }]] 98 | } 99 | } 100 | }, 101 | "DependsOn": "iamMFAPerm" 102 | }, 103 | "iamUserInlinePolicyRule": { 104 | "Type": "AWS::Config::ConfigRule", 105 | "Properties": { 106 | "ConfigRuleName": "ConfigRuleStatus-IAM-User-InlinePolicy-Rule", 107 | "Description": "Checks whether Users have an inline policy.", 108 | "Scope": { 109 | "ComplianceResourceTypes": [ 110 | "AWS::IAM::User" 111 | ] 112 | }, 113 | "Source": { 114 | "Owner": "CUSTOM_LAMBDA", 115 | "SourceDetails": [{ 116 | "EventSource": "aws.config", 117 | "MessageType": "ConfigurationItemChangeNotification" 118 | }], 119 | "SourceIdentifier": { 120 | "Fn::Join": ["", [{ 121 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 122 | }, { 123 | "Fn::Join": [":", [{ 124 | "Ref": "AWS::Region" 125 | }, { 126 | "Ref": "AWS::AccountId" 127 | }]] 128 | }, { 129 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 130 | }, { 131 | "Fn::Join": [":", ["-iamUserInlinePolicy", { 132 | "Ref": "LambdaStage" 133 | }]] 134 | }]] 135 | } 136 | } 137 | }, 138 | "DependsOn": "iamUserInlinePolicyPerm" 139 | }, 140 | "iamUserManagedPolicyRule": { 141 | "Type": "AWS::Config::ConfigRule", 142 | "Properties": { 143 | "ConfigRuleName": "ConfigRuleStatus-IAM-User-ManagedPolicy-Rule", 144 | "Description": "Checks whether Users have a managed policy directly attached.", 145 | "Scope": { 146 | "ComplianceResourceTypes": [ 147 | "AWS::IAM::User" 148 | ] 149 | }, 150 | "Source": { 151 | "Owner": "CUSTOM_LAMBDA", 152 | "SourceDetails": [{ 153 | "EventSource": "aws.config", 154 | "MessageType": "ConfigurationItemChangeNotification" 155 | }], 156 | "SourceIdentifier": { 157 | "Fn::Join": ["", [{ 158 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 159 | }, { 160 | "Fn::Join": [":", [{ 161 | "Ref": "AWS::Region" 162 | }, { 163 | "Ref": "AWS::AccountId" 164 | }]] 165 | }, { 166 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 167 | }, { 168 | "Fn::Join": [":", ["-iamUserManagedPolicy", { 169 | "Ref": "LambdaStage" 170 | }]] 171 | }]] 172 | } 173 | } 174 | }, 175 | "DependsOn": "iamUserManagedPolicyPerm" 176 | }, 177 | "ec2SecGrpCidrIngressRule": { 178 | "Type": "AWS::Config::ConfigRule", 179 | "Properties": { 180 | "ConfigRuleName": "ConfigRuleStatus-EC2-SecGrp-Cidr-Ingress-Rule", 181 | "Description": "Checks whether a Security Group has an ingress rule with a CIDR range that disallows unrestricted traffic and applies to a single host.", 182 | "Scope": { 183 | "ComplianceResourceTypes": [ 184 | "AWS::EC2::SecurityGroup" 185 | ] 186 | }, 187 | "Source": { 188 | "Owner": "CUSTOM_LAMBDA", 189 | "SourceDetails": [{ 190 | "EventSource": "aws.config", 191 | "MessageType": "ConfigurationItemChangeNotification" 192 | }], 193 | "SourceIdentifier": { 194 | "Fn::Join": ["", [{ 195 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 196 | }, { 197 | "Fn::Join": [":", [{ 198 | "Ref": "AWS::Region" 199 | }, { 200 | "Ref": "AWS::AccountId" 201 | }]] 202 | }, { 203 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 204 | }, { 205 | "Fn::Join": [":", ["-ec2CidrIngress", { 206 | "Ref": "LambdaStage" 207 | }]] 208 | }]] 209 | } 210 | } 211 | }, 212 | "DependsOn": "ec2SecGrpCidrIngressPerm" 213 | }, 214 | "ec2SecGrpCidrEgressRule": { 215 | "Type": "AWS::Config::ConfigRule", 216 | "Properties": { 217 | "ConfigRuleName": "ConfigRuleStatus-EC2-SecGrp-Cidr-Egress-Rule", 218 | "Description": "Checks whether a Security Group has an egress rule with a CIDR range that disallows unrestricted traffic and applies to a single host.", 219 | "Scope": { 220 | "ComplianceResourceTypes": [ 221 | "AWS::EC2::SecurityGroup" 222 | ] 223 | }, 224 | "Source": { 225 | "Owner": "CUSTOM_LAMBDA", 226 | "SourceDetails": [{ 227 | "EventSource": "aws.config", 228 | "MessageType": "ConfigurationItemChangeNotification" 229 | }], 230 | "SourceIdentifier": { 231 | "Fn::Join": ["", [{ 232 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 233 | }, { 234 | "Fn::Join": [":", [{ 235 | "Ref": "AWS::Region" 236 | }, { 237 | "Ref": "AWS::AccountId" 238 | }]] 239 | }, { 240 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 241 | }, { 242 | "Fn::Join": [":", ["-ec2CidrEgress", { 243 | "Ref": "LambdaStage" 244 | }]] 245 | }]] 246 | } 247 | } 248 | }, 249 | "DependsOn": "ec2SecGrpCidrEgressPerm" 250 | }, 251 | "iamMFAPerm": { 252 | "Type": "AWS::Lambda::Permission", 253 | "Properties": { 254 | "FunctionName": { 255 | "Fn::Join": ["", [{ 256 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 257 | }, { 258 | "Fn::Join": [":", [{ 259 | "Ref": "AWS::Region" 260 | }, { 261 | "Ref": "AWS::AccountId" 262 | }]] 263 | }, { 264 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 265 | }, { 266 | "Fn::Join": [":", ["-iamUserMFA", { 267 | "Ref": "LambdaStage" 268 | }]] 269 | }]] 270 | }, 271 | "Action": "lambda:InvokeFunction", 272 | "Principal": "config.amazonaws.com" 273 | } 274 | }, 275 | "iamUserInlinePolicyPerm": { 276 | "Type": "AWS::Lambda::Permission", 277 | "Properties": { 278 | "FunctionName": { 279 | "Fn::Join": ["", [{ 280 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 281 | }, { 282 | "Fn::Join": [":", [{ 283 | "Ref": "AWS::Region" 284 | }, { 285 | "Ref": "AWS::AccountId" 286 | }]] 287 | }, { 288 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 289 | }, { 290 | "Fn::Join": [":", ["-iamUserInlinePolicy", { 291 | "Ref": "LambdaStage" 292 | }]] 293 | }]] 294 | }, 295 | "Action": "lambda:InvokeFunction", 296 | "Principal": "config.amazonaws.com" 297 | } 298 | }, 299 | "iamUserManagedPolicyPerm": { 300 | "Type": "AWS::Lambda::Permission", 301 | "Properties": { 302 | "FunctionName": { 303 | "Fn::Join": ["", [{ 304 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 305 | }, { 306 | "Fn::Join": [":", [{ 307 | "Ref": "AWS::Region" 308 | }, { 309 | "Ref": "AWS::AccountId" 310 | }]] 311 | }, { 312 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 313 | }, { 314 | "Fn::Join": [":", ["-iamUserManagedPolicy", { 315 | "Ref": "LambdaStage" 316 | }]] 317 | }]] 318 | }, 319 | "Action": "lambda:InvokeFunction", 320 | "Principal": "config.amazonaws.com" 321 | } 322 | }, 323 | "ec2SecGrpCidrIngressPerm": { 324 | "Type": "AWS::Lambda::Permission", 325 | "Properties": { 326 | "FunctionName": { 327 | "Fn::Join": ["", [{ 328 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 329 | }, { 330 | "Fn::Join": [":", [{ 331 | "Ref": "AWS::Region" 332 | }, { 333 | "Ref": "AWS::AccountId" 334 | }]] 335 | }, { 336 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 337 | }, { 338 | "Fn::Join": [":", ["-ec2CidrIngress", { 339 | "Ref": "LambdaStage" 340 | }]] 341 | }]] 342 | }, 343 | "Action": "lambda:InvokeFunction", 344 | "Principal": "config.amazonaws.com" 345 | } 346 | }, 347 | "ec2SecGrpCidrEgressPerm": { 348 | "Type": "AWS::Lambda::Permission", 349 | "Properties": { 350 | "FunctionName": { 351 | "Fn::Join": ["", [{ 352 | "Fn::FindInMap": ["LambdaArn", "Base", "Segment"] 353 | }, { 354 | "Fn::Join": [":", [{ 355 | "Ref": "AWS::Region" 356 | }, { 357 | "Ref": "AWS::AccountId" 358 | }]] 359 | }, { 360 | "Fn::FindInMap": ["LambdaArn", "FunctionPrefix", "Segment"] 361 | }, { 362 | "Fn::Join": [":", ["-ec2CidrEgress", { 363 | "Ref": "LambdaStage" 364 | }]] 365 | }]] 366 | }, 367 | "Action": "lambda:InvokeFunction", 368 | "Principal": "config.amazonaws.com" 369 | } 370 | } 371 | }, 372 | "Outputs": { 373 | "StackName": { 374 | "Value": { 375 | "Ref": "AWS::StackName" 376 | } 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /otherResources/config-service-resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Setup Config Service", 4 | "Resources": { 5 | "ConfigBucket": { 6 | "Type": "AWS::S3::Bucket", 7 | "DeletionPolicy": "Retain", 8 | "Properties": { 9 | "AccessControl": "BucketOwnerFullControl" 10 | } 11 | }, 12 | "ConfigBucketPolicy": { 13 | "Type": "AWS::S3::BucketPolicy", 14 | "Properties": { 15 | "Bucket": { 16 | "Ref": "ConfigBucket" 17 | }, 18 | "PolicyDocument": { 19 | "Version": "2012-10-17", 20 | "Id": "PutObjPolicy", 21 | "Statement": [{ 22 | "Sid": "DenyUnEncryptedObjectUploads", 23 | "Effect": "Deny", 24 | "Principal": "*", 25 | "Action": "s3:PutObject", 26 | "Resource": {"Fn::Join":["", ["arn:aws:s3:::", {"Ref": "ConfigBucket"}, "/*"]]}, 27 | "Condition": { 28 | "StringNotEquals": { 29 | "s3:x-amz-server-side-encryption": "AES256" 30 | } 31 | } 32 | }] 33 | } 34 | } 35 | }, 36 | "ConfigTopic": { 37 | "Type": "AWS::SNS::Topic", 38 | "Properties": { 39 | "DisplayName": "config-topic", 40 | "TopicName": "config-topic" 41 | } 42 | }, 43 | "ConfigRole": { 44 | "Type": "AWS::IAM::Role", 45 | "Properties": { 46 | "AssumeRolePolicyDocument": { 47 | "Statement": [{ 48 | "Action": [ 49 | "sts:AssumeRole" 50 | ], 51 | "Effect": "Allow", 52 | "Principal": { 53 | "Service": [ 54 | "config.amazonaws.com" 55 | ] 56 | } 57 | }], 58 | "Version": "2012-10-17" 59 | }, 60 | "Path": "/", 61 | "Policies": [{ 62 | "PolicyDocument": { 63 | "Statement": [{ 64 | "Effect": "Allow", 65 | "Action": "sns:Publish", 66 | "Resource": { 67 | "Ref": "ConfigTopic" 68 | } 69 | }, { 70 | "Effect": "Allow", 71 | "Action": [ 72 | "s3:PutObject" 73 | ], 74 | "Resource": [{ 75 | "Fn::Join": [ 76 | "", [ 77 | "arn:aws:s3:::", { 78 | "Ref": "ConfigBucket" 79 | }, 80 | "/AWSLogs/", { 81 | "Ref": "AWS::AccountId" 82 | }, 83 | "/*" 84 | ] 85 | ] 86 | }], 87 | "Condition": { 88 | "StringLike": { 89 | "s3:x-amz-acl": "bucket-owner-full-control" 90 | } 91 | } 92 | }, { 93 | "Effect": "Allow", 94 | "Action": [ 95 | "s3:GetBucketAcl" 96 | ], 97 | "Resource": { 98 | "Fn::Join": [ 99 | "", [ 100 | "arn:aws:s3:::", { 101 | "Ref": "ConfigBucket" 102 | } 103 | ] 104 | ] 105 | } 106 | }, { 107 | "Effect": "Allow", 108 | "Action": [ 109 | "cloudtrail:DescribeTrails", 110 | "ec2:Describe*", 111 | "config:Put*", 112 | "cloudtrail:GetTrailStatus", 113 | "s3:GetObject", 114 | "iam:GetAccountAuthorizationDetails", 115 | "iam:GetGroup", 116 | "iam:GetGroupPolicy", 117 | "iam:GetPolicy", 118 | "iam:GetPolicyVersion", 119 | "iam:GetRole", 120 | "iam:GetRolePolicy", 121 | "iam:GetUser", 122 | "iam:GetUserPolicy", 123 | "iam:ListAttachedGroupPolicies", 124 | "iam:ListAttachedRolePolicies", 125 | "iam:ListAttachedUserPolicies", 126 | "iam:ListEntitiesForPolicy", 127 | "iam:ListGroupPolicies", 128 | "iam:ListGroupsForUser", 129 | "iam:ListInstanceProfilesForRole", 130 | "iam:ListPolicyVersions", 131 | "iam:ListRolePolicies", 132 | "iam:ListUserPolicies", 133 | "iam:ListMFADevices", 134 | "cloudtrail:DescribeTrails", 135 | "cloudtrail:GetTrailStatus", 136 | "cloudtrail:LookupEvents" 137 | ], 138 | "Resource": "*" 139 | }] 140 | }, 141 | "PolicyName": "root" 142 | }] 143 | } 144 | }, 145 | "ConfigRecorder": { 146 | "Type": "AWS::Config::ConfigurationRecorder", 147 | "Properties": { 148 | "Name": "default", 149 | "RecordingGroup": { 150 | "ResourceTypes": [ 151 | "AWS::EC2::Instance", 152 | "AWS::EC2::InternetGateway", 153 | "AWS::EC2::NetworkAcl", 154 | "AWS::EC2::NetworkInterface", 155 | "AWS::EC2::RouteTable", 156 | "AWS::EC2::SecurityGroup", 157 | "AWS::EC2::Subnet", 158 | "AWS::EC2::Volume", 159 | "AWS::EC2::VPC", 160 | "AWS::IAM::Policy", 161 | "AWS::IAM::Role", 162 | "AWS::IAM::User" 163 | ] 164 | }, 165 | "RoleARN": { 166 | "Fn::GetAtt": [ 167 | "ConfigRole", 168 | "Arn" 169 | ] 170 | } 171 | } 172 | }, 173 | "DeliveryChannel": { 174 | "Type": "AWS::Config::DeliveryChannel", 175 | "Properties": { 176 | "ConfigSnapshotDeliveryProperties": { 177 | "DeliveryFrequency": "Twelve_Hours" 178 | }, 179 | "S3BucketName": { 180 | "Ref": "ConfigBucket" 181 | }, 182 | "SnsTopicARN": { 183 | "Ref": "ConfigTopic" 184 | } 185 | } 186 | } 187 | }, 188 | "Outputs": { 189 | "StackName": { 190 | "Value": { 191 | "Ref": "AWS::StackName" 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Dave Bettinger" 4 | }, 5 | "dependencies": { 6 | "aws-config": "^1.0.7", 7 | "bluebird": "^3.3.4", 8 | "serverless": "^0.5.5", 9 | "serverless-optimizer-plugin": "^2.0.0", 10 | "serverless-synchronous-resource-plugin": "^0.1.0" 11 | }, 12 | "description": "A project to create AWS Config Rules and use them to test AWS Resource compliance.", 13 | "devDependencies": { 14 | "chai": "^3.5.0", 15 | "chai-as-promised": "^5.2.0", 16 | "del": "^2.2.0", 17 | "gulp": "^3.9.1", 18 | "gulp-istanbul": "^0.10.4", 19 | "gulp-jshint": "^2.0.0", 20 | "gulp-json-transform": "^0.3.0", 21 | "gulp-mocha": "^2.2.0", 22 | "gulp-replace": "^0.5.4", 23 | "istanbul": "^0.4.2", 24 | "jshint": "^2.9.1", 25 | "merge-stream": "^1.0.0", 26 | "mocha": "^2.4.5", 27 | "sinon": "^1.17.3", 28 | "yargs": "^4.3.1" 29 | }, 30 | "license": "MIT", 31 | "name": "config-rule-status", 32 | "optionalDependencies": {}, 33 | "private": false, 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/stelligent/config-rule-status" 37 | }, 38 | "scripts": { 39 | "postinstall": "(cd components && npm install)" 40 | }, 41 | "version": "0.0.1" 42 | } -------------------------------------------------------------------------------- /pipeline/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Require Logic 4 | var actions = require('../gulpfile'); 5 | 6 | var callback = function(err, event, context){ 7 | var AWS = require('aws-sdk'); 8 | if (!AWS.config.region) { 9 | AWS.config.region = process.env.AWS_DEFAULT_REGION; 10 | } 11 | var codepipeline = new AWS.CodePipeline(); 12 | var params; 13 | if (err) { 14 | console.error('Failure: ' + err); 15 | params = { 16 | jobId: event['CodePipeline.job'].id, 17 | failureDetails: { 18 | message: err, 19 | type: 'JobFailed', 20 | externalExecutionId: context.invokeid 21 | } 22 | }; 23 | codepipeline.putJobFailureResult(params, function(err) { 24 | context.fail(err); 25 | }); 26 | } 27 | else { 28 | console.log('Success!'); 29 | params = { 30 | jobId: event['CodePipeline.job'].id 31 | }; 32 | codepipeline.putJobSuccessResult(params, function(err) { 33 | if (err) { 34 | context.fail(err); 35 | } else { 36 | context.succeed('Action complete.'); 37 | } 38 | }); 39 | } 40 | }; 41 | 42 | // Lambda Handler 43 | module.exports.handler = function(event, context) { 44 | console.log(event['CodePipeline.job'].data.actionConfiguration.configuration.UserParameters); 45 | var userParams = JSON.parse(event['CodePipeline.job'].data.actionConfiguration.configuration.UserParameters); 46 | switch (userParams.action) { 47 | case 'build': 48 | actions.runTask({ 49 | 'task': 'build' 50 | }, function(err){ 51 | callback(err, event, context); 52 | }); 53 | break; 54 | case 'deploy:lambda': 55 | actions.runTask({ 56 | 'task': 'deploy:lambda', 57 | 'args': userParams.args 58 | }, function(err){ 59 | callback(err, event, context); 60 | }); 61 | break; 62 | case 'deploy:config': 63 | actions.runTask({ 64 | 'task': 'deploy:config', 65 | 'args': userParams.args 66 | }, function(err){ 67 | callback(err, event, context); 68 | }); 69 | break; 70 | case 'verify': 71 | actions.runTask({ 72 | 'task': 'verify', 73 | 'args': userParams.args 74 | }, function(err){ 75 | callback(err, event, context); 76 | }); 77 | break; 78 | default: 79 | actions.runTask({ 80 | 'task': 'build' 81 | }, function(err){ 82 | callback(err, event, context); 83 | }); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /s-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config-rule-status", 3 | "custom": {}, 4 | "version": "0.0.1", 5 | "profile": "config-rule-status-v0.0.1", 6 | "location": "https://github.com/stelligent/config-rule-status.git", 7 | "author": "Dave Bettinger", 8 | "description": "A project to create AWS Config Rules and use them to test AWS Resource compliance.", 9 | "plugins": [ 10 | "serverless-optimizer-plugin", 11 | "serverless-synchronous-resource-plugin" 12 | ] 13 | } -------------------------------------------------------------------------------- /s-resources-cf.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "The AWS CloudFormation template for this Serverless application's resources outside of Lambdas and Api Gateway", 4 | "Resources": { 5 | "IamRoleLambda": { 6 | "Type": "AWS::IAM::Role", 7 | "Properties": { 8 | "AssumeRolePolicyDocument": { 9 | "Version": "2012-10-17", 10 | "Statement": [ 11 | { 12 | "Effect": "Allow", 13 | "Principal": { 14 | "Service": [ 15 | "lambda.amazonaws.com" 16 | ] 17 | }, 18 | "Action": [ 19 | "sts:AssumeRole" 20 | ] 21 | } 22 | ] 23 | }, 24 | "Path": "/" 25 | } 26 | }, 27 | "IamPolicyLambda": { 28 | "Type": "AWS::IAM::Policy", 29 | "Properties": { 30 | "PolicyName": "${stage}-${project}-lambda", 31 | "PolicyDocument": { 32 | "Version": "2012-10-17", 33 | "Statement": [ 34 | { 35 | "Action": [ 36 | "iam:List*", 37 | "iam:Get*", 38 | "ec2:Describe*", 39 | "codepipeline:PutJobSuccessResult", 40 | "codepipeline:PutJobFailureResult" 41 | ], 42 | "Effect": "Allow", 43 | "Resource": "*" 44 | }, 45 | { 46 | "Action": [ 47 | "config:*" 48 | ], 49 | "Effect": "Allow", 50 | "Resource": "*" 51 | }, 52 | { 53 | "Effect": "Allow", 54 | "Action": [ 55 | "logs:CreateLogGroup", 56 | "logs:CreateLogStream", 57 | "logs:PutLogEvents" 58 | ], 59 | "Resource": "arn:aws:logs:${region}:*:*" 60 | } 61 | ] 62 | }, 63 | "Roles": [ 64 | { 65 | "Ref": "IamRoleLambda" 66 | } 67 | ] 68 | } 69 | } 70 | }, 71 | "Outputs": { 72 | "IamRoleArnLambda": { 73 | "Description": "ARN of the lambda IAM role", 74 | "Value": { 75 | "Fn::GetAtt": [ 76 | "IamRoleLambda", 77 | "Arn" 78 | ] 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /s-templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "functionName": "ConfigRuleStatus-${name}" 3 | } -------------------------------------------------------------------------------- /tests/ec2-tests.js: -------------------------------------------------------------------------------- 1 | /* jshint unused: false, quotmark: false */ 2 | 'use strict'; 3 | 4 | var chai = require('chai'); 5 | var chaiAsPromised = require('chai-as-promised'); 6 | var expect = chai.expect; 7 | var sinon = require('sinon'); 8 | var lambdaRunner = require('./lib/runner.js').lambdaRunner; 9 | var globLib = require('../components/lib/global'); 10 | 11 | chai.use(chaiAsPromised); 12 | 13 | describe('ec2CidrIngress', function() { 14 | var secGrpStub; 15 | 16 | beforeEach(function() { 17 | var consoleInfoStub = sinon.stub(console, 'info', function() {}); 18 | var consoleErrorStub = sinon.stub(console, 'error', function() {}); 19 | secGrpStub = sinon.stub(globLib.ec2, 'describeSecurityGroups'); 20 | }); 21 | 22 | afterEach(function() { 23 | console.info.restore(); 24 | console.error.restore(); 25 | globLib.ec2.describeSecurityGroups.restore(); 26 | }); 27 | 28 | it('should be rejected with undefined invokingEvent.configurationItem', 29 | function() { 30 | 31 | var event = { 32 | "invokingEvent": "{\"foo\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-365fe04e\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 33 | "ruleParameters": "{}", 34 | "resultToken": "null", 35 | "eventLeftScope": false 36 | }; 37 | var lambdaResult = lambdaRunner('components/configRules/ec2CidrIngress', event); 38 | return expect(lambdaResult).to.be.rejectedWith('invokingEvent.configurationItem'); 39 | 40 | } 41 | ); 42 | 43 | it('should be InvalidGroup', 44 | function() { 45 | secGrpStub.yields({ 46 | message: 'The security group \'sg-111111\' does not exist', 47 | code: 'InvalidGroup.NotFound', 48 | }, null); 49 | var event = { 50 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-365fe04e\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 51 | "ruleParameters": "{}", 52 | "resultToken": "null", 53 | "eventLeftScope": false 54 | }; 55 | var lambdaResult = lambdaRunner('components/configRules/ec2CidrIngress', event); 56 | return expect(lambdaResult).to.eventually.have.deep.property('code', 'InvalidGroup.NotFound'); 57 | 58 | } 59 | ); 60 | 61 | it('should be COMPLIANT', 62 | function() { 63 | secGrpStub.yields(null, { 64 | 'SecurityGroups': [{ 65 | 'IpPermissionsEgress': [{ 66 | 'IpProtocol': '-1', 67 | 'IpRanges': [{ 68 | 'CidrIp': '0.0.0.0/0' 69 | }], 70 | 'UserIdGroupPairs': [], 71 | 'PrefixListIds': [] 72 | }], 73 | 'Description': 'launch-wizard-1 created 2016-03-10T10:51:56.616-05:00', 74 | 'IpPermissions': [], 75 | 'GroupName': 'launch-wizard-1', 76 | 'VpcId': 'vpc-f399e097', 77 | 'OwnerId': '592804526322', 78 | 'GroupId': 'sg-ed58dd95' 79 | }] 80 | }); 81 | var event = { 82 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-ed58dd95\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 83 | "ruleParameters": "{}", 84 | "resultToken": "null", 85 | "eventLeftScope": false 86 | }; 87 | var lambdaResult = lambdaRunner('components/configRules/ec2CidrIngress', event); 88 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'COMPLIANT'); 89 | } 90 | ); 91 | 92 | it('should be NON_COMPLIANT', 93 | function() { 94 | secGrpStub.yields(null, { 95 | 'SecurityGroups': [{ 96 | 'IpPermissionsEgress': [{ 97 | 'IpProtocol': '-1', 98 | 'IpRanges': [{ 99 | 'CidrIp': '0.0.0.0/0' 100 | }], 101 | 'UserIdGroupPairs': [], 102 | 'PrefixListIds': [] 103 | }], 104 | 'Description': 'launch-wizard-1 created 2016-03-10T10:51:56.616-05:00', 105 | 'IpPermissions': [{ 106 | 'PrefixListIds': [], 107 | 'FromPort': 22, 108 | 'IpRanges': [{ 109 | 'CidrIp': '0.0.0.0/0' 110 | }], 111 | 'ToPort': 22, 112 | 'IpProtocol': 'tcp', 113 | 'UserIdGroupPairs': [] 114 | }], 115 | 'GroupName': 'launch-wizard-1', 116 | 'VpcId': 'vpc-f399e097', 117 | 'OwnerId': '592804526322', 118 | 'GroupId': 'sg-ed58dd95' 119 | }] 120 | }); 121 | var event = { 122 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-ed58dd95\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 123 | "ruleParameters": "{}", 124 | "resultToken": "null", 125 | "eventLeftScope": false 126 | }; 127 | var lambdaResult = lambdaRunner('components/configRules/ec2CidrIngress', event); 128 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'NON_COMPLIANT'); 129 | } 130 | ); 131 | 132 | }); 133 | 134 | describe('ec2CidrEgress', function() { 135 | var secGrpStub; 136 | 137 | beforeEach(function() { 138 | var consoleInfoStub = sinon.stub(console, 'info', function() {}); 139 | var consoleErrorStub = sinon.stub(console, 'error', function() {}); 140 | secGrpStub = sinon.stub(globLib.ec2, 'describeSecurityGroups'); 141 | }); 142 | 143 | afterEach(function() { 144 | console.info.restore(); 145 | console.error.restore(); 146 | globLib.ec2.describeSecurityGroups.restore(); 147 | }); 148 | 149 | it('should be InvalidGroup', 150 | function() { 151 | secGrpStub.yields({ 152 | message: 'The security group \'sg-111111\' does not exist', 153 | code: 'InvalidGroup.NotFound', 154 | }, null); 155 | var event = { 156 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-365fe04e\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 157 | "ruleParameters": "{}", 158 | "resultToken": "null", 159 | "eventLeftScope": false 160 | }; 161 | var lambdaResult = lambdaRunner('components/configRules/ec2CidrEgress', event); 162 | return expect(lambdaResult).to.eventually.have.deep.property('code', 'InvalidGroup.NotFound'); 163 | 164 | } 165 | ); 166 | 167 | it('should be COMPLIANT', 168 | function() { 169 | secGrpStub.yields(null, { 170 | 'SecurityGroups': [{ 171 | 'IpPermissionsEgress': [], 172 | 'Description': 'launch-wizard-1 created 2016-03-10T10:51:56.616-05:00', 173 | 'IpPermissions': [], 174 | 'GroupName': 'launch-wizard-1', 175 | 'VpcId': 'vpc-f399e097', 176 | 'OwnerId': '592804526322', 177 | 'GroupId': 'sg-ed58dd95' 178 | }] 179 | }); 180 | var event = { 181 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-ed58dd95\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 182 | "ruleParameters": "{}", 183 | "resultToken": "null", 184 | "eventLeftScope": false 185 | }; 186 | var lambdaResult = lambdaRunner('components/configRules/ec2CidrEgress', event); 187 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'COMPLIANT'); 188 | } 189 | ); 190 | 191 | it('should be NON_COMPLIANT', 192 | function() { 193 | secGrpStub.yields(null, { 194 | 'SecurityGroups': [{ 195 | 'IpPermissionsEgress': [{ 196 | 'IpProtocol': '-1', 197 | 'IpRanges': [{ 198 | 'CidrIp': '0.0.0.0/0' 199 | }], 200 | 'UserIdGroupPairs': [], 201 | 'PrefixListIds': [] 202 | }], 203 | 'Description': 'launch-wizard-1 created 2016-03-10T10:51:56.616-05:00', 204 | 'IpPermissions': [{ 205 | 'PrefixListIds': [], 206 | 'FromPort': 22, 207 | 'IpRanges': [{ 208 | 'CidrIp': '0.0.0.0/0' 209 | }], 210 | 'ToPort': 22, 211 | 'IpProtocol': 'tcp', 212 | 'UserIdGroupPairs': [] 213 | }], 214 | 'GroupName': 'launch-wizard-1', 215 | 'VpcId': 'vpc-f399e097', 216 | 'OwnerId': '592804526322', 217 | 'GroupId': 'sg-ed58dd95' 218 | }] 219 | }); 220 | var event = { 221 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"sg-ed58dd95\",\"resourceType\":\"AWS::EC2::SecurityGroup\",\"tags\":{},\"relationships\":[]}}", 222 | "ruleParameters": "{}", 223 | "resultToken": "null", 224 | "eventLeftScope": false 225 | }; 226 | var lambdaResult = lambdaRunner('components/configRules/ec2CidrEgress', event); 227 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'NON_COMPLIANT'); 228 | } 229 | ); 230 | 231 | }); 232 | -------------------------------------------------------------------------------- /tests/iam-tests.js: -------------------------------------------------------------------------------- 1 | /* jshint unused: false, quotmark: false */ 2 | 'use strict'; 3 | 4 | var chai = require('chai'); 5 | var chaiAsPromised = require('chai-as-promised'); 6 | var expect = chai.expect; 7 | var sinon = require('sinon'); 8 | var lambdaRunner = require('./lib/runner.js').lambdaRunner; 9 | var globLib = require('../components/lib/global'); 10 | 11 | chai.use(chaiAsPromised); 12 | 13 | describe('IAM/userInlinePolicy', function() { 14 | 15 | var userStub; 16 | var userPoliciesStub; 17 | 18 | beforeEach(function() { 19 | var consoleInfoStub = sinon.stub(console, 'info', function() {}); 20 | var consoleErrorStub = sinon.stub(console, 'error', function() {}); 21 | userStub = sinon.stub(globLib.iam, 'getUser'); 22 | userPoliciesStub = sinon.stub(globLib.iam, 'listUserPolicies'); 23 | }); 24 | 25 | afterEach(function() { 26 | console.info.restore(); 27 | console.error.restore(); 28 | globLib.iam.getUser.restore(); 29 | globLib.iam.listUserPolicies.restore(); 30 | }); 31 | 32 | it('should be NoSuchEntity', 33 | function() { 34 | userStub.yields({ 35 | message: 'The user with name dave.bettinger.foo cannot be found.', 36 | code: 'NoSuchEntity', 37 | }, null); 38 | var event = { 39 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 40 | "ruleParameters": "{}", 41 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 42 | "eventLeftScope": false 43 | }; 44 | var lambdaResult = lambdaRunner('components/configRules/iamUserInlinePolicy', event); 45 | return expect(lambdaResult).to.eventually.have.deep.property('code', 'NoSuchEntity'); 46 | 47 | 48 | 49 | } 50 | ); 51 | 52 | 53 | it('should be COMPLIANT', 54 | function() { 55 | userStub.yields(null, { 56 | 'User': { 57 | 'UserName': 'dave.bettinger.goldbase' 58 | } 59 | }); 60 | userPoliciesStub.yields(null, { 61 | 'PolicyNames': [] 62 | }); 63 | var event = { 64 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 65 | "ruleParameters": "{}", 66 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 67 | "eventLeftScope": false 68 | }; 69 | var lambdaResult = lambdaRunner('components/configRules/iamUserInlinePolicy', event); 70 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'COMPLIANT'); 71 | } 72 | ); 73 | 74 | it('should be NON_COMPLIANT', 75 | function() { 76 | userStub.yields(null, { 77 | 'User': { 78 | 'UserName': 'test.user' 79 | } 80 | }); 81 | userPoliciesStub.yields(null, { 82 | 'PolicyNames': [ 83 | 'policygen-test.user-201603141400' 84 | ] 85 | }); 86 | var event = { 87 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 88 | "ruleParameters": "{}", 89 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 90 | "eventLeftScope": false 91 | }; 92 | var lambdaResult = lambdaRunner('components/configRules/iamUserInlinePolicy', event); 93 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'NON_COMPLIANT'); 94 | 95 | } 96 | ); 97 | 98 | 99 | }); 100 | 101 | 102 | describe('IAM/userManagedPolicy', function() { 103 | var userStub; 104 | var userPoliciesStub; 105 | 106 | beforeEach(function() { 107 | var consoleInfoStub = sinon.stub(console, 'info', function() {}); 108 | var consoleErrorStub = sinon.stub(console, 'error', function() {}); 109 | userStub = sinon.stub(globLib.iam, 'getUser'); 110 | userPoliciesStub = sinon.stub(globLib.iam, 'listAttachedUserPolicies'); 111 | }); 112 | 113 | afterEach(function() { 114 | console.info.restore(); 115 | console.error.restore(); 116 | globLib.iam.getUser.restore(); 117 | globLib.iam.listAttachedUserPolicies.restore(); 118 | }); 119 | 120 | it('should be NoSuchEntity', 121 | function() { 122 | userStub.yields({ 123 | message: 'The user with name dave.bettinger.foo cannot be found.', 124 | code: 'NoSuchEntity', 125 | }, null); 126 | var event = { 127 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 128 | "ruleParameters": "{}", 129 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 130 | "eventLeftScope": false 131 | }; 132 | var lambdaResult = lambdaRunner('components/configRules/iamUserManagedPolicy', event); 133 | return expect(lambdaResult).to.eventually.have.deep.property('code', 'NoSuchEntity'); 134 | 135 | 136 | } 137 | ); 138 | 139 | 140 | it('should be COMPLIANT', 141 | function() { 142 | userStub.yields(null, { 143 | 'User': { 144 | 'UserName': 'dave.bettinger.goldbase' 145 | } 146 | }); 147 | userPoliciesStub.yields(null, { 148 | 'AttachedPolicies': [] 149 | 150 | }); 151 | var event = { 152 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 153 | "ruleParameters": "{}", 154 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 155 | "eventLeftScope": false 156 | }; 157 | var lambdaResult = lambdaRunner('components/configRules/iamUserManagedPolicy', event); 158 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'COMPLIANT'); 159 | } 160 | ); 161 | 162 | it('should be NON_COMPLIANT', 163 | function() { 164 | userStub.yields(null, { 165 | 'User': { 166 | 'UserName': 'test.user' 167 | } 168 | }); 169 | userPoliciesStub.yields(null, { 170 | 'AttachedPolicies': [{ 171 | 'PolicyName': 'AmazonS3ReadOnlyAccess', 172 | 'PolicyArn': 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' 173 | }] 174 | }); 175 | var event = { 176 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 177 | "ruleParameters": "{}", 178 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 179 | "eventLeftScope": false 180 | }; 181 | var lambdaResult = lambdaRunner('components/configRules/iamUserManagedPolicy', event); 182 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'NON_COMPLIANT'); 183 | 184 | } 185 | ); 186 | 187 | 188 | }); 189 | 190 | 191 | describe('IAM/userMFA', function() { 192 | var userStub; 193 | var userDevicesStub; 194 | 195 | beforeEach(function() { 196 | var consoleInfoStub = sinon.stub(console, 'info', function() {}); 197 | var consoleErrorStub = sinon.stub(console, 'error', function() {}); 198 | userStub = sinon.stub(globLib.iam, 'getUser'); 199 | userDevicesStub = sinon.stub(globLib.iam, 'listMFADevices'); 200 | }); 201 | 202 | afterEach(function() { 203 | console.info.restore(); 204 | console.error.restore(); 205 | globLib.iam.getUser.restore(); 206 | globLib.iam.listMFADevices.restore(); 207 | }); 208 | 209 | it('should be NoSuchEntity on call to getUser', 210 | function() { 211 | userStub.yields({ 212 | message: 'The user with name dave.bettinger.foo cannot be found.', 213 | code: 'NoSuchEntity', 214 | }, null); 215 | var event = { 216 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 217 | "ruleParameters": "{}", 218 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 219 | "eventLeftScope": false 220 | }; 221 | var lambdaResult = lambdaRunner('components/configRules/iamUserMFA', event); 222 | return expect(lambdaResult).to.eventually.have.deep.property('code', 'NoSuchEntity'); 223 | } 224 | ); 225 | 226 | it('should be COMPLIANT', 227 | function() { 228 | userStub.yields(null, { 229 | 'User': { 230 | 'UserName': 'dave.bettinger.goldbase' 231 | } 232 | }); 233 | userDevicesStub.yields(null, { 234 | 'MFADevices': ['a device'] 235 | 236 | }); 237 | var event = { 238 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 239 | "ruleParameters": "{}", 240 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 241 | "eventLeftScope": false 242 | }; 243 | var lambdaResult = lambdaRunner('components/configRules/iamUserMFA', event); 244 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'COMPLIANT'); 245 | } 246 | ); 247 | 248 | it('should be NON_COMPLIANT', 249 | function() { 250 | userStub.yields(null, { 251 | 'User': { 252 | 'UserName': 'test.user' 253 | } 254 | }); 255 | userDevicesStub.yields(null, { 256 | 'MFADevices': [] 257 | }); 258 | var event = { 259 | "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2015-09-25T04:05:35.693Z\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"null\",\"resourceType\":\"AWS::IAM::User\",\"tags\":{},\"relationships\":[],\"configuration\":{\"userName\":\"dave.bettinger.goldbase\"}}}", 260 | "ruleParameters": "{}", 261 | "resultToken": "38400000-8cf0-11bd-b23e-10b96e4ef00d", 262 | "eventLeftScope": false 263 | }; 264 | var lambdaResult = lambdaRunner('components/configRules/iamUserMFA', event); 265 | return expect(lambdaResult).to.eventually.have.deep.property('compliance', 'NON_COMPLIANT'); 266 | 267 | } 268 | ); 269 | 270 | 271 | }); 272 | -------------------------------------------------------------------------------- /tests/lib/context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function guid() { 4 | function s4() { 5 | return Math.floor((1 + Math.random()) * 0x10000) 6 | .toString(16) 7 | .substring(1); 8 | } 9 | 10 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); 11 | } 12 | 13 | module.exports = function(lambdaName, cb) { 14 | cb = cb || function() {}; 15 | 16 | var id = guid(), 17 | name = lambdaName || 'test-lambda'; 18 | 19 | var succeed = function(result) { 20 | done(null, result); 21 | }; 22 | 23 | var fail = function(error) { 24 | done(error, null); 25 | }; 26 | 27 | var done = function(error, result) { 28 | if (error !== null) { 29 | if (error.message.toString().startsWith('Result Token provided is invalid')) { 30 | 31 | // This error is expected to be thrown by Config.putEvaluations, because the 32 | //stubbed event used for testing has a fake result token. 33 | // This error actually indicates a successful test run. At this point the error 34 | //object will contain the compliance key, so it is assigned to result for inspection. 35 | 36 | //console.error('Token error...getting compliance from error object...:' + error); 37 | result = error; 38 | } else { 39 | return cb(error, null); 40 | } 41 | } 42 | cb(null, result); 43 | }; 44 | return { 45 | awsRequestId: id, 46 | invokeid: id, 47 | logGroupName: '/aws/lambda/' + name, 48 | logStreamName: '2015/09/22/[HEAD]13370a84ca4ed8b77c427af260', 49 | functionVersion: 'HEAD', 50 | isDefaultFunctionVersion: true, 51 | 52 | functionName: name, 53 | memoryLimitInMB: '1024', 54 | 55 | succeed: succeed, 56 | fail: fail, 57 | done: cb, 58 | 59 | getRemainingTimeInMillis: function() { 60 | return 5000; 61 | } 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /tests/lib/runner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./utils'); 4 | var ctx = require('./context.js'); 5 | var BbPromise = require('bluebird'); 6 | 7 | module.exports.lambdaRunner = function(func, evt) { 8 | var lambdaPath = func + '/handler.js'; 9 | var lambdaHandler = 'handler'; 10 | // load lambda function 11 | var lambdaAbsolutePath = utils.getAbsolutePath(lambdaPath); 12 | var lambdaFunc = require(lambdaAbsolutePath); 13 | var _event = evt; 14 | 15 | // create Promise wrapper for the lambda function 16 | var p = new BbPromise(function(resolve, reject) { 17 | try { 18 | lambdaFunc[lambdaHandler](_event, ctx(lambdaPath, function(err, result) { 19 | // Show error 20 | if (err) { 21 | //console.error('Err: ' + lambdaPath + ': ' + utils.outputJSON(err)); 22 | return resolve(err); 23 | } 24 | // Show success response 25 | //console.error('Result: ' + lambdaPath + ': ' + utils.outputJSON(result)); 26 | return resolve(result); 27 | })); 28 | } catch (err) { 29 | //console.error('Error executing lambda: ' + lambdaPath + ': ' + err); 30 | return reject(err); 31 | 32 | } 33 | }).then(function(result) { 34 | //console.info(utils.outputJSON('Then Result: ' + lambdaPath + ': ' + result)); 35 | return result; 36 | }); 37 | 38 | return p; 39 | }; 40 | -------------------------------------------------------------------------------- /tests/lib/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * utility functions 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports = Utils; 8 | 9 | function Utils() {} 10 | 11 | Utils.hexChars = '0123456789abcdef'.split(''); 12 | Utils.generateRandomHex = function(length) { 13 | var hexVal = ''; 14 | for (var i = 0; i < length; i++) { 15 | hexVal += Utils.hexChars[Math.floor(Math.random() * Utils.hexChars.length)]; 16 | } 17 | return hexVal; 18 | }; 19 | 20 | Utils.getAbsolutePath = function(path) { 21 | var absolutePath; 22 | if (path.match(/^\//)) { 23 | absolutePath = path; 24 | } else { 25 | absolutePath = [process.cwd(), path].join('/'); 26 | } 27 | return absolutePath; 28 | }; 29 | 30 | Utils.outputJSON = function(json) { 31 | console.log(typeof json === 'object' ? JSON.stringify(json, null, '\t') : json); 32 | }; 33 | -------------------------------------------------------------------------------- /tests/tester-tests.js: -------------------------------------------------------------------------------- 1 | /* jshint unused: false */ 2 | 'use strict'; 3 | 4 | var chai = require('chai'); 5 | var chaiAsPromised = require('chai-as-promised'); 6 | var expect = chai.expect; 7 | var sinon = require('sinon'); 8 | var lambdaRunner = require('./lib/runner.js').lambdaRunner; 9 | var globLib = require('../components/lib/global'); 10 | 11 | chai.use(chaiAsPromised); 12 | 13 | describe('tester', function() { 14 | 15 | var configRulesStub; 16 | var configRulesComplianceStub; 17 | 18 | beforeEach(function() { 19 | var consoleInfoStub = sinon.stub(console, 'info', function() {}); 20 | var consoleErrorStub = sinon.stub(console, 'error', function() {}); 21 | configRulesStub = sinon.stub(globLib.configService, 'describeConfigRules'); 22 | configRulesComplianceStub = sinon.stub(globLib.configService, 'describeComplianceByConfigRule'); 23 | }); 24 | 25 | afterEach(function() { 26 | console.info.restore(); 27 | console.error.restore(); 28 | globLib.configService.describeConfigRules.restore(); 29 | globLib.configService.describeComplianceByConfigRule.restore(); 30 | }); 31 | 32 | it('should error on describeConfigRules', function () { 33 | configRulesStub.yields({ 34 | 'code': 'Some Error', 35 | 'message': 'describeConfigRules call failed', 36 | 'retryable': false, 37 | 'statusCode': 1, 38 | 'time': new Date() 39 | }, null); 40 | var event = {}; 41 | var lambdaResult = lambdaRunner('components/complianceTest/tester', event); 42 | return expect(lambdaResult).to.eventually.have.deep.property('message', 'describeConfigRules call failed'); 43 | }); 44 | 45 | it('should PASS', 46 | function() { 47 | configRulesStub.yields(null, { 48 | 'ConfigRules': [{ 49 | 'ConfigRuleName': 'rule1' 50 | }, { 51 | 'ConfigRuleName': 'rule2' 52 | }, { 53 | 'ConfigRuleName': 'rule3' 54 | }] 55 | }); 56 | configRulesComplianceStub.yields(null, { 57 | 'ComplianceByConfigRules': [{ 58 | 'ConfigRuleName': 'rule1', 59 | 'Compliance': { 60 | 'ComplianceType': 'COMPLIANT' 61 | } 62 | }, { 63 | 'ConfigRuleName': 'rule2', 64 | 'Compliance': { 65 | 'ComplianceType': 'COMPLIANT' 66 | } 67 | }, { 68 | 'ConfigRuleName': 'rule3', 69 | 'Compliance': { 70 | 'ComplianceType': 'COMPLIANT' 71 | } 72 | }] 73 | }); 74 | var event = {}; 75 | var lambdaResult = lambdaRunner('components/complianceTest/tester', event); 76 | return expect(lambdaResult).to.eventually.have.deep.property('result', 'PASS'); 77 | 78 | 79 | } 80 | ); 81 | 82 | 83 | it('should FAIL', 84 | function() { 85 | configRulesStub.yields(null, { 86 | 'ConfigRules': [{ 87 | 'ConfigRuleName': 'rule1' 88 | }, { 89 | 'ConfigRuleName': 'rule2' 90 | }, { 91 | 'ConfigRuleName': 'rule3' 92 | }] 93 | }); 94 | configRulesComplianceStub.yields(null, { 95 | 'ComplianceByConfigRules': [{ 96 | 'ConfigRuleName': 'rule1', 97 | 'Compliance': { 98 | 'ComplianceType': 'COMPLIANT' 99 | } 100 | }, { 101 | 'ConfigRuleName': 'rule2', 102 | 'Compliance': { 103 | 'ComplianceType': 'NON_COMPLIANT' 104 | } 105 | }, { 106 | 'ConfigRuleName': 'rule3', 107 | 'Compliance': { 108 | 'ComplianceType': 'COMPLIANT' 109 | } 110 | }] 111 | }); 112 | var event = {}; 113 | var lambdaResult = lambdaRunner('components/complianceTest/tester', event); 114 | return expect(lambdaResult).to.eventually.have.deep.property('result', 'FAIL'); 115 | } 116 | ); 117 | }); 118 | --------------------------------------------------------------------------------