Merge pull request #3274 from gradido/add_rate_limiter

feat(other): request limit
This commit is contained in:
einhornimmond 2024-01-23 09:03:12 +01:00 committed by GitHub
commit 8927aeaafb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 297 additions and 3 deletions

View File

@ -29,9 +29,11 @@
"dotenv": "^10.0.0",
"email-templates": "^10.0.1",
"express": "^4.17.1",
"express-slow-down": "^2.0.1",
"gradido-database": "file:../database",
"graphql": "^15.5.1",
"graphql-request": "5.0.0",
"helmet": "^5.1.1",
"i18n": "^0.15.1",
"jose": "^4.14.4",
"lodash.clonedeep": "^4.5.0",

View File

@ -4,6 +4,8 @@
import { Connection as DbConnection } from '@dbTools/typeorm'
import { ApolloServer } from 'apollo-server-express'
import express, { Express, json, urlencoded } from 'express'
import { slowDown } from 'express-slow-down'
import helmet from 'helmet'
import { Logger } from 'log4js'
import { CONFIG } from '@/config'
@ -56,6 +58,28 @@ export const createServer = async (
// cors
app.use(cors)
// Helmet helps secure Express apps by setting HTTP response headers.
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
app.use(helmet())
// rate limiter/ slow down to many requests
const limiter = slowDown({
windowMs: 1000, // 1 second
delayAfter: 10, // Allow 10 requests per 1 second.
delayMs: (hits) => hits * 50, // Add 100 ms of delay to every request after the 10th one.
/**
* So:
*
* - requests 1-10 are not delayed.
* - request 11 is delayed by 550ms
* - request 12 is delayed by 600ms
* - request 13 is delayed by 650ms
*
* and so on. After 1 seconds, the delay is reset to 0.
*/
})
app.use(limiter)
// bodyparser json
app.use(json())
// bodyparser urlencoded for elopage

View File

@ -3225,6 +3225,18 @@ expect@^27.2.5:
jest-message-util "^27.2.5"
jest-regex-util "^27.0.6"
express-rate-limit@7:
version "7.1.5"
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.1.5.tgz#af4c81143a945ea97f2599d13957440a0ddbfcfe"
integrity sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==
express-slow-down@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/express-slow-down/-/express-slow-down-2.0.1.tgz#60c4515467314675d89c54ec608e2d586aa30f87"
integrity sha512-zRogSZhNXJYKDBekhgFfFXGrOngH7Fub7Mx2g8OQ4RUBwSJP/3TVEKMgSGR/WlneT0mJ6NBUnidHhIELGVPe3w==
dependencies:
express-rate-limit "7"
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@ -3679,7 +3691,7 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
"gradido-database@file:../database":
version "2.0.1"
version "2.1.1"
dependencies:
"@types/uuid" "^8.3.4"
cross-env "^7.0.3"
@ -3826,6 +3838,11 @@ he@1.2.0, he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
helmet@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-5.1.1.tgz#609823c5c2e78aea62dd9afc8f544ca409da5e85"
integrity sha512-/yX0oVZBggA9cLJh8aw3PPCfedBnbd7J2aowjzsaWwZh7/UFY0nccn/aHAggIgWUFfnykX8GKd3a1pSbrmlcVQ==
highlight.js@^10.7.1:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"

View File

@ -0,0 +1,3 @@
limit_req_zone $binary_remote_addr zone=frontend:20m rate=5r/s;
limit_req_zone $binary_remote_addr zone=backend:25m rate=15r/s;
limit_req_zone $binary_remote_addr zone=api:5m rate=30r/s;

View File

@ -1,5 +1,8 @@
location /api/$FEDERATION_APIVERSION {
limit_req zone=api burst=60 nodelay;
limit_conn addr 30;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

View File

@ -21,6 +21,16 @@ server {
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
include /etc/nginx/common/limit_requests.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
#gzip_static on;
gzip on;
@ -42,6 +52,8 @@ server {
# Frontend (default)
location / {
limit_req zone=frontend burst=40 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -58,6 +70,8 @@ server {
# Backend
location /graphql {
limit_req zone=backend burst=10 nodelay;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -74,6 +88,8 @@ server {
# Backend webhooks
location /hook {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -90,6 +106,8 @@ server {
# Webhook reverse proxy
location /hooks/ {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_pass http://127.0.0.1:9000/hooks/;
access_log $GRADIDO_LOG_PATH/nginx-access.hooks.log gradido_log;
@ -98,6 +116,8 @@ server {
# Admin Frontend
location /admin {
limit_req zone=frontend burst=30 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

View File

@ -6,6 +6,16 @@ server {
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
include /etc/nginx/common/limit_requests.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
#gzip_static on;
gzip on;
@ -27,6 +37,8 @@ server {
# Frontend (default)
location / {
limit_req zone=frontend burst=40 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -43,6 +55,8 @@ server {
# Backend
location /graphql {
limit_req zone=backend burst=10 nodelay;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -59,6 +73,8 @@ server {
# Backend webhooks
location /hook {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -66,7 +82,6 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
# no trailing slash to keep the hook/ prefix
proxy_pass http://127.0.0.1:4000/hook;
proxy_redirect off;
@ -76,6 +91,8 @@ server {
# Webhook reverse proxy
location /hooks/ {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_pass http://127.0.0.1:9000/hooks/;
access_log $GRADIDO_LOG_PATH/nginx-access.hooks.log gradido_log;
@ -84,6 +101,8 @@ server {
# Admin Frontend
location /admin {
limit_req zone=frontend burst=30 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -97,7 +116,7 @@ server {
access_log $GRADIDO_LOG_PATH/nginx-access.admin.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn;
}
# Federation
$FEDERATION_NGINX_CONF

View File

@ -21,6 +21,16 @@ server {
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
include /etc/nginx/common/limit_requests.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
gzip on;
@ -28,6 +38,8 @@ server {
index updating.html;
location / {
limit_req zone=frontend;
limit_conn addr 10;
try_files /updating.html =404;
}

View File

@ -6,6 +6,16 @@ server {
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
include /etc/nginx/common/limit_requests.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
gzip on;
@ -13,6 +23,8 @@ server {
index updating.html;
location / {
limit_req zone=frontend;
limit_conn addr 10;
try_files /updating.html =404;
}

View File

@ -29,8 +29,10 @@
"dlt-database": "file:../dlt-database",
"dotenv": "10.0.0",
"express": "4.17.1",
"express-slow-down": "^2.0.1",
"graphql": "^16.7.1",
"graphql-scalars": "^1.22.2",
"helmet": "^7.1.0",
"log4js": "^6.7.1",
"nodemon": "^2.0.20",
"protobufjs": "^7.2.5",

View File

@ -0,0 +1,98 @@
# -----------------------------------------------
# !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!!
# !!! DO NOT MODIFY THIS FILE BY YOURSELF !!!
# -----------------------------------------------
type Community {
confirmedAt: String!
createdAt: String!
foreign: Boolean!
id: Int!
iotaTopic: String!
rootPublicKeyHex: String!
}
input CommunityDraft {
createdAt: String!
foreign: Boolean!
uuid: String!
}
"""The `Decimal` scalar type to represent currency values"""
scalar Decimal
"""Type of the transaction"""
enum InputTransactionType {
CREATION
RECEIVE
SEND
}
type Mutation {
addCommunity(data: CommunityDraft!): TransactionResult!
sendTransaction(data: TransactionDraft!): TransactionResult!
}
type Query {
communities(confirmed: Boolean, foreign: Boolean, uuid: String): [Community!]!
community(confirmed: Boolean, foreign: Boolean, uuid: String): Community!
isCommunityExist(confirmed: Boolean, foreign: Boolean, uuid: String): Boolean!
}
input TransactionDraft {
amount: Decimal!
backendTransactionId: Int!
createdAt: String!
recipientUser: UserIdentifier!
senderUser: UserIdentifier!
targetDate: String
type: InputTransactionType!
}
type TransactionError {
message: String!
name: String!
type: TransactionErrorType!
}
"""Transaction Error Type"""
enum TransactionErrorType {
ALREADY_EXIST
DB_ERROR
INVALID_SIGNATURE
LOGIC_ERROR
MISSING_PARAMETER
NOT_FOUND
NOT_IMPLEMENTED_YET
PROTO_DECODE_ERROR
PROTO_ENCODE_ERROR
}
type TransactionRecipe {
createdAt: String!
id: Int!
topic: String!
type: TransactionType!
}
type TransactionResult {
error: TransactionError
recipe: TransactionRecipe
succeed: Boolean!
}
"""Type of the transaction"""
enum TransactionType {
COMMUNITY_ROOT
GRADIDO_CREATION
GRADIDO_DEFERRED_TRANSFER
GRADIDO_TRANSFER
GROUP_FRIENDS_UPDATE
REGISTER_ADDRESS
}
input UserIdentifier {
accountNr: Int = 1
communityUuid: String
uuid: String!
}

View File

@ -6,6 +6,8 @@ import bodyParser from 'body-parser'
import cors from 'cors'
import express, { Express } from 'express'
// graphql
import { slowDown } from 'express-slow-down'
import helmet from 'helmet'
import { Logger } from 'log4js'
import { schema } from '@/graphql/schema'
@ -40,6 +42,27 @@ const createServer = async (
// plugins
logger,
})
// Helmet helps secure Express apps by setting HTTP response headers.
app.use(helmet())
// rate limiter/ slow down to many requests
const limiter = slowDown({
windowMs: 1000, // 1 second
delayAfter: 10, // Allow 10 requests per 1 second.
delayMs: (hits) => hits * 50, // Add 100 ms of delay to every request after the 10th one.
/**
* So:
*
* - requests 1-10 are not delayed.
* - request 11 is delayed by 550ms
* - request 12 is delayed by 600ms
* - request 13 is delayed by 650ms
*
* and so on. After 1 seconds, the delay is reset to 0.
*/
})
app.use(limiter)
await apollo.start()
app.use(
'/',

View File

@ -2833,6 +2833,18 @@ expect@^27.5.1:
jest-matcher-utils "^27.5.1"
jest-message-util "^27.5.1"
express-rate-limit@7:
version "7.1.5"
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.1.5.tgz#af4c81143a945ea97f2599d13957440a0ddbfcfe"
integrity sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==
express-slow-down@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/express-slow-down/-/express-slow-down-2.0.1.tgz#60c4515467314675d89c54ec608e2d586aa30f87"
integrity sha512-zRogSZhNXJYKDBekhgFfFXGrOngH7Fub7Mx2g8OQ4RUBwSJP/3TVEKMgSGR/WlneT0mJ6NBUnidHhIELGVPe3w==
dependencies:
express-rate-limit "7"
express@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@ -3407,6 +3419,11 @@ hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
helmet@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-7.1.0.tgz#287279e00f8a3763d5dccbaf1e5ee39b8c3784ca"
integrity sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==
highlight.js@^10.7.1:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"

View File

@ -24,8 +24,10 @@
"decimal.js-light": "^2.5.1",
"dotenv": "10.0.0",
"express": "4.17.1",
"express-slow-down": "^2.0.1",
"graphql": "15.5.1",
"graphql-request": "5.0.0",
"helmet": "^7.1.0",
"lodash.clonedeep": "^4.5.0",
"log4js": "^6.7.1",
"reflect-metadata": "^0.1.13",

View File

@ -24,6 +24,8 @@ import { Connection } from '@dbTools/typeorm'
import { apolloLogger } from './logger'
import { Logger } from 'log4js'
import helmet from 'helmet'
import { slowDown } from 'express-slow-down'
// i18n
// import { i18n } from './localization'
@ -62,6 +64,27 @@ export const createServer = async (
// cors
app.use(cors)
// Helmet helps secure Express apps by setting HTTP response headers.
app.use(helmet())
// rate limiter/ slow down to many requests
const limiter = slowDown({
windowMs: 1000, // 1 second
delayAfter: 10, // Allow 10 requests per 1 second.
delayMs: (hits) => hits * 50, // Add 100 ms of delay to every request after the 10th one.
/**
* So:
*
* - requests 1-10 are not delayed.
* - request 11 is delayed by 550ms
* - request 12 is delayed by 600ms
* - request 13 is delayed by 650ms
*
* and so on. After 1 seconds, the delay is reset to 0.
*/
})
app.use(limiter)
// bodyparser json
app.use(express.json())
// bodyparser urlencoded for elopage

View File

@ -2624,6 +2624,18 @@ expect@^27.5.1:
jest-matcher-utils "^27.5.1"
jest-message-util "^27.5.1"
express-rate-limit@7:
version "7.1.5"
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.1.5.tgz#af4c81143a945ea97f2599d13957440a0ddbfcfe"
integrity sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==
express-slow-down@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/express-slow-down/-/express-slow-down-2.0.1.tgz#60c4515467314675d89c54ec608e2d586aa30f87"
integrity sha512-zRogSZhNXJYKDBekhgFfFXGrOngH7Fub7Mx2g8OQ4RUBwSJP/3TVEKMgSGR/WlneT0mJ6NBUnidHhIELGVPe3w==
dependencies:
express-rate-limit "7"
express@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@ -3127,6 +3139,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
helmet@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-7.1.0.tgz#287279e00f8a3763d5dccbaf1e5ee39b8c3784ca"
integrity sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==
html-encoding-sniffer@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"