Browse Source

Initial commit

master
digital dreamer 11 years ago
parent
commit
31e8fe2222
  1. 113
      README.md
  2. 22
      package.json
  3. 65
      settings.json
  4. 244
      twister-proxy.js

113
README.md

@ -1,4 +1,115 @@
twister-proxy twister-proxy
============= =============
RPC proxy for running a public server for the Twister P2P microblogging platform. Version 0.1.1
This is an RPC proxy for running a public server for the Twister P2P microblogging network. Public servers allow anyone to easily read news posted on Twister from the web. If a user wants to become active on the network, he/she is directed to instructions on how to install the app. Twister proxy needs twister-core to be able to access the network.
Twister is in alpha phase, it's still under construction. It is already being used, but it may be unstable, and difficult to compile. This is the project website http://twister.net.co/
Twister is open source, the source code is available here: https://github.com/miguelfreitas/twister-core
## Running a public server
**1 - install Twister**
instructions can be found here: http://twister.net.co/?page_id=23
**2 - install node.js**
it's available for all major platforms from here: http://nodejs.org/
**3 - install twister-proxy**
clone it from the repository
> git clone https://github.com/digital-dreamer/twister-proxy.git
install it
> cd twister-proxy
> npm install
**4 - run twisterd**
go to your twister-core folder and run twisterd with the following options:
> ./twisterd -daemon -rpcallowip=127.0.0.1 -public_server_mode=1
This will run twister server in background, allow RPC calls, but only form the same computer, and put it in "public server mode", which is designed for this purpose.
**5 - run twister-proxy**
go to the twister-proxy folder and run
> node twister-proxy.js &
this will launch a public server on default http port 80. If you need to change any settings, you can edit the settings.json file.
If you type your server's URL into a web browser, you should see the twister web application. It is now functional, but if you care about privacy for your users, I highly recommend taking one more step and enabling SSL.
## Enable SSL
**1 - upgrade OpenSSL to the latest version to protect your server from Heartbleed**
Visit http://heartbleed.com/ if you want to know more about this issue.
**2 - generate a key and certificate request**
> openssl genrsa -des3 -out server-key.pem 2048
> openssl req -new -key server-key.pem -out request.csr
Keep the generated server-key.pem safe.
If you want to know more about keys: https://www.openssl.org/docs/HOWTO/keys.txt
**3 - request a certificate**
You now give the request.csr file to a Certification Authority (CA) - a company that will generate a certificate and give it back to you.
This guide shows where to get a certificate cheap or for free:
http://webdesign.about.com/od/ssl/tp/cheapest-ssl-certificates.htm
**4 - enable SSL in twister-proxy**
Edit the settings.json file
* In "ssl_key_file", specify a path to the server-key.pem file that you generated.
* In "ssl_certificate_file", specify a path to the file that you received from your Certificate Authority.
* Change "enable_https" from false to true.
That's it. If you now run twister-proxy, it will use secure https connections.
## Production
**1 - To keep a log, redirect twister proxy output to a file**
Example:
> node twister-proxy.js > output.log &
**2 - You can use the "forever" module to keep the proxy server running**
A guide can be found here:
https://blog.nodejitsu.com/keep-a-nodejs-server-up-with-forever/
## Troubleshooting
### Cannot connect to twisterd
Twister must be running and accepting RPC calls, run it with these parameters:
> ./twisterd -daemon -rpcallowip=127.0.0.1 -public_server_mode=1
If you changed the RPC port, username or password in twister.conf, you need to change it in settings.json too.
### Configuration file settings.json couldn't be parsed
You probably damaged settings.json when editing it. If you can spot what went wrong, you can correct it, if not, download the default settings.json and redo your customization.
If you get stuck, and need some help setting up a public Twister server, you can ask in the issue sction, even if it is not an actual issue with the code.

22
package.json

