Skip to content

Commit

Permalink
A DynamoDB + S3 solution to store ip block list.
Browse files Browse the repository at this point in the history
This is a big change in structure and files in this commit.
changes include:
use index.js to route different events to a proper handler.
modify monitor.js to handle guardduty finding events.
modify monitor.js to save malicious ip addresses to DynamoDB.
add generator.js to handle dynamodb stream events.
use generator.js to create and store a static ip block list to S3.
enforce code quality checking with eslint.
add all necessary files to include on build.
create a local.js to help developers/automation tools invoke lambda functions locally.
remove unnecessary dependencies.

Change-Id: Id4a4216ac7ce24855f7e46747ace47992d5e203a
  • Loading branch information
JaydenLiang committed Aug 2, 2018
1 parent 4302eff commit 16d0b15
Show file tree
Hide file tree
Showing 12 changed files with 607 additions and 182 deletions.
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/dist
/node_modules
/local
126 changes: 126 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
{
"extends": "eslint:recommended",
"env": {
"node": true,
"commonjs": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 8
},
"rules": {
"array-bracket-spacing": "error",
"arrow-parens": ["error", "as-needed"],
"arrow-spacing": "error",
"block-spacing": "error",
"brace-style": ["error", "1tbs", {
"allowSingleLine": true
}],
"comma-dangle": "error",
"comma-style": ["error", "last"],
"computed-property-spacing": "error",
"curly": "error",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": ["error", "always", {
"null": "ignore"
}],
"func-call-spacing": "error",
"generator-star-spacing": "error",
"indent": ["error", 4, {
"CallExpression": {
"arguments": "off"
},
"FunctionDeclaration": {
"parameters": "off"
},
"FunctionExpression": {
"parameters": "off"
},
"MemberExpression": "off",
"SwitchCase": 1
}],
"keyword-spacing": "error",
"key-spacing": ["error", {
"beforeColon": false,
"afterColon": true,
"mode": "minimum"
}],
"linebreak-style": ["error", "unix"],
"max-depth": ["error", 5 ],
"max-len": ["error", 100, {
"ignoreRegExpLiterals": true
}],
"max-params": ["error", 7],
"new-parens": "error",
"no-bitwise": "error",
"no-cond-assign": ["error", "except-parens"],
"no-confusing-arrow": "error",
"no-console": "off",
"no-constant-condition": ["error", {
"checkLoops": false
}],
"no-empty": ["error", {
"allowEmptyCatch": true
}],
"no-extend-native": "error",
"no-extra-bind": "error",
"no-lone-blocks": "error",
"no-mixed-spaces-and-tabs": "error",
"no-multi-spaces": ["error", {
"ignoreEOLComments": true,
"exceptions": {
"VariableDeclarator": true
}
}],
"no-multi-str": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-shadow": "error",
"no-tabs": "error",
"no-template-curly-in-string": "error",
"no-trailing-spaces": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-use-before-define": ["error", {
"functions": false
}],
"no-whitespace-before-property": "error",
"operator-linebreak": ["error", "after"],
"prefer-template": "error",
"quote-props": ["error", "as-needed"],
"quotes": ["error", "single"],
"require-await": "error",
"semi": ["error", "always", {
"omitLastInOneLineBlock": true
}],
"semi-spacing": ["error", {
"before": false,
"after": true
}],
"semi-style": "error",
"spaced-comment": ["error", "always", {
"block": {
"balanced": true
}
}],
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"space-infix-ops": "error",
"space-in-parens": "error",
"strict": "error",
"switch-colon-spacing": "error",
"template-curly-spacing": "error",
"valid-jsdoc": ["error", {
"requireReturn": false
}],
"wrap-iife": ["error", "any"],
"yield-star-spacing": "error",
"yoda": "error"
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/dist
/node_modules
npm-debug.log
/local
16 changes: 0 additions & 16 deletions extension/object_extension.js

This file was deleted.

197 changes: 197 additions & 0 deletions generator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
'use strict';

/*
Author: Fortinet
This generator script handles the creation of the static ip block list resource in the S3 bucket.
Information about the Lambda function and configuration is provided in the main script: index.js.
Required IAM permissions:
S3: ListBucket, HeadBucket, GetObject, PutObject, PutObjectAcl
DynamoDB: Scan
*/
const respArr = [];

