diff --git a/backend/package.json b/backend/package.json index 45fe7b050..43b7fb87c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts index c162d9f6f..250a4b901 100644 --- a/backend/src/server/createServer.ts +++ b/backend/src/server/createServer.ts @@ -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 diff --git a/backend/yarn.lock b/backend/yarn.lock index 0b3ceb323..234dc817a 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -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" diff --git a/deployment/bare_metal/nginx/common/limit_requests.conf b/deployment/bare_metal/nginx/common/limit_requests.conf new file mode 100644 index 000000000..e9026ee81 --- /dev/null +++ b/deployment/bare_metal/nginx/common/limit_requests.conf @@ -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; \ No newline at end of file diff --git a/deployment/bare_metal/nginx/sites-available/gradido-federation.conf.template b/deployment/bare_metal/nginx/sites-available/gradido-federation.conf.template index 2192b7dbb..5123deb5e 100644 --- a/deployment/bare_metal/nginx/sites-available/gradido-federation.conf.template +++ b/deployment/bare_metal/nginx/sites-available/gradido-federation.conf.template @@ -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'; diff --git a/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template b/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template index b8559a0fb..822c326d0 100644 --- a/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template +++ b/deployment/bare_metal/nginx/sites-available/gradido.conf.ssl.template @@ -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'; diff --git a/deployment/bare_metal/nginx/sites-available/gradido.conf.template b/deployment/bare_metal/nginx/sites-available/gradido.conf.template index 6b885a26a..1f673ee41 100644 --- a/deployment/bare_metal/nginx/sites-available/gradido.conf.template +++ b/deployment/bare_metal/nginx/sites-available/gradido.conf.template @@ -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 diff --git a/deployment/bare_metal/nginx/sites-available/update-page.conf.ssl.template b/deployment/bare_metal/nginx/sites-available/update-page.conf.ssl.template index 06bc5bbc0..ee7732230 100644 --- a/deployment/bare_metal/nginx/sites-available/update-page.conf.ssl.template +++ b/deployment/bare_metal/nginx/sites-available/update-page.conf.ssl.template @@ -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; } diff --git a/deployment/bare_metal/nginx/sites-available/update-page.conf.template b/deployment/bare_metal/nginx/sites-available/update-page.conf.template index e6cb51c7c..38dfb2d02 100644 --- a/deployment/bare_metal/nginx/sites-available/update-page.conf.template +++ b/deployment/bare_metal/nginx/sites-available/update-page.conf.template @@ -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; } diff --git a/dlt-connector/package.json b/dlt-connector/package.json index 5a4c54394..8b5ae357c 100644 --- a/dlt-connector/package.json +++ b/dlt-connector/package.json @@ -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", diff --git a/dlt-connector/schema.graphql b/dlt-connector/schema.graphql new file mode 100644 index 000000000..4ee07180d --- /dev/null +++ b/dlt-connector/schema.graphql @@ -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! +} \ No newline at end of file diff --git a/dlt-connector/src/server/createServer.ts b/dlt-connector/src/server/createServer.ts index ed87d54ac..50e8d96cb 100755 --- a/dlt-connector/src/server/createServer.ts +++ b/dlt-connector/src/server/createServer.ts @@ -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( '/', diff --git a/dlt-connector/yarn.lock b/dlt-connector/yarn.lock index 3c7a8bf36..6d50426b1 100644 --- a/dlt-connector/yarn.lock +++ b/dlt-connector/yarn.lock @@ -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" diff --git a/federation/package.json b/federation/package.json index fa21e04a1..1457b1be1 100644 --- a/federation/package.json +++ b/federation/package.json @@ -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", diff --git a/federation/src/server/createServer.ts b/federation/src/server/createServer.ts index b79847254..97729b882 100644 --- a/federation/src/server/createServer.ts +++ b/federation/src/server/createServer.ts @@ -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 diff --git a/federation/yarn.lock b/federation/yarn.lock index ca33138dd..74cc04521 100644 --- a/federation/yarn.lock +++ b/federation/yarn.lock @@ -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"