digital dreamer
11 years ago
4 changed files with 443 additions and 1 deletions
@ -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. |
||||||
|
@ -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" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -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 |
||||||
|
} |
||||||
|
} |
@ -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…
Reference in new issue