-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A DynamoDB + S3 solution to store ip block list.
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
1 parent
4302eff
commit 16d0b15
Showing
12 changed files
with
607 additions
and
182 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/dist | ||
/node_modules | ||
/local |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
/dist | ||
/node_modules | ||
npm-debug.log | ||
/local |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}; |
Oops, something went wrong.