@ -0,0 +1,22 @@
{
"name": "twister-proxy",
"description": "RPC proxy for running a public server for the Twister P2P microblogging platform.",
"version": "0.1.1",
"author": "digital dreamer <digitaldreamer@email.cz>",
"dependencies":
{
"express": ">=2.5.x",
"log-timestamp": ">=0.1.1"
},
"repository": {
"type": "git",
"url": "https://github.com/digital-dreamer/twister-proxy.git"
},
"licenses":
[
{
"type": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
]
}

65
settings.json

@ -0,0 +1,65 @@
{
"Server":
{
"ssl_key_file": "insert/path/to/your/server-key-file",
"ssl_certificate_file": "insert/path/to/your/ssl-cetrificate",
"enable_https": false,
"https_port": 443,
"http_port": 80
},
"RPC":
{
"host": "localhost",
"port": 28332,
"user": "user",
"password": "pwd"
},
"CallLimits":
[
{
"name": "getbestblockhash",
"maxPerMinute": null,
"maxPerMinutePerIP": null
},
{
"name": "getinfo",
"maxPerMinute": null,
"maxPerMinutePerIP": null
},
{
"name": "listwalletusers",
"maxPerMinute": null,
"maxPerMinutePerIP": null
},
{
"name": "getblock",
"maxPerMinute": null,
"maxPerMinutePerIP": null
},
{
"name": "dhtget",
"maxPerMinute": null,
"maxPerMinutePerIP": null
},
{
"name": "listusernamespartial",
"maxPerMinute": null,
"maxPerMinutePerIP": null
},
{
"name": "gettrendinghashtags",
"maxPerMinute": null,
"maxPerMinutePerIP": null
}
],
"LogAsAttackThreshold":
{
"callsOverLimits": 30,
"invalidRequests": 30,
"forbiddenCalls": 30
}
}

244
twister-proxy.js

@ -0,0 +1,244 @@
var fs = require("fs");
var https = require("https");
var http = require("http");
var express = require("express");
var console = require("console");
var app = express();
try
{
var settings = JSON.parse(fs.readFileSync("settings.json"));
}
catch(e)
{
console.log("Error: Configuration file settings.json couldn't be parsed.\nSee Troubleshooting section in README.md for instructions.");
process.exit(1);
}
if(settings.Server.enable_https)
{
try
{
var privateKey = fs.readFileSync(settings.Server.ssl_key_file).toString();
}
catch(e)
{
console.log("Error: unable to load SSL key. Please edit setting.json and put the correct path to your SSL private key file into \"ssl_key_file.\"");
process.exit(1);
}
try
{
var certificate = fs.readFileSync(settings.Server.ssl_certificate_file).toString();
}
catch(e)
{
console.log("Error: unable to load SSL certificate. Please edit setting.json and put the correct path to your SSL certificate file into \"ssl_certificate_file.\"");
process.exit(1);
}
var credentials = {key: privateKey, cert: certificate};
}
maxCallsPerMinute = {};
maxCallsPerMinutePerIP = {};
callsRemaining = {};
perIPCounter = {};
droppedCallsCounter = {};
var invalidRequestCounter = 0;
var forbiddenCallCounter = 0;
var connectionErrorMessageDisplayed = false;
settings.CallLimits.forEach(function(x) {
maxCallsPerMinute[x.name] = x.maxPerMinute;
maxCallsPerMinutePerIP[x.name] = x.maxPerMinutePerIP;
callsRemaining[x.name] = x.maxPerMinute;
droppedCallsCounter[x.name] = 0;
});
CounterInstance = function()
{
this.overLimit=0;
this.invalidRequests=0;
this.forbiddenCalls=0;
this.callsRemaining={};
for (x in maxCallsPerMinutePerIP)
{
if(maxCallsPerMinutePerIP[x]!==null)
this.callsRemaining[x]=maxCallsPerMinutePerIP[x];
}
}
require("log-timestamp");
app.get("*", function(request, response)
{
if(settings.Server.enable_https&&request.protocol=="http")
{
if(settings.Server.https_port==443)
secureUrl="https://"+request.host+request.path;
else
secureUrl="https://"+request.host+":"+settings.Server.https_port+request.path;
response.writeHead(302, {"Location": secureUrl});
response.end();
return;
}
var webProxy = http.request({host: settings.RPC.host, port: settings.RPC.port, method: request.method, path: request.path, headers: request.headers}, function (proxy_res)
{
proxy_res.pipe(response, {end: true});
});
webProxy.on("error", function(error)
{
if(!connectionErrorMessageDisplayed)
{
console.log("Error: cannot connect to twisterd.\nSee Troubleshooting section in README.md for instructions.");
connectionErrorMessageDisplayed=true;
}
response.send(502);
});
request.pipe(webProxy, {end: true});
});
app.post("/", function(request, response)
{
request.rawBody = "";
request.setEncoding("utf8");
request.addListener("data", function(chunk)
{
request.rawBody += chunk;
});
request.addListener("end", function()
{
var remoteIP = request.connection.remoteAddress;
if(perIPCounter[remoteIP]===undefined)
{
perIPCounter[remoteIP]=new CounterInstance();
}
try
{
bodyJson=JSON.parse(request.rawBody);
rpcMethod=bodyJson.method;
}
catch(e)
{
perIPCounter[remoteIP].invalidRequests++;
invalidRequestCounter++;
return;
}
if(maxCallsPerMinute[rpcMethod]===undefined)
{
perIPCounter[remoteIP].forbiddenCalls++;
forbiddenCallCounter++;
return;
}
if(maxCallsPerMinute[rpcMethod]!==null)
{
if(callsRemaining[rpcMethod]<1)
{
droppedCallsCounter[rpcMethod]++;
return;
}
else
{
callsRemaining[rpcMethod]--;
}
}
if(maxCallsPerMinutePerIP[rpcMethod]!==null)
{
if(perIPCounter[remoteIP].callsRemaining[rpcMethod]<1)
{
perIPCounter[remoteIP].overLimit++;
return;
}
else
{
perIPCounter[remoteIP].callsRemaining[rpcMethod]--;
}
}
var rpcProxy = http.request({host: settings.RPC.host, port: settings.RPC.port, method: request.method, headers: request.headers}, function(proxy_res)
{
proxy_res.on("data", function(chunk)
{
response.write(chunk, "binary");
});
proxy_res.on("end", function(chunk)
{
response.end();
});
proxy_res.on("error", function(error)
{
if(!connectionErrorMessageDisplayed)
{
console.log("Error: cannot connect to twisterd.\nSee Troubleshooting section in README.md for instructions.");
connectionErrorMessageDisplayed=true;
}
response.send(502);
});
response.writeHead(proxy_res.statusCode, proxy_res.headers);
});
rpcProxy.write(request.rawBody, "binary");
rpcProxy.end();
});
});
if(settings.Server.enable_https)
{
https.createServer(credentials, app).listen(settings.Server.https_port);
}
http.createServer(app).listen(settings.Server.http_port);
setInterval(function()
{
for(method in maxCallsPerMinute)
{
callsRemaining[method] = maxCallsPerMinute[method];
if(droppedCallsCounter[method]!==0)
{
console.log("Dropped "+droppedCallsCounter[method]+" calls to "+method+" over the limit of "+maxCallsPerMinute[method]);
droppedCallsCounter[method] = 0;
}
};
if(invalidRequestCounter!==0)
{
console.log("Received "+invalidRequestCounter+" invalid POST requests.");
invalidRequestCounter = 0;
}
if(forbiddenCallCounter!==0)
{
console.log("Denied "+forbiddenCallCounter+" attempts to access forbidden API calls.");
forbiddenCallCounter = 0;
}
for(ip in perIPCounter)
{
if(perIPCounter[ip].overLimit>=settings.LogAsAttackThreshold.callsOverLimits)
{
console.log("IP "+ip+" tried to send "+perIPCounter[ip].overLimit+" calls more than the limits allow.");
};
if(perIPCounter[ip].invalidRequests>=settings.LogAsAttackThreshold.invalidRequests)
{
console.log("IP "+ip+" sent "+perIPCounter[ip].invalidRequests+" invalid request that couldn't be parsed.");
};
if(perIPCounter[ip].forbiddenCalls>=settings.LogAsAttackThreshold.forbiddenCalls)
{
console.log("IP "+ip+" tried to send "+perIPCounter[ip].forbiddenCalls+" calls to forbideen functions.");
};
}
perIPCounter = {};
connectionErrorMessageDisplayed = false;
}, 60000);
Loading…
Cancel
Save