Skip to content

Commit

Permalink
Merge pull request #13 from johnweland/develop
Browse files Browse the repository at this point in the history
Final push for MVP; Added telemetry via geoip-lite; minor code cleanup; performance enhancement in for loop.
  • Loading branch information
johnweland authored Dec 15, 2020
2 parents f827f83 + 5856984 commit fb41232
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 38 deletions.
41 changes: 32 additions & 9 deletions handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { APIGatewayEvent, APIGatewayProxyHandler } from "aws-lambda";
import fetch from "node-fetch";
import "source-map-support/register";
import geoip from 'geoip-lite';

/**
* This is the doc comment for handler.ts
Expand Down Expand Up @@ -38,30 +39,39 @@ export const ipcheck: APIGatewayProxyHandler = async (event, _context) => {
let lists: Array<FireHolFile> = await getFireholLists();
let flagged: boolean = false;
let count: number = 0;
let foundIn: string = "";
for (let i = 0; i < lists.length; i++) {
let foundIn: string|null = null;
// NOTE: add let n for length caching, should improve performance some
for (let i:number = 0, n:number = lists.length; i < n; ++i) {
let lines: string[] = await readIPset(lists[i]);
count = count + lines.length;
if (lines.includes(ip)) {
flagged = true;
foundIn = lists[i].path;
foundIn = `https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/${lists[i].path}`;
break;
}
}

let telemetry = await getTelemetry(ip);
let message = `The IP address ${ip}`;
flagged
? message += ` was found in ${foundIn}.`
? message += ` was found amoung an ipset, please see 'request_results'.`
: message += ` is safe.`;


return {
statusCode: 200,
body: JSON.stringify(
{
message: message,
reference_ip: ip,
addresses_searched: count,
files_searched: lists.length,
foundIn,
origin
request_results: {
ip,
telemetry: JSON.parse(telemetry),
addresses_searched: count,
ipsets_count: lists.length,
found: flagged,
ipset: foundIn
},
request_origin: origin
},
null,
2,
Expand Down Expand Up @@ -182,3 +192,16 @@ export const readIPset = async (file: FireHolFile) => {
});
return sanitized;
};

/**
*
* @param ip
*/
export const getTelemetry = async (ip:string) => {
let telemetry = await geoip.lookup(ip);

if (telemetry) {
return JSON.stringify(telemetry);
}
return JSON.stringify("no telemetry data found");
}
195 changes: 195 additions & 0 deletions localtest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
const { APIGatewayEvent, APIGatewayProxyHandler } = require("aws-lambda");
const fetch = require("node-fetch");
require("source-map-support/register");
const geoip = require('geoip-lite');

/**
* This is the doc comment for handler.ts
* @namespace handler.ts
* GitHub Repository: {@link https://github.com/johnweland/ipblock-microservice}
*/


/**
* Primary function for comparing input/request IP to a list of blocked IPSets from Firehol
* @param {APIGatewayEvent} event
* @param {Context} _context
*
* @returns {json} JSON object with success or error data
*/
export const ipcheck = async (event, _context) => {
try {
let { ip, origin } = await evaluateRequest(event);
let valid = await validateIp(ip);
if (!valid) {
throw new Error(`invalid IP address ${ip}`);
}
let lists = await getFireholLists();
let flagged = false;
let count = 0;
let foundIn = "";
for (let i = 0; i < lists.length; i++) {
let lines = await readIPset(lists[i]);
count = count + lines.length;
if (lines.includes(ip)) {
flagged = true;
foundIn = lists[i].path;
break;
}
}
let telemetry = {};
if (flagged) {
telemetry = await getTelemetry(ip);
}
let message = `The IP address ${ip}`;
flagged
? message += ` was found in ${foundIn}.`
: message += ` is safe.`;


return {
statusCode: 200,
body: JSON.stringify(
{
message: message,
reference_ip: {
ip,
telemetry
},
addresses_searched: count,
files_searched: lists.length,
foundIn,
origin
},
null,
2,
),
};
} catch (err) {
return {
statusCode: 400,
body: JSON.stringify(
{
error: err.message,
},
null,
2,
),
};
}
};

/**
* Evaluate incoming request for querystring AND|OR origin
* @param {object} request Inbound request
*
* @throws {InValidArgumentException} Invalid IP Address
* @return {object} IP and origin
*/
export const evaluateRequest = (request) => {
let ip = null;
let requestOrigin = null;
if (
typeof request.queryStringParameters !== "undefined" &&
request.queryStringParameters !== null
) {
if (
typeof request.queryStringParameters.ip !== "undefined" &&
request.queryStringParameters.ip.length &&
request.queryStringParameters.ip !== null
) {
ip = request.queryStringParameters.ip;
} else {
throw new Error(
"The only query parameter excepted is '?ip=<SOMEVALUE>'.",
);
}
}

if (
typeof request.requestContext !== "undefined" &&
typeof request.requestContext.identity !== "undefined" &&
typeof request.requestContext.identity.sourceIp !== "undefined"
) {
requestOrigin = request.requestContext.identity.sourceIp;
}
if (requestOrigin !== null && ip == null) {
ip = requestOrigin;
}

return {
ip,
origin: {
ip: requestOrigin,
country: request.headers['CloudFront-Viewer-Country'],
userAgent: request.headers['User-Agent']
},
};
};

/**
* Validate IP Address as either ipV4 or ipV6
* @param {string} ip v4 or v6 IP address
*
* @returns {boolean} Valid IP address?
*/
export const validateIp = (ip) => {
let ipV4 = new RegExp(
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
).test(ip);
let ipV6 = new RegExp(/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/).test(
ip,
);
if (!ipV4 && !ipV6) {
return false;
}
return true;
};

/**
* Return a array of Files ending in .ipset from the git repository master branch
*
* @returns {[File]} An array of file:File
*/
export const getFireholLists = async () => {
const response = await fetch(
`https://api.github.com/repos/firehol/blocklist-ipsets/git/trees/master?recursive=1`,
);
const json = await response.json();
const files = await json.tree.filter((file) => {
if (file.path.endsWith(".ipset")) {
return file.path;
}
});
return files;
};

/**
* Strips out comment lines and converts the IP address lines to an array of IP addresses
* @param {FireHolFile} file File object from which to grab the path
*
* @returns {array} A sanitized list of IP Addresses
*/
export const readIPset = async (file) => {
const response = await fetch(
`https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/${file.path}`,
);
const text = await response.text();
const sanitized = await text.split("\n").filter((line) => {
return line.indexOf("#") !== 0;
});
return sanitized;
};

/**
*
* @param ip
*/
export const getTelemetry = async (ip) => {
let telemetry = await geoip.lookup(ip);

if (telemetry) {
return JSON.stringify(telemetry);
}
return JSON.stringify({});
}
Loading

0 comments on commit fb41232

Please sign in to comment.