let S3 = null,
docClient = null;

/*
* set response for callback
*/
const setResp = (msg, detail) => {
respArr.push({
msg: msg,
detail: detail
});
};

/*
* clear response for callback
*/
const unsetResp = () => {
respArr.length = 0;
};

/*
* check if bucket exists
*/
const bucketExists = () => {
return new Promise((resolve, reject) => {
let params = {
Bucket: process.env.S3_BUCKET
};
S3.headBucket(params, function(err, data) { // eslint-disable-line no-unused-vars
if (err) {
console.log('called bucketExists and return error: ', err.stack);
reject(err);
} else {
console.log('called bucketExists: no error.'); // successful response
resolve(params.Bucket);
}
});
});
};

/*
* scan the ip block list table and return a set of ip block list
* return a promise to resolve a list of the table items.
*/
const scanDBTable = () => {
return new Promise((resolve, reject) => {
let params = {
TableName: process.env.DDB_TABLE_NAME
};
docClient.scan(params, function(err, data) {
if (err) {
console.log('call scanDBTable: return error', err.stack);
reject(err);
} else {
console.log('called scanDBTable: scan completed.');
resolve(data.Items);
}
});
});
};

/*
* get the block list file
*/
const getBlockListFile = () => {
return new Promise((resolve, reject) => {
S3.getObject({
Bucket: process.env.S3_BUCKET,
Key: process.env.S3_BLOCKLIST_KEY
}, function(err, data) {
if (err && err.statusCode.toString() !== '404') {
console.log('called saveBlockListToBucket and return error: ', err.stack);
reject('Get ip block list error.');
} else {
if (err && err.statusCode.toString() === '404') {
resolve('');
} else {
resolve(data.Body.toString('ascii'));
}
}
});
});
};

/*
* save the block list file
*/
const saveBlockListFile = (items, blockList) => {
return new Promise((resolve, reject) => {
let found = new Set(),
added = 0;

items.forEach(finding => {
if (blockList.indexOf(finding.ip) < 0) {
blockList += `${finding.ip}\r\n`;
added++;
}
found.add(finding.ip);
});

S3.putObject({
Body: blockList,
Bucket: process.env.S3_BUCKET,
Key: process.env.S3_BLOCKLIST_KEY,
ACL: 'public-read',
ContentType: 'text/plain'
}, function(err, data) { // eslint-disable-line no-unused-vars
if (err) {
console.log('called saveBlockListToBucket and return error: ',
err.stack);
reject('Put ip block list error');
} else {
console.log('called saveBlockListToBucket: no error.');
let msg = `${found.size} IP addresses found,
and ${added} new IP addresses have been added to ip block list.`;
setResp(msg, {
found: found.size,
added: added
});
resolve();
}
});
});
};

exports.handler = async (event, context, callback) => {
const AWS = require('aws-sdk');

// locking API versions
AWS.config.apiVersions = {
lambda: '2015-03-31',
s3: '2006-03-01',
dynamodb: '2012-08-10',
dynamodbstreams: '2012-08-10'
};

unsetResp();

// verify all required process env variables
// check and set AWS region
if (!process.env.REGION) {
setResp('Must specify an AWS region.', null);
callback(null, respArr);
return;
}

if (!process.env.S3_BUCKET || !process.env.S3_BLOCKLIST_KEY) {
setResp('Must specify the S3 bucket and the IP block list file.', null);
callback(null, respArr);
return;
}

if (!process.env.DDB_TABLE_NAME) {
setResp('Must specify an AWS DB Table name.', null);
callback(null, respArr);
return;
}

AWS.config.update({
region: process.env.REGION
});
// AWS services
S3 = new AWS.S3();
docClient = new AWS.DynamoDB.DocumentClient();

try {
unsetResp();
// scan the DynamoDB table to get all ip records
let ipRecords = await scanDBTable();
// check if s3 bucket exists.
await bucketExists();
// get the current block list
let blockList = await getBlockListFile();
// update and save the ip block list file
await saveBlockListFile(ipRecords, blockList);
} catch (err) {
setResp('There\'s a problem in generating ip block list. Pleasesee detailed' +
' information in CloudWatch logs.', null);
} finally {
callback(null, respArr);
}
};
Loading

0 comments on commit 16d0b15

Please sign in to comment.