Skip to content

Commit

Permalink
Added origin-server check for ports 8080 and 8443
Browse files Browse the repository at this point in the history
Added ability to define retuned http codes as "origin server protected"
In html added comment that "origin server protected" means that it cannot be accessed
Fixed bug attack analytics debug measurements
  • Loading branch information
dtzur1 committed Aug 9, 2020
1 parent 5255a4f commit b5ad8f5
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 50 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# (SPV) site-protection-viewer
This **nodejs** tool will provide the user with a simple way to see the current security configuration of the websites protected by our Cloud WAF (Incapsula). It provides a centralized view of all the account website security configuration and also checks whether the sites origin servers are not restricted to receive traffic only from Incapsula as described [here](https://support.incapsula.com/hc/en-us/articles/200627570-Restricting-direct-access-to-your-website-Incapsula-s-IP-addresses-). The tool uses the [Incapsula API](https://docs.incapsula.com/Content/API/api.htm) to get the relevant site information and http/https calls to check the origin servers accessibility.
The output is an html file and (if configured) csv files.
For more infomation please refer to this [blog](https://www.imperva.com/blog/enhance-imperva-cloud-waf-with-a-new-management-tool-in-the-imperva-github/).
The tool can be used with it's default settings or if needed, user may change the behavior by changing the values in the settings file as described in the configuration section of this page.
The output is an html file and (if configured) csv files.
**Is Protected** origin server implies that the origin server can be accessed by an http and https request. In some cases it might not mean that the origin server is not protected. Decision parameters can be configured in the settings.js file.
For more information please refer to this [blog](https://www.imperva.com/blog/enhance-imperva-cloud-waf-with-a-new-management-tool-in-the-imperva-github/).
The tool can be used with its default settings or if needed, user may change the behavior by changing the values in the settings file as described in the configuration section of this page.

# Usage
## Installation
Expand All @@ -28,6 +29,7 @@ The tool can be used with it's default settings or if needed, user may change th
- **originServerFileNamePrefix** (default 'Origin-servers') - String used as prefix for origin server csv file
- **attackAnalyticsFileNamePrefix** (default 'Attack-Analytics') - String used as prefix for Attack Analytics csv file
- **originServerProtectedCode** When origin servers are checked, if one of these codes is returned, it implies that the origin server was NOT reached - it is protected
- **originServerHttpProtectedCode** /When origin servers are checked, if an http code is returned, it implies that the origin server was NOT reached - it is protected
- **protectionDisplay** - Use these settings to control the display of whether a setting is considered protected or not.
- **printDebugInfo** - (default false) - *true* to print debug info during execution
- **numConcurrentConnections** - (default 15) - Number of concurrent open API sessions
Expand Down
86 changes: 51 additions & 35 deletions checkOriginReached.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ var printDebugInfo = settings.printDebugInfo;
//Getting arguments
function getOriginServerInfo(originData, originDataOutpt, informCaller)
{
console.log("Check " + originData.length/2 + " Origin Servers access (http & https) - this may take a while");
console.log("Check " + originData.length/4 + " Origin Servers access (http & https) - this may take a while");
totalNumServers = originData.length;
if(printDebugInfo)
console.time("Check Origin Servers - total time");


async.forEach(originData, function(site, cb){
checkOriginServer(site.subAccountId, site.siteId, site.Name, site.serverName, site.Protocol, originDataOutpt, cb);
checkOriginServer(site.subAccountId, site.siteId, site.Name, site.serverName, site.Protocol, site.portNum, originDataOutpt, cb);
}, function(err){
if (err){
//deal with the error
Expand All @@ -38,13 +39,12 @@ function getOriginServerInfo(originData, originDataOutpt, informCaller)
});
}

function checkOriginServer(subAccountId, siteId, siteName, serverNamesList, protocol, origDataOutpt, siteCb)
function checkOriginServer(subAccountId, siteId, siteName, serverNamesList, protocol, portNum, origDataOutpt, siteCb)
{
var serverNames = serverNamesList.split(';'); // split string on comma space

async.forEach(serverNames, function(serverName, cb){
serverNameIndex++
checkIfReachable(serverNameIndex, subAccountId, siteId, siteName, serverName, protocol, origDataOutpt, cb);
checkIfReachable(serverNameIndex, subAccountId, siteId, siteName, serverName, protocol, portNum, origDataOutpt, cb);
}, function(err){
if (err){
//deal with the error
Expand All @@ -54,9 +54,11 @@ function checkOriginServer(subAccountId, siteId, siteName, serverNamesList, prot
});
}

function checkIfReachable(index, subAccountId, siteId, siteName, serverName, protocol, origDataOutpt, cb)
function checkIfReachable(index, subAccountId, siteId, siteName, serverName, protocol, portNum, origDataOutpt, cb)
{
RequestUrl = protocol + '://' + serverName;
RequestUrl = protocol + '://' + serverName + ':' + portNum;
if(printDebugInfo)
console.log(RequestUrl)
var result;
if(printDebugInfo)
console.log("Check if " + serverName + " is reachable");
Expand All @@ -65,9 +67,9 @@ request({ url: RequestUrl, method: "GET", followRedirect: false, timeout:setting
{
var isProtected = false;
if(printDebugInfo)
console.log("origin server # " + totalNumServers-- + " Server answered: " + serverName)
if (err)
console.log("origin server # " + totalNumServers-- + " Server answered: " + serverName + " " + protocol + " " + portNum)

if (err)
{
//Protected Only if error code is in list, else it means origin server is reachable
for (var i = 0; i < settings.originServerProtectedCode.length; i++)
Expand All @@ -76,37 +78,50 @@ request({ url: RequestUrl, method: "GET", followRedirect: false, timeout:setting
{
isProtected = true;
break;
}
}
if (isProtected == true)
addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, true, err.code, origDataOutpt);
else
{
if (!err.code)
err.code = err.reason;
addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, false, err.code, origDataOutpt);
}
}
else
addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, false, resp.statusCode, origDataOutpt);
cb();
});
}
}
if (isProtected == true)
{
addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, portNum, true, err.code, origDataOutpt);
}
else
{
if (!err.code)
err.code = err.reason;
addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, portNum, false, err.code, origDataOutpt);
}
}
else //No error, checking if http code is also considered "protected"
{
for (var i = 0; i < settings.originServerHttpProtectedCode.length; i++)
{
if (resp.statusCode == settings.originServerHttpProtectedCode[i])
{
isProtected = true;
break;
}
}
addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, portNum, isProtected, resp.statusCode, origDataOutpt);
}
cb();
});
}

function addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, isProtected, code, origDataOutpt)
function addOriginInfoToSite(subAccountId, siteId, siteName, serverName, protocol, portNum, isProtected, code, origDataOutpt)
{
var notFound = true;
for (var i=0; i<origDataOutpt.length; i++)
{
if (origDataOutpt[i].siteId == siteId)
{
origDataOutpt[i].originServers.push({"serverName": serverName, "protocol": protocol, "isProtected": isProtected, "code": code})
origDataOutpt[i].originServers.push({"serverName": serverName, "protocol": protocol, "portNum": portNum, "isProtected": isProtected, "code": code})
notFound = false;
break;
}
}
if (notFound)
origDataOutpt.push({"isFullySorted": false, "isSorted": false, "subAccountId": subAccountId, "siteId": siteId, "domain": siteName,"originServers":[{"serverName": serverName, "protocol": protocol, "isProtected": isProtected, "code": code}]})
origDataOutpt.push({"isFullySorted": false, "isSorted": false, "subAccountId": subAccountId, "siteId": siteId, "domain": siteName,
"originServers":[{"serverName": serverName, "protocol": protocol, "portNum": portNum, "isProtected": isProtected, "code": code}]})
}

//Colored html status
Expand Down Expand Up @@ -138,18 +153,18 @@ function buildOriginServersReport(domain, sitesOriginServersInfo, siteSummaryObj
if (origServers)
{
originServersOutput += '<table border="1">';
originServersOutput += '<tr><th align="left">Origin Server</th> <th align="left">Protocol</th><th align="left">Is Protected</th><th align="left">Code</th> </tr>\n';
originServersOutput += '<tr><th align="left">Origin Server</th> <th align="left">Protocol</th><th align="left">Is Protected <small>(No direct access)</small></th><th align="left">Code</th> </tr>\n';
for (var i = 0; i < origServers.length; i++)
{
if (origServers[i].isProtected == true)
originServersOutput += '<tr><td align="left">' + origServers[i].serverName + '</td>' +
'<td align="left">' + origServers[i].protocol + '</td>' +
'<td align="left">' + origServers[i].protocol + ' (' + origServers[i].portNum + ')</td>' +
htmlYesStr +
'<td align="left">' + origServers[i].code + '</td></tr>\n';
else
{
originServersOutput += '<tr><td align="left">' + origServers[i].serverName + '</td>' +
'<td align="left">' + origServers[i].protocol + '</td>' +
'<td align="left">' + origServers[i].protocol + ' (' + origServers[i].portNum + ')</td>' +
htmlNoStr +
'<td align="left">' + origServers[i].code + '</td></tr>\n';

Expand Down Expand Up @@ -195,7 +210,7 @@ function buildOriginServerSummary(sitesOriginServersInfo, mainAccountInfo, subAc
//Sort
sitesOriginServersInfo = sortSiteOrigServers(sitesOriginServersInfo, mainAccountInfo, subAccountsOutput);

originServersOutput += '<th align="left">Site</th><th align="left">Origin Server</th><th align="left">Protocol</th><th align="left">Is Protected</th><th align="left">Reason</th> </tr>\n';
originServersOutput += '<th align="left">Site</th><th align="left">Origin Server</th><th align="left">Protocol</th><th align="left">Is Protected <small>(No direct access)</small></th><th align="left">Reason</th> </tr>\n';

for (var i = 0; i < sitesOriginServersInfo.originServers.length; i++)
{
Expand Down Expand Up @@ -231,7 +246,7 @@ function buildOriginServerSummary(sitesOriginServersInfo, mainAccountInfo, subAc
originServerStatusStr = htmlNoStr;

originServersOutput += '<tr>' + domainStr + '<td align="left">' + sitesOriginServersInfo.originServers[i].originServers[j].serverName + '</td>' +
'<td align="left">' + sitesOriginServersInfo.originServers[i].originServers[j].protocol + '</td>' +
'<td align="left">' + sitesOriginServersInfo.originServers[i].originServers[j].protocol + ' (' + sitesOriginServersInfo.originServers[i].originServers[j].portNum + ')</td>' +
originServerStatusStr + '<td align="left">' + sitesOriginServersInfo.originServers[i].originServers[j].code + '</td></tr>\n';
}
}
Expand All @@ -255,7 +270,7 @@ function createOriginServerCsv(fileName, sitesOriginServersInfo, mainAccountInfo
//Sort
sitesOriginServersInfo = sortSiteOrigServers(sitesOriginServersInfo, mainAccountInfo, subAccountsOutput);

csvFileOutput += 'Site,Origin Server,Protocol,Is Protected, Reason, Account ID, Site ID\r\n';
csvFileOutput += 'Site,Origin Server,Protocol, Port, Is Protected, Reason, Account ID, Site ID\r\n';

for (var i = 0; i < sitesOriginServersInfo.originServers.length; i++)
{
Expand All @@ -271,7 +286,8 @@ function createOriginServerCsv(fileName, sitesOriginServersInfo, mainAccountInfo
csvFileOutput += sitesOriginServersInfo.originServers[i].accountName + ',';
}
csvFileOutput += sitesOriginServersInfo.originServers[i].domain + ',' +
sitesOriginServersInfo.originServers[i].originServers[j].serverName + ',' + sitesOriginServersInfo.originServers[i].originServers[j].protocol + ',';
sitesOriginServersInfo.originServers[i].originServers[j].serverName + ',' + sitesOriginServersInfo.originServers[i].originServers[j].protocol + ',' +
sitesOriginServersInfo.originServers[i].originServers[j].portNum + ',';

//In order to write site only once per all origin servers
if (sitesOriginServersInfo.originServers[i].originServers[j].isProtected == true)
Expand Down
4 changes: 2 additions & 2 deletions getAttackAnalyticsInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function getAAInfoList(timeNow, commonPostData, accountList, aASubAccountOutput,
{
totalNumAccounts = accountList.length;
if(settings.printDebugInfo)
console.time("Get account info - total time");
console.time("Get account AA info - total time");

async.forEachLimit(accountList, settings.numConcurrentConnections, function(account, cb){
getAaAccountInfo(timeNow, commonPostData, account.accountId, aASubAccountOutput, cb);
Expand All @@ -20,7 +20,7 @@ function getAAInfoList(timeNow, commonPostData, accountList, aASubAccountOutput,
informCaller();
}
if(settings.printDebugInfo)
console.timeEnd("Get account info - total time")
console.timeEnd("Get account AA info - total time")

informCaller();
});
Expand Down
14 changes: 10 additions & 4 deletions settings.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module.exports = Object.freeze({
//Configuration

accountId: "", // Your account ID - Mandatory
apiId: "", // Your api id - Mandatory
apiKey: "", // Your api key - Mandatory

checkOriginServers: true, // When set to true, origin servers protection will be validated, this may take a longer time
getSubAccountsInfo: true, // When set to true, Sub accounts are also listed, this may take a longer time
getAttackAnalyticsInfo: true, // When set to true Attack Analytics info is also displayed if licensed. This may take a longer time. When set to true, getSubAccountsInfo must also be true
Expand All @@ -27,11 +26,18 @@ module.exports = Object.freeze({
//When origin servers are checked, if one of these codes is returned, it implies that the origin server was NOT reached - it is protected
// Some common error codes can be found in https://nodejs.org/api/errors.html
originServerProtectedCode: [
"ETIMEDOUT", //(Operation timed out): A connect or send request failed because the connected party did not properly respond after a period of time. Usually encountered by http or net � often a sign that a socket.end() was not properly called
"EAI_AGAIN", // This is a DNS lookup timed out error means it is either a network connectivity error or some proxy related error
"ECONNREFUSED", //(Connection refused): No connection could be made because the target machine actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host. (from https://www.codingdefined.com/2015/06/nodejs-error-errno-eaiagain.html)
"ECONNRESET", //(Connection reset by peer): A connection was forcibly closed by a peer. This normally results from a loss of the connection on the remote socket due to a timeout or reboot. Commonly encountered via the http and net modules
"ENOTFOUND", //(DNS lookup failed): Indicates a DNS failure of either EAI_NODATA or EAI_NONAME. This is not a standard POSIX error
"ETIMEDOUT" //(Operation timed out): A connect or send request failed because the connected party did not properly respond after a period of time. Usually encountered by http or net � often a sign that a socket.end() was not properly called
"ESOCKETTIMEDOUT"
],

//When origin servers are checked, if an http code is returned, it implies that the origin server was NOT reached - it is protected.
// For example, we are aware that in several cases, http code 403 implies that actual server can't be accessed.
originServerHttpProtectedCode: [
// for example 403
],

/* These settings will defines per rule if protected. Per rule (id). The received paramters values from the API are compared and if equal to
Expand Down Expand Up @@ -112,7 +118,7 @@ module.exports = Object.freeze({
originServerConnectionTimeout: 10000, //(In milliseconds)

//Internal usage
version: "2.1",
version: "2.2",
pageSize: 100

});
Expand Down
14 changes: 8 additions & 6 deletions spv.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ var genericPostData = {
};

/**/
var appVersion = "2.1";
var requiredSettingsVersion = 2.1;
var appVersion = "2.2";
var requiredSettingsVersion = 2.2;
/**/

//Colored html status
Expand Down Expand Up @@ -206,8 +206,10 @@ function buildHtmlReport(commonPostData, siteData)
// Setting data for all sites to check origin server protection for http and https
for (var j=0; j < siteData.sites[i].ips.length; j++)
{
originsData.push({'subAccountId':siteData.sites[i].account_id, 'siteId': siteData.sites[i].site_id, 'Name': siteData.sites[i].domain, 'Protocol': 'https', 'serverName': siteData.sites[i].ips[j]});
originsData.push({'subAccountId':siteData.sites[i].account_id, 'siteId': siteData.sites[i].site_id,'Name': siteData.sites[i].domain, 'Protocol': 'http', 'serverName': siteData.sites[i].ips[j]});
originsData.push({'subAccountId':siteData.sites[i].account_id, 'siteId': siteData.sites[i].site_id, 'Name': siteData.sites[i].domain, 'Protocol': 'https', 'portNum': '443', 'serverName': siteData.sites[i].ips[j]});
originsData.push({'subAccountId':siteData.sites[i].account_id, 'siteId': siteData.sites[i].site_id, 'Name': siteData.sites[i].domain, 'Protocol': 'https', 'portNum': '8443', 'serverName': siteData.sites[i].ips[j]});
originsData.push({'subAccountId':siteData.sites[i].account_id, 'siteId': siteData.sites[i].site_id,'Name': siteData.sites[i].domain, 'Protocol': 'http', 'portNum': '80','serverName': siteData.sites[i].ips[j]});
originsData.push({'subAccountId':siteData.sites[i].account_id, 'siteId': siteData.sites[i].site_id,'Name': siteData.sites[i].domain, 'Protocol': 'http', 'portNum': '8080','serverName': siteData.sites[i].ips[j]});
}

addSubAccountIdId(siteData.sites[i].account_id)
Expand Down Expand Up @@ -286,7 +288,7 @@ function buildHtmlSummaryTable(isWebVolDDosPurchased)

//If checking orig servers
if (checkOriginServers)
tableOutput += '<th align="left">Origin Server Protected</th></tr>\n';
tableOutput += '<th align="left">Origin Server Protected <small>(No direct access)</small></th></tr>\n';
else
tableOutput += '</tr>\n';

Expand All @@ -305,7 +307,7 @@ function buildHtmlSummaryTable(isWebVolDDosPurchased)

if (checkOriginServers)
{
output += '<p><b>Number of sites with protected origin-server:</b> ' + (settingsSummary.totalSites - settingsSummary.totalNotConfigured - settingsSummary.origNotProtected) +
output += '<p><b>Number of sites with protected origin-server (no direct access):</b> ' + (settingsSummary.totalSites - settingsSummary.totalNotConfigured - settingsSummary.origNotProtected) +
' out of ' + (settingsSummary.totalSites - settingsSummary.totalNotConfigured) + '</p>\n';
}
else
Expand Down

0 comments on commit b5ad8f5

Please sign in to comment.