diff --git a/apps/api/.env.example b/apps/api/.env.example index 4c84bd683737..e84f1e5e6a95 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -73,3 +73,7 @@ MINIO_ENDPOINT_SSL=0 # API key rate limit API_KEY_RATE_LIMIT="60/minute" + +# Number of reverse proxies in front of the API, used to read the client IP from +# X-Forwarded-For for rate limiting. Leave unset to keep the default behaviour. +# NUM_PROXIES= diff --git a/apps/api/plane/settings/common.py b/apps/api/plane/settings/common.py index 7f942a1bdca4..e9d83bc70025 100644 --- a/apps/api/plane/settings/common.py +++ b/apps/api/plane/settings/common.py @@ -150,6 +150,14 @@ "SCHEMA_COERCE_PATH_PK": False, } +# Number of reverse proxies in front of the API. DRF throttles use it to pick the +# client IP from X-Forwarded-For. Unset keeps DRF's default (None). +_num_proxies_raw = os.environ.get("NUM_PROXIES", "").strip() +if _num_proxies_raw.isdecimal() and len(_num_proxies_raw) <= 3: + REST_FRAMEWORK["NUM_PROXIES"] = int(_num_proxies_raw) +elif _num_proxies_raw: + _logger.warning("NUM_PROXIES: ignoring invalid value %r", _num_proxies_raw) + # API key throttle rate (DRF SimpleRateThrottle format, e.g. "60/minute") API_KEY_RATE_LIMIT = os.environ.get("API_KEY_RATE_LIMIT", "60/minute") diff --git a/apps/api/plane/tests/unit/settings/test_num_proxies.py b/apps/api/plane/tests/unit/settings/test_num_proxies.py new file mode 100644 index 000000000000..468f2a7d2fd7 --- /dev/null +++ b/apps/api/plane/tests/unit/settings/test_num_proxies.py @@ -0,0 +1,38 @@ +# Copyright (c) 2023-present Plane Software, Inc. and contributors +# SPDX-License-Identifier: AGPL-3.0-only +# See the LICENSE file for details. + +"""Unit tests for the optional NUM_PROXIES setting.""" + +import importlib + +import pytest + +from plane.settings import common + + +@pytest.fixture +def load_rest_framework(monkeypatch): + """Re-execute settings/common.py with NUM_PROXIES set to ``value`` (None = unset).""" + + def _load(value): + if value is None: + monkeypatch.delenv("NUM_PROXIES", raising=False) + else: + monkeypatch.setenv("NUM_PROXIES", value) + return importlib.reload(common).REST_FRAMEWORK + + yield _load + monkeypatch.undo() + importlib.reload(common) + + +@pytest.mark.unit +class TestNumProxiesSetting: + @pytest.mark.parametrize("value", [None, "", "abc", "-1", "9" * 5000]) + def test_unset_or_invalid_keeps_drf_default(self, load_rest_framework, value): + assert "NUM_PROXIES" not in load_rest_framework(value) + + @pytest.mark.parametrize("value, expected", [("1", 1), ("0", 0)]) + def test_non_negative_integer_is_applied(self, load_rest_framework, value, expected): + assert load_rest_framework(value)["NUM_PROXIES"] == expected diff --git a/deployments/cli/community/README.md b/deployments/cli/community/README.md index dd5079366be1..d3374f90158e 100644 --- a/deployments/cli/community/README.md +++ b/deployments/cli/community/README.md @@ -63,7 +63,7 @@ mkdir plane-selfhost cd plane-selfhost ``` -#### For *Docker Compose* based setup +#### For _Docker Compose_ based setup ``` curl -fsSL -o setup.sh https://github.com/makeplane/plane/releases/latest/download/setup.sh @@ -71,7 +71,7 @@ curl -fsSL -o setup.sh https://github.com/makeplane/plane/releases/latest/downlo chmod +x setup.sh ``` -#### For *Docker Swarm* based setup +#### For _Docker Swarm_ based setup ``` curl -fsSL -o setup.sh https://github.com/makeplane/plane/releases/latest/download/swarm.sh @@ -89,7 +89,8 @@ Lets get started by running the `./setup.sh` command. This will prompt you with the below options. -#### Docker Compose +#### Docker Compose + ```bash Select an Action you want to perform: 1) Install (x86_64) @@ -152,6 +153,16 @@ Below are the most import keys you must refer to. _ There are many other settings you can play with, but we suggest you configure `EMAIL SETTINGS` as it will enable you to invite your teammates onto the platform. +#### Client IP and rate limiting behind proxies + +Some endpoints are rate-limited per client IP. Two settings in `plane.env` control how that IP is determined: + +- **Plane directly on the internet behind the bundled proxy:** set `TRUSTED_PROXIES=private_ranges` (or your load balancer's ranges) and `NUM_PROXIES=1`. +- **Behind an additional load balancer or CDN:** add its ranges to `TRUSTED_PROXIES` (space-separated, e.g. `TRUSTED_PROXIES=private_ranges 203.0.113.0/24`) and raise `NUM_PROXIES` by one per extra hop (e.g. `NUM_PROXIES=2`). +- **Leaving `NUM_PROXIES` empty and `TRUSTED_PROXIES` at its default (`0.0.0.0/0`)** keeps the current behaviour. + +Restart the services after changing these values. + --- ### Continue with setup - Start Server (Docker Compose) @@ -189,7 +200,7 @@ You have successfully self hosted `Plane` instance. Access the application by go In case you want to make changes to `plane.env` variables, we suggest you to stop the services before doing that. -#### Docker Compose +#### Docker Compose Lets again run the `./setup.sh` command. You will again be prompted with the below options. This time select `3` to stop the services @@ -239,6 +250,7 @@ In case you want to make changes to `plane.env` variables, without stopping the Lets again run the `./setup.sh` command. You will again be prompted with the below options. This time select `4` to restart the services #### Docker Compose + ```bash Select a Action you want to perform: 1) Install (x86_64) @@ -275,7 +287,7 @@ If all goes well, you will see the confirmation from docker cli --- -### Upgrading Plane Version +### Upgrading Plane Version It is always advised to keep Plane up to date with the latest release. @@ -337,9 +349,9 @@ Once done with making changes in `plane.env` file, jump on to `Redeploy Stack` ### View Logs -There would a time when you might want to check what is happening inside the API, Worker or any other container. +There would a time when you might want to check what is happening inside the API, Worker or any other container. -Lets again run the `./setup.sh` command. You will again be prompted with the below options. +Lets again run the `./setup.sh` command. You will again be prompted with the below options. This time select `6` to view logs. @@ -361,7 +373,6 @@ Action [2]: 6 #### Docker Swarm - ```bash 1) Deploy Stack 2) Remove Stack @@ -375,7 +386,9 @@ Action [3]: 6 ``` #### Service Menu Options for Logs + This will further open sub-menu with list of services + ```bash Select a Service you want to view the logs for: 1) Web @@ -395,6 +408,7 @@ Service: 3 ``` Select any of the service to view the logs e.g. `3`. Expect something similar to this + ```bash api-1 | Waiting for database... api-1 | Database available! @@ -439,9 +453,9 @@ api-1 | [2024-05-02 03:56:03 +0000] [25] [INFO] Application startup complete. ``` -To exit this, use `CTRL+C` and then you will land on to the main-menu with the list of actions. +To exit this, use `CTRL+C` and then you will land on to the main-menu with the list of actions. -Similarly, you can view the logs of other services. +Similarly, you can view the logs of other services. --- @@ -500,12 +514,12 @@ When you want to restore the previously backed-up data, follow the instructions ```bash -------------------------------------------- - ____ _ ///////// - | _ \| | __ _ _ __ ___ ///////// - | |_) | |/ _` | '_ \ / _ \ ///// ///// - | __/| | (_| | | | | __/ ///// ///// - |_| |_|\__,_|_| |_|\___| //// - //// + ____ _ ///////// + | _ \| | __ _ _ __ ___ ///////// + | |_) | |/ _` | '_ \ / _ \ ///// ///// + | __/| | (_| | | | | __/ ///// ///// + |_| |_|\__,_|_| |_|\___| //// + //// -------------------------------------------- Project management tool from the future -------------------------------------------- @@ -550,7 +564,7 @@ When you want to restore the previously backed-up data on Plane Commercial Air-G ./restore-airgapped.sh ``` -1. After restoration, you are ready to start Plane Commercial (Airgapped) will all your previously saved data. +1. After restoration, you are ready to start Plane Commercial (Airgapped) will all your previously saved data. --- @@ -627,4 +641,5 @@ In case the suffixes are wrong or the mentioned volumes are not found, you will In case of successful migration, it will be a silent exit without error. Now its time to restart v0.14.0 setup. - \ No newline at end of file + + diff --git a/deployments/cli/community/docker-compose.yml b/deployments/cli/community/docker-compose.yml index d32459724293..f966e334f99b 100644 --- a/deployments/cli/community/docker-compose.yml +++ b/deployments/cli/community/docker-compose.yml @@ -33,6 +33,7 @@ x-proxy-env: &proxy-env LISTEN_HTTPS_PORT: ${LISTEN_HTTPS_PORT:-443} BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads} SITE_ADDRESS: ${SITE_ADDRESS:-:80} + TRUSTED_PROXIES: ${TRUSTED_PROXIES:-0.0.0.0/0} x-mq-env: &mq-env # RabbitMQ Settings RABBITMQ_HOST: ${RABBITMQ_HOST:-plane-mq} @@ -56,6 +57,7 @@ x-app-env: &app-env SECRET_KEY: ${SECRET_KEY} AMQP_URL: ${AMQP_URL:-amqp://plane:plane@plane-mq:5672/plane} API_KEY_RATE_LIMIT: ${API_KEY_RATE_LIMIT:-60/minute} + NUM_PROXIES: ${NUM_PROXIES:-} MINIO_ENDPOINT_SSL: ${MINIO_ENDPOINT_SSL:-0} LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY} WEBHOOK_ALLOWED_IPS: ${WEBHOOK_ALLOWED_IPS:-} diff --git a/deployments/cli/community/variables.env b/deployments/cli/community/variables.env index f141d01fb7dc..f79bf4743bbb 100644 --- a/deployments/cli/community/variables.env +++ b/deployments/cli/community/variables.env @@ -42,6 +42,8 @@ AMQP_URL= # If SSL Cert to be generated, set CERT_EMAIl="email " CERT_ACME_CA=https://acme-v02.api.letsencrypt.org/directory +# Proxies whose forwarded headers the bundled proxy trusts, e.g. "private_ranges" or your +# load balancer's CIDR ranges (space-separated). See README: "Client IP and rate limiting behind proxies". TRUSTED_PROXIES=0.0.0.0/0 SITE_ADDRESS=:80 CERT_EMAIL= @@ -84,6 +86,11 @@ API_KEY_RATE_LIMIT=60/minute # where period is second/minute/hour/day. AUTHENTICATION_RATE_LIMIT=10/minute +# Number of reverse proxies in front of the API (1 for the bundled proxy, plus one per +# extra load balancer/CDN hop), used to read the client IP for rate limiting. +# Leave empty to keep the default behaviour. +NUM_PROXIES= + # Live server environment variables — change this before deploying to production. # Generate a secure value with: openssl rand -hex 32 LIVE_SERVER_SECRET_KEY=change-this-key-on-deployment