Rate limit Socket IO communication - WIP (#4036)

Includes settings
    Includes i18n
    Includes a nice notification
    Disconnects on rate limit
    Includes feeding into metrics/stats
    Include console warn to server console.
pull/4175/head^2
John McLear 2020-07-19 22:44:24 +01:00 committed by GitHub
parent 4f5cf2dc63
commit 40014d8230
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 75 additions and 8 deletions

View File

@ -57,8 +57,6 @@ jobs:
- "bin/installDeps.sh"
- "cd src && npm install && cd -"
- "npm install -g etherpad-load-test"
# I set loadTest to true
- "sed 's/\"loadTest\": false,/\"loadTest\": true,/g' settings.json.template > settings.json"
script:
- "tests/frontend/travis/runnerLoadTest.sh"

View File

@ -480,6 +480,23 @@
*/
"allowAnyoneToImport": false,
/*
* From Etherpad 1.9.0 onwards, when Etherpad is in production mode commits from individual users are rate limited
*
* The default is to allow at most 10 changes per IP in a 1 second window.
* After that the change is rejected.
*
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
*/
"commitRateLimiting": {
// duration of the rate limit window (seconds)
"duration": 1,
// maximum number of chanes per IP to allow during the rate limit window
"points": 10
},
/*
* Toolbar buttons configuration.
*

View File

@ -87,6 +87,9 @@
"pad.modals.deleted": "Deleted.",
"pad.modals.deleted.explanation": "This pad has been removed.",
"pad.modals.rateLimited": "Rate Limited.",
"pad.modals.rateLimited.explanation": "You sent too many messages to this pad so it disconnected you.",
"pad.modals.disconnected": "You have been disconnected.",
"pad.modals.disconnected.explanation": "The connection to the server was lost",
"pad.modals.disconnected.cause": "The server may be unavailable. Please notify the service administrator if this continues to happen.",

View File

@ -37,6 +37,12 @@ var channels = require("channels");
var stats = require('../stats');
var remoteAddress = require("../utils/RemoteAddress").remoteAddress;
const nodeify = require("nodeify");
const { RateLimiterMemory } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterMemory({
points: settings.commitRateLimiting.points,
duration: settings.commitRateLimiting.duration
});
/**
* A associative array that saves informations about a session
@ -164,6 +170,19 @@ exports.handleDisconnect = async function(client)
*/
exports.handleMessage = async function(client, message)
{
var env = process.env.NODE_ENV || 'development';
if (env === 'production') {
try {
await rateLimiter.consume(client.handshake.address); // consume 1 point per event from IP
}catch(e){
console.warn("Rate limited: ", client.handshake.address, " to reduce the amount of rate limiting that happens edit the rateLimit values in settings.json");
stats.meter('rateLimited').mark();
client.json.send({disconnect:"rateLimited"});
return;
}
}
if (message == null) {
return;
}

View File

@ -343,6 +343,22 @@ exports.importExportRateLimiting = {
"max": 10
};
/*
* From Etherpad 1.9.0 onwards, commits from individual users are rate limited
*
* The default is to allow at most 10 changes per IP in a 1 second window.
* After that the change is rejected.
*
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
*/
exports.commitRateLimiting = {
// duration of the rate limit window (seconds)
"duration": 1,
// maximum number of chanes per IP to allow during the rate limit window
"points": 10
};
/*
* From Etherpad 1.8.3 onwards, the maximum allowed size for a single imported
* file is always bounded.

5
src/package-lock.json generated
View File

@ -7472,6 +7472,11 @@
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"rate-limiter-flexible": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.1.4.tgz",
"integrity": "sha512-wtbWcqZbCqyAO1k63moagJlCZuPCEqbJJ6il1y2JVoiUyxlE36+cM7ETta9K6tTom9O5pNK+CxwHMgyyyJ31Gg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",

View File

@ -54,6 +54,7 @@
"nodeify": "1.0.1",
"npm": "6.14.5",
"openapi-backend": "2.4.1",
"rate-limiter-flexible": "^2.1.4",
"rehype": "^10.0.0",
"rehype-format": "^3.0.1",
"request": "2.88.2",

View File

@ -63,9 +63,8 @@ var padconnectionstatus = (function()
what: 'disconnected',
why: msg
};
var k = String(msg); // known reason why
if (!(k == 'userdup' || k == 'deleted' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth' || k == 'badChangeset' || k == 'corruptPad'))
if (!(k == 'userdup' || k == 'deleted' || k == 'looping' || k == 'slowcommit' || k == 'initsocketfail' || k == 'unauth' || k == 'rateLimited' || k == 'badChangeset' || k == 'corruptPad'))
{
k = 'disconnected';
}

View File

@ -279,6 +279,10 @@
<h1 data-l10n-id="pad.modals.deleted"></h1>
<p data-l10n-id="pad.modals.deleted.explanation"></p>
</div>
<div class="rateLimited">
<h1 data-l10n-id="pad.modals.rateLimited"></h1>
<p data-l10n-id="pad.modals.rateLimited.explanation"></p>
</div>
<div class="disconnected with_reconnect_timer">
<% e.begin_block("disconnected"); %>
<h1 data-l10n-id="pad.modals.disconnected"></h1>

View File

@ -16,7 +16,10 @@ sed 's#\"soffice\": null,#\"soffice\":\"/usr/bin/soffice\",#g' settings.json.tem
sed 's/\"allowAnyoneToImport\": false,/\"allowAnyoneToImport\": true,/g' settings.json.soffice > settings.json.allowImport
# Set "max": 10 to 100 to not agressively rate limit
sed 's/\"max\": 10/\"max\": 100/g' settings.json.allowImport > settings.json
sed 's/\"max\": 10/\"max\": 100/g' settings.json.allowImport > settings.json.rateLimit
# Set "points": 10 to 1000 to not agressively rate limit commits
sed 's/\"points\": 10/\"points\": 1000/g' settings.json.rateLimit > settings.json
# start Etherpad, assuming all dependencies are already installed.
#

View File

@ -9,6 +9,11 @@ MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
# reliably move to the etherpad base folder before running it
cd "${MY_DIR}/../../../"
# Set "points": 10 to 1000 to not agressively rate limit commits
sed 's/\"points\": 10/\"points\": 1000/g' settings.json.template > settings.json.points
# And enable loadTest
sed 's/\"loadTest\": false,/\"loadTest\": true,/g' settings.json.points > settings.json
# start Etherpad, assuming all dependencies are already installed.
#
# This is possible because the "install" section of .travis.yml already contains
@ -29,9 +34,6 @@ echo "Now I will try for 15 seconds to connect to Etherpad on http://localhost:9
echo "Successfully connected to Etherpad on http://localhost:9001"
# a copy of settings.json is necessary for the backend tests to work
cp settings.json.template settings.json
# Build the minified files?
curl http://localhost:9001/p/minifyme -f -s > /dev/null