From 532643ba52101f2c2bc0f84f2928de2eeba71aef Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 12 Dec 2023 12:30:05 -0500 Subject: [PATCH 1/5] Remove nginx docker folders since this is not something we want to specifically support in the repos. Porter operators can choose how they do TLS/SSL individually. --- deploy/docker/nginx/Dockerfile | 4 --- deploy/docker/nginx/docker-compose.yml | 38 ----------------------- deploy/docker/nginx/porter.local_location | 19 ------------ 3 files changed, 61 deletions(-) delete mode 100644 deploy/docker/nginx/Dockerfile delete mode 100644 deploy/docker/nginx/docker-compose.yml delete mode 100644 deploy/docker/nginx/porter.local_location diff --git a/deploy/docker/nginx/Dockerfile b/deploy/docker/nginx/Dockerfile deleted file mode 100644 index f7bb6b1..0000000 --- a/deploy/docker/nginx/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM nginxproxy/nginx-proxy:alpine - -# Copy porter.local virtual host location configuration file -COPY ./deploy/docker/nginx/porter.local_location /etc/nginx/vhost.d/ diff --git a/deploy/docker/nginx/docker-compose.yml b/deploy/docker/nginx/docker-compose.yml deleted file mode 100644 index 3834eb3..0000000 --- a/deploy/docker/nginx/docker-compose.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: '3' - -services: - - nginx-proxy: - restart: always - image: nginxproxy/nginx-proxy:alpine - build: - context: ../../.. - dockerfile: deploy/docker/nginx/Dockerfile - ports: - - "443:443" - volumes: - - /var/run/docker.sock:/tmp/docker.sock:ro - # because of the vhost name used below, the cert and key should be named "porter.local.crt" and "porter.local.key" respectively; - # otherwise a conf file needs to be specified that providers server configuration values including ssl_certificate and ssl_certificate_key - - "${TLS_DIR}:/etc/nginx/certs/" - - nginx-porter: - restart: on-failure - image: porter:latest - build: - context: ../../.. - dockerfile: deploy/docker/Dockerfile - expose: - # Default Porter port - - "9155" - volumes: - - .:/code - - ~/.local/share/nucypher:/nucypher - command: [ "nucypher-porter", "run", - "--eth-domain", "${WEB3_PROVIDER_URI}", - "--domain", "${NUCYPHER_NETWORK}" ] - environment: - - VIRTUAL_HOST=porter.local - - VIRTUAL_PORT=9155 - depends_on: - - nginx-proxy diff --git a/deploy/docker/nginx/porter.local_location b/deploy/docker/nginx/porter.local_location deleted file mode 100644 index 8c4771b..0000000 --- a/deploy/docker/nginx/porter.local_location +++ /dev/null @@ -1,19 +0,0 @@ -# -# Allow CORS for any domain by default - modify if not desired -# -# https://enable-cors.org/server_nginx.html -# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin -# -if ($request_method ~* "(GET|POST)") { - add_header "Access-Control-Allow-Origin" *; -} - -# Preflighted requests -if ($request_method = 'OPTIONS' ) { - add_header "Access-Control-Allow-Origin" *; - add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD"; - # Tell client that this pre-flight info is valid for 20 days - add_header 'Access-Control-Max-Age' 1728000; - add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type, Accept"; - return 204; -} From 7164d7492c2b0f47d1d982b35137bc23722ba0a4 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 12 Dec 2023 12:32:31 -0500 Subject: [PATCH 2/5] Update documentation and docker-compose. --- README.rst | 41 ++++++++++++++++++++++++-------- deploy/docker/docker-compose.yml | 6 ++--- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index a69b0bd..7585c86 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,8 @@ operate their own. Running a Porter Instance ------------------------- +By default, Porter runs on port ``9155``. + Security Considerations *********************** @@ -39,16 +41,33 @@ Security Considerations .. note:: - - Ideally, you would run Porter behind a reverse proxy (e.g. `nginx `_) for additional - functionality such as HTTPS, CORS, authentication etc. + Managing a Porter instance on ``mainnet`` requires solid server + administration skills. This includes understanding how to provision and + secure servers, applying security best practices, and maintaining + consistent system performance. Key competencies like network configuration, + SSL/TLS encryption, and CORS, are also essential to ensure the + secure and efficient operation of your Porter instance. + +.. warning:: + + By default, Porter runs over HTTP. However, Porter instances must be + secured with a valid HTTPS certificate in order to be compatible with + network applications. A Porter instance running without SSL/TLS is not + only insecure but also browser-based apps and websites will be + unable to connect. + + To secure your Porter instance with HTTPS, use a reverse proxy + like `Nginx `_ or + `Apache `_ for SSL + processing, and potentially `Let's Encrypt `_ + for automated SSL certificate issuance and renewal. Additionally, consider + using cloud-based services like AWS/Digital Ocean load balancers or + Cloudflare for SSL termination and enhanced security. Run via Docker ************** -By default, Porter runs on port ``9155``. - #. Get the latest ``porter`` image: .. code:: bash @@ -66,7 +85,8 @@ By default, Porter runs on port ``9155``. --restart=unless-stopped \ nucypher/porter:latest \ nucypher-porter run \ - --eth-endpoint \ + --eth-endpoint \ + --polygon-endpoint \ --domain The command above is for illustrative purposes and can be modified as @@ -113,7 +133,7 @@ For a full list of CLI options after installation ``nucypher-porter``, run: * Run Porter service via HTTP .. code:: console - $ nucypher-porter run --eth-endpoint --domain + $ nucypher-porter run --eth-endpoint --polygon-endpoint --domain ______ @@ -127,6 +147,7 @@ For a full list of CLI options after installation ``nucypher-porter``, run: TACo Domain: ETH Endpoint URI: ... + Polygon Endpoint URI: ... Running Porter Web Controller at http://127.0.0.1:9155 @@ -256,7 +277,7 @@ Example Response } } }, - "version":"1.0.0" + "version": "3.3.0" } .. note:: @@ -351,7 +372,7 @@ Example Response } ] }, - "version": "1.0.0" + "version": "3.3.0" } @@ -455,5 +476,5 @@ Example Response } ] }, - "version": "1.0.0" + "version": "3.3.0" } diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index a077432..ac6edda 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -15,6 +15,6 @@ services: - .:/code - ~/.local/share/nucypher:/nucypher command: ["nucypher-porter", "run", - "--eth-endpoint", "${WEB3_PROVIDER_URI}", - "--domain", "${NUCYPHER_NETWORK}", - "--allow-origins", "${PORTER_CORS_ALLOW_ORIGINS}"] # empty string if env var not defined which translates to CORS not enabled by default + "--eth-endpoint", "${ETH_WEB3_PROVIDER_URI}", + "--polygon-endpoint", "${POLY_WEB3_PROVIDER_URI}", + "--domain", "${TACO_DOMAIN}"] From fab46b5867d74533b450640ce9164a56403efa03 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 12 Dec 2023 14:54:13 -0500 Subject: [PATCH 3/5] Refactor timeout code so that it can be unit tested. Added unit test for timeout adjustment. --- porter/main.py | 40 ++++++++++++----------- tests/test_porter_configure_timeout.py | 44 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 tests/test_porter_configure_timeout.py diff --git a/porter/main.py b/porter/main.py index d0e5187..63c31e0 100644 --- a/porter/main.py +++ b/porter/main.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Dict, List, NamedTuple, Optional, Sequence +from typing import Dict, List, NamedTuple, Optional, Sequence, Union from constant_sorrow.constants import NO_CONTROL_PROTOCOL from eth_typing import ChecksumAddress @@ -152,15 +152,9 @@ def get_ursulas( include_ursulas: Optional[Sequence[ChecksumAddress]] = None, timeout: Optional[int] = None, ) -> List[UrsulaInfo]: - if timeout and timeout > self.MAX_GET_URSULAS_TIMEOUT: - self.log.warn( - f"Provided sampling timeout ({timeout}s) exceeds " - f"maximum ({self.MAX_GET_URSULAS_TIMEOUT}s); " - f"using {self.MAX_GET_URSULAS_TIMEOUT}s instead" - ) - timeout = self.MAX_GET_URSULAS_TIMEOUT - else: - timeout = timeout or self.MAX_GET_URSULAS_TIMEOUT + timeout = self._configure_timeout( + "sampling", timeout, self.MAX_GET_URSULAS_TIMEOUT + ) reservoir = self._make_reservoir(exclude_ursulas, include_ursulas) available_nodes_to_sample = len(reservoir.values) + len(reservoir.reservoir) @@ -244,15 +238,9 @@ def decrypt( timeout: Optional[int] = None, ) -> DecryptOutcome: decryption_client = ThresholdDecryptionClient(self) - if timeout and timeout > self.MAX_DECRYPTION_TIMEOUT: - self.log.warn( - f"Provided decryption timeout ({timeout}s) exceeds " - f"maximum ({self.MAX_DECRYPTION_TIMEOUT}s); " - f"using {self.MAX_DECRYPTION_TIMEOUT}s instead" - ) - timeout = self.MAX_DECRYPTION_TIMEOUT - else: - timeout = timeout or self.MAX_DECRYPTION_TIMEOUT + timeout = self._configure_timeout( + "decryption", timeout, self.MAX_DECRYPTION_TIMEOUT + ) successes, failures = decryption_client.gather_encrypted_decryption_shares( encrypted_requests=encrypted_decryption_requests, @@ -265,6 +253,20 @@ def decrypt( ) return decrypt_outcome + def _configure_timeout( + self, operation: str, timeout: Union[int, None], max_timeout: int + ): + if timeout and timeout > max_timeout: + self.log.warn( + f"Provided {operation} timeout ({timeout}s) exceeds " + f"maximum ({max_timeout}s); " + f"using {max_timeout}s instead" + ) + timeout = max_timeout + else: + timeout = timeout or max_timeout + return timeout + def _make_reservoir( self, exclude_ursulas: Optional[Sequence[ChecksumAddress]] = None, diff --git a/tests/test_porter_configure_timeout.py b/tests/test_porter_configure_timeout.py new file mode 100644 index 0000000..d628ec6 --- /dev/null +++ b/tests/test_porter_configure_timeout.py @@ -0,0 +1,44 @@ +import pytest + +from porter.main import Porter + + +@pytest.mark.parametrize( + "timeout_scenarios", + [ + (None, 10, 10), + (1, 10, 1), + (5, 10, 5), + (9, 10, 9), + (10, 10, 10), + (11, 10, 10), + (20, 10, 10), + (25, 10, 10), + ( + Porter.MAX_GET_URSULAS_TIMEOUT - 1, + Porter.MAX_GET_URSULAS_TIMEOUT, + Porter.MAX_GET_URSULAS_TIMEOUT - 1, + ), + ( + Porter.MAX_GET_URSULAS_TIMEOUT + 1, + Porter.MAX_GET_URSULAS_TIMEOUT, + Porter.MAX_GET_URSULAS_TIMEOUT, + ), + ( + Porter.MAX_DECRYPTION_TIMEOUT / 2, + Porter.MAX_DECRYPTION_TIMEOUT, + Porter.MAX_DECRYPTION_TIMEOUT / 2, + ), + ( + Porter.MAX_DECRYPTION_TIMEOUT * 2, + Porter.MAX_DECRYPTION_TIMEOUT, + Porter.MAX_DECRYPTION_TIMEOUT, + ), + ], +) +def test_porter_configure_timeout_defined_results(porter, timeout_scenarios): + provided_timeout, max_timeout, expected_timeout = timeout_scenarios + resultant_timeout = porter._configure_timeout( + operation="test", timeout=provided_timeout, max_timeout=max_timeout + ) + assert resultant_timeout == expected_timeout From 61710c3b2693754a8833796d9643481e10d7b73e Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 12 Dec 2023 15:21:16 -0500 Subject: [PATCH 4/5] Update README to provide information about configurable timeouts. --- README.rst | 59 +++++++++++++++++++++++++++++++++++++++----------- porter/main.py | 2 +- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 7585c86..27c3f18 100644 --- a/README.rst +++ b/README.rst @@ -65,6 +65,22 @@ Security Considerations Cloudflare for SSL termination and enhanced security. +Configurable Operation Timeouts +******************************* +Some Porter endpoints allow optional integer timeouts to be specified as a +parameter. However, to prevent DDOS attacks, timeouts are capped. By default +the ``/decrypt`` and ``/get_ursulas`` endpoints limit their timeouts at 15s. If +the optional timeout parameter is not provided or the provided timeout +parameter value is greater than the default timeout, the timeout used for the +operation will be the default timeout. + +If modifying the default timeout values is desirable, they can be configured +via environment variables: + +* ``PORTER_MAX_DECRYPTION_TIMEOUT`` for ``/decrypt`` operations +* ``PORTER_MAX_GET_URSULAS_TIMEOUT`` for ``/get_ursulas`` operations + + Run via Docker ************** @@ -226,6 +242,15 @@ Parameters | ``encrypted_decryption_requests`` | Dict[String, String] | | Base64 encoded encrypted decryption requests | | | | | keyed by node staking provider address. | +-----------------------------------+----------------------+------------------------------------------------+ +| ``timeout`` | *(Optional)* int | | The timeout for the operation. Default value | +| | | | is 15s unless the Porter instance is | +| | | | configured to modify the default setting via | +| | | | the ``PORTER_MAX_DECRYPTION_TIMEOUT`` env | +| | | | variable on startup. Timeouts provided that | +| | | | are greater than this max default value are | +| | | | capped at the default value | ++-----------------------------------+----------------------+------------------------------------------------+ + Returns ^^^^^^^ @@ -293,19 +318,27 @@ and associated information. Parameters ^^^^^^^^^^ -+----------------------------------+---------------+-----------------------------------------------+ -| **Parameter** | **Type** | **Description** | -+==================================+===============+===============================================+ -| ``quantity`` | Integer | Number of total TACo nodes to return. | -+----------------------------------+---------------+-----------------------------------------------+ -| ``include_ursulas`` *(Optional)* | List[String] | | List of Ursula checksum addresses to | -| | | | give preference to. If any of these Ursulas | -| | | | are unavailable, they will not be included | -| | | | in result. | -+----------------------------------+---------------+-----------------------------------------------+ -| ``exclude_ursulas`` *(Optional)* | List[String] | | List of Ursula checksum addresses to not | -| | | | include in the result. | -+----------------------------------+---------------+-----------------------------------------------+ ++----------------------------------+------------------+------------------------------------------------+ +| **Parameter** | **Type** | **Description** | ++==================================+==================+================================================+ +| ``quantity`` | Integer | Number of total TACo nodes to return. | ++----------------------------------+------------------+------------------------------------------------+ +| ``include_ursulas`` *(Optional)* | List[String] | | List of Ursula checksum addresses to | +| | | | give preference to. If any of these Ursulas | +| | | | are unavailable, they will not be included | +| | | | in result. | ++----------------------------------+------------------+------------------------------------------------+ +| ``exclude_ursulas`` *(Optional)* | List[String] | | List of Ursula checksum addresses to not | +| | | | include in the result. | ++----------------------------------+------------------+------------------------------------------------+ +| ``timeout`` | *(Optional)* int | | The timeout for the operation. Default value | +| | | | is 15s unless the Porter instance is | +| | | | configured to modify the default setting via | +| | | | the ``PORTER_MAX_GET_URSULAS_TIMEOUT`` env | +| | | | variable on startup. Timeouts provided that | +| | | | are greater than this max default value are | +| | | | capped at the default value | ++----------------------------------+------------------+------------------------------------------------+ Returns diff --git a/porter/main.py b/porter/main.py index 63c31e0..90d0073 100644 --- a/porter/main.py +++ b/porter/main.py @@ -56,7 +56,7 @@ class Porter(Learner): DEFAULT_PORT = 9155 - MAX_GET_URSULAS_TIMEOUT = os.getenv("PORTER_GET_URSULAS_TIMEOUT", default=15) + MAX_GET_URSULAS_TIMEOUT = os.getenv("PORTER_MAX_GET_URSULAS_TIMEOUT", default=15) MAX_DECRYPTION_TIMEOUT = os.getenv( "PORTER_MAX_DECRYPTION_TIMEOUT", default=ThresholdDecryptionClient.DEFAULT_DECRYPTION_TIMEOUT, From f0998d49243ede7e52b6840210df67d2c83f7c35 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 13 Dec 2023 08:15:48 -0500 Subject: [PATCH 5/5] Cleanup test to make it clearer. --- tests/test_porter_configure_timeout.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_porter_configure_timeout.py b/tests/test_porter_configure_timeout.py index d628ec6..fd49d24 100644 --- a/tests/test_porter_configure_timeout.py +++ b/tests/test_porter_configure_timeout.py @@ -4,7 +4,7 @@ @pytest.mark.parametrize( - "timeout_scenarios", + "provided_timeout,max_timeout,expected_timeout", [ (None, 10, 10), (1, 10, 1), @@ -36,8 +36,9 @@ ), ], ) -def test_porter_configure_timeout_defined_results(porter, timeout_scenarios): - provided_timeout, max_timeout, expected_timeout = timeout_scenarios +def test_porter_configure_timeout( + porter, provided_timeout, max_timeout, expected_timeout +): resultant_timeout = porter._configure_timeout( operation="test", timeout=provided_timeout, max_timeout=max_timeout )