digital dreamer
11 years ago
4 changed files with 443 additions and 1 deletions
@ -1,4 +1,115 @@
@@ -1,4 +1,115 @@
|
||||
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 @@
@@ -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 @@
@@ -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 @@
@@ -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