mirror of https://github.com/nucypher/nucypher.git
Make CORS allow origins opt-in by default (instead of opt-out and having '*' as the default).
Ensure that empty string for --allow-origins equates to cors not enabled. Add tests for CORS CLI option.pull/2807/head
parent
5b60b2a0ab
commit
9853509716
|
@ -16,7 +16,8 @@ services:
|
|||
- ~/.local/share/nucypher:/nucypher
|
||||
command: ["nucypher", "porter", "run",
|
||||
"--provider", "${WEB3_PROVIDER_URI}",
|
||||
"--network", "${NUCYPHER_NETWORK}"]
|
||||
"--network", "${NUCYPHER_NETWORK}",
|
||||
"--allow-origins", "${PORTER_CORS_ALLOW_ORIGINS}"] # empty string if env var not defined which translates to CORS not enabled by default
|
||||
|
||||
porter-https:
|
||||
restart: on-failure
|
||||
|
@ -33,7 +34,8 @@ services:
|
|||
"--provider", "${WEB3_PROVIDER_URI}",
|
||||
"--network", "${NUCYPHER_NETWORK}",
|
||||
"--tls-key-filepath", "/etc/porter/tls/key.pem",
|
||||
"--tls-certificate-filepath", "/etc/porter/tls/cert.pem"]
|
||||
"--tls-certificate-filepath", "/etc/porter/tls/cert.pem",
|
||||
"--allow-origins", "${PORTER_CORS_ALLOW_ORIGINS}"] # empty string if env var not defined which translates to CORS not enabled by default
|
||||
|
||||
porter-https-auth:
|
||||
restart: on-failure
|
||||
|
@ -52,4 +54,5 @@ services:
|
|||
"--network", "${NUCYPHER_NETWORK}",
|
||||
"--tls-key-filepath", "/etc/porter/tls/key.pem",
|
||||
"--tls-certificate-filepath", "/etc/porter/tls/cert.pem",
|
||||
"--basic-auth-filepath", "/etc/porter/auth/htpasswd"]
|
||||
"--basic-auth-filepath", "/etc/porter/auth/htpasswd",
|
||||
"--allow-origins", "${PORTER_CORS_ALLOW_ORIGINS}"] # empty string if env var not defined which translates to CORS not enabled by default
|
||||
|
|
|
@ -21,8 +21,13 @@ import click
|
|||
from nucypher.blockchain.eth.networks import NetworksInventory
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.cli.config import group_general_config
|
||||
from nucypher.cli.literature import BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED, PORTER_RUN_MESSAGE, \
|
||||
BASIC_AUTH_REQUIRES_HTTPS
|
||||
from nucypher.cli.literature import (
|
||||
PORTER_BASIC_AUTH_ENABLED,
|
||||
PORTER_BASIC_AUTH_REQUIRES_HTTPS,
|
||||
PORTER_BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED,
|
||||
PORTER_CORS_ALLOWED_ORIGINS,
|
||||
PORTER_RUN_MESSAGE,
|
||||
)
|
||||
from nucypher.cli.options import (
|
||||
option_network,
|
||||
option_provider_uri,
|
||||
|
@ -34,7 +39,6 @@ from nucypher.cli.options import (
|
|||
from nucypher.cli.types import NETWORK_PORT
|
||||
from nucypher.cli.utils import setup_emitter, get_registry
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.utilities.porter.control.interfaces import PorterInterface
|
||||
from nucypher.utilities.porter.porter import Porter
|
||||
|
||||
|
||||
|
@ -58,7 +62,7 @@ def porter():
|
|||
@click.option('--tls-certificate-filepath', help="Pre-signed TLS certificate filepath", type=click.Path(dir_okay=False, exists=True, path_type=Path))
|
||||
@click.option('--tls-key-filepath', help="TLS private key filepath", type=click.Path(dir_okay=False, exists=True, path_type=Path))
|
||||
@click.option('--basic-auth-filepath', help="htpasswd filepath for basic authentication", type=click.Path(dir_okay=False, exists=True, resolve_path=True, path_type=Path))
|
||||
@click.option('--allow-origins', help="The CORS origin(s) to allow requests from - allows all origins by default", type=click.STRING, required=False, default="*")
|
||||
@click.option('--allow-origins', help="The CORS origin(s) string to allow requests from - used as the value for the 'Access-Control-Allow-Origin' response header; not configured by default", type=click.STRING)
|
||||
@click.option('--dry-run', '-x', help="Execute normally without actually starting Porter", is_flag=True)
|
||||
@click.option('--eager', help="Start learning and scraping the network before starting up other services", is_flag=True, default=True)
|
||||
def run(general_config,
|
||||
|
@ -81,14 +85,14 @@ def run(general_config,
|
|||
# HTTP/HTTPS
|
||||
if bool(tls_key_filepath) ^ bool(tls_certificate_filepath):
|
||||
raise click.BadOptionUsage(option_name='--tls-key-filepath, --tls-certificate-filepath',
|
||||
message=BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED)
|
||||
message=PORTER_BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED)
|
||||
|
||||
is_https = (tls_key_filepath and tls_certificate_filepath)
|
||||
|
||||
# check authentication
|
||||
if basic_auth_filepath and not is_https:
|
||||
raise click.BadOptionUsage(option_name='--basic-auth-filepath',
|
||||
message=BASIC_AUTH_REQUIRES_HTTPS)
|
||||
message=PORTER_BASIC_AUTH_REQUIRES_HTTPS)
|
||||
|
||||
if federated_only:
|
||||
if not teacher_uri:
|
||||
|
@ -138,12 +142,17 @@ def run(general_config,
|
|||
if not federated_only:
|
||||
emitter.message(f"Provider: {provider_uri}", color='green')
|
||||
|
||||
# firm up falsy status (i.e. change specified empty string to None)
|
||||
allow_origins = allow_origins if allow_origins else None
|
||||
if allow_origins:
|
||||
emitter.message(PORTER_CORS_ALLOWED_ORIGINS.format(allow_origins=allow_origins), color='green')
|
||||
|
||||
if basic_auth_filepath:
|
||||
emitter.message("Basic Authentication enabled", color='green')
|
||||
emitter.message(PORTER_BASIC_AUTH_ENABLED, color='green')
|
||||
|
||||
controller = PORTER.make_web_controller(crash_on_error=False,
|
||||
htpasswd_filepath=basic_auth_filepath,
|
||||
cors_origins=allow_origins)
|
||||
cors_allow_origins=allow_origins)
|
||||
http_scheme = "https" if is_https else "http"
|
||||
message = PORTER_RUN_MESSAGE.format(http_scheme=http_scheme, http_port=http_port)
|
||||
emitter.message(message, color='green', bold=True)
|
||||
|
|
|
@ -722,6 +722,10 @@ SUCCESSFUL_MANUALLY_SAVE_METADATA = "Successfully saved node metadata to {metada
|
|||
|
||||
PORTER_RUN_MESSAGE = "Running Porter Web Controller at {http_scheme}://127.0.0.1:{http_port}"
|
||||
|
||||
BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED = "Both --tls-key-filepath and --tls-certificate-filepath must be provided to launch porter with TLS; only one specified"
|
||||
PORTER_BASIC_AUTH_ENABLED = "Basic Authentication enabled"
|
||||
|
||||
BASIC_AUTH_REQUIRES_HTTPS = "Basic authentication can only be used with HTTPS. --tls-key-filepath and --tls-certificate-filepath must also be provided"
|
||||
PORTER_CORS_ALLOWED_ORIGINS = "CORS Allow Origins: {allow_origins}"
|
||||
|
||||
PORTER_BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED = "Both --tls-key-filepath and --tls-certificate-filepath must be provided to launch porter with TLS; only one specified"
|
||||
|
||||
PORTER_BASIC_AUTH_REQUIRES_HTTPS = "Basic authentication can only be used with HTTPS. --tls-key-filepath and --tls-certificate-filepath must also be provided"
|
||||
|
|
|
@ -200,7 +200,10 @@ the Pipe for nucypher network operations
|
|||
self.controller = controller
|
||||
return controller
|
||||
|
||||
def make_web_controller(self, crash_on_error: bool = False, htpasswd_filepath: Path = None, cors_origins: str = '*'):
|
||||
def make_web_controller(self,
|
||||
crash_on_error: bool = False,
|
||||
htpasswd_filepath: Path = None,
|
||||
cors_allow_origins: str = None):
|
||||
controller = WebController(app_name=self.APP_NAME,
|
||||
crash_on_error=crash_on_error,
|
||||
interface=self._interface_class(porter=self))
|
||||
|
@ -209,18 +212,25 @@ the Pipe for nucypher network operations
|
|||
# Register Flask Decorator
|
||||
porter_flask_control = controller.make_control_transport()
|
||||
|
||||
try:
|
||||
from flask_cors import CORS
|
||||
from flask_htpasswd import HtPasswdAuth
|
||||
except ImportError:
|
||||
raise ImportError('Porter installation is required - run "pip install nucypher[porter]" and try again.')
|
||||
# CORS origins
|
||||
if cors_allow_origins:
|
||||
try:
|
||||
from flask_cors import CORS
|
||||
except ImportError:
|
||||
raise ImportError('Porter installation is required for to specify CORS origins '
|
||||
'- run "pip install nucypher[porter]" and try again.')
|
||||
|
||||
# CORS
|
||||
porter_flask_control.config['CORS_ORIGINS'] = cors_origins
|
||||
_ = CORS(app=porter_flask_control)
|
||||
porter_flask_control.config['CORS_ORIGINS'] = cors_allow_origins
|
||||
_ = CORS(app=porter_flask_control)
|
||||
|
||||
# Basic Auth
|
||||
if htpasswd_filepath:
|
||||
try:
|
||||
from flask_htpasswd import HtPasswdAuth
|
||||
except ImportError:
|
||||
raise ImportError('Porter installation is required for basic authentication '
|
||||
'- run "pip install nucypher[porter]" and try again.')
|
||||
|
||||
porter_flask_control.config['FLASK_HTPASSWD_PATH'] = str(htpasswd_filepath.absolute())
|
||||
# ensure basic auth required for all endpoints
|
||||
porter_flask_control.config['FLASK_AUTH_ALL'] = True
|
||||
|
|
|
@ -22,9 +22,10 @@ import pytest
|
|||
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.cli.literature import (
|
||||
PORTER_RUN_MESSAGE,
|
||||
BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED,
|
||||
BASIC_AUTH_REQUIRES_HTTPS
|
||||
PORTER_BASIC_AUTH_ENABLED,
|
||||
PORTER_BASIC_AUTH_REQUIRES_HTTPS,
|
||||
PORTER_BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED,
|
||||
PORTER_RUN_MESSAGE, PORTER_CORS_ALLOWED_ORIGINS
|
||||
)
|
||||
from nucypher.cli.main import nucypher_cli
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
|
@ -96,7 +97,7 @@ def test_federated_porter_cli_run_tls_filepath_and_certificate(click_runner,
|
|||
'--tls-key-filepath', tempfile_path) # only tls-key provided
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code != 0 # both --tls-key-filepath and --tls-certificate-filepath must be provided for TLS
|
||||
assert BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED in result.output
|
||||
assert PORTER_BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED in result.output
|
||||
|
||||
porter_run_command = ('porter', 'run',
|
||||
'--dry-run',
|
||||
|
@ -105,7 +106,7 @@ def test_federated_porter_cli_run_tls_filepath_and_certificate(click_runner,
|
|||
'--tls-certificate-filepath', tempfile_path) # only certificate provided
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code != 0 # both --tls-key-filepath and --tls-certificate-filepath must be provided for TLS
|
||||
assert BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED in result.output
|
||||
assert PORTER_BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED in result.output
|
||||
|
||||
#
|
||||
# tls-key and certificate filepaths must exist
|
||||
|
@ -156,6 +157,56 @@ def test_federated_cli_run_https(click_runner, federated_ursulas, temp_dir_path,
|
|||
assert PORTER_RUN_MESSAGE.format(http_scheme="https", http_port=Porter.DEFAULT_PORT) in result.output
|
||||
|
||||
|
||||
def test_federated_cli_run_https_with_cors_origin(click_runner,
|
||||
federated_ursulas,
|
||||
temp_dir_path,
|
||||
federated_teacher_uri):
|
||||
tls_key_path = Path(temp_dir_path) / 'key.pem'
|
||||
_write_random_data(tls_key_path)
|
||||
certificate_file_path = Path(temp_dir_path) / 'fullchain.pem'
|
||||
_write_random_data(certificate_file_path)
|
||||
|
||||
allow_origins = "nucypher.com"
|
||||
|
||||
porter_run_command = ('porter', 'run',
|
||||
'--dry-run',
|
||||
'--federated-only',
|
||||
'--teacher', federated_teacher_uri,
|
||||
'--tls-key-filepath', tls_key_path,
|
||||
'--tls-certificate-filepath', certificate_file_path,
|
||||
'--allow-origins', allow_origins)
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert PORTER_RUN_MESSAGE.format(http_scheme="https", http_port=Porter.DEFAULT_PORT) in result.output
|
||||
assert PORTER_CORS_ALLOWED_ORIGINS.format(allow_origins=allow_origins) in result.output
|
||||
|
||||
|
||||
def test_federated_cli_run_https_with_empty_string_cors_origin(click_runner,
|
||||
federated_ursulas,
|
||||
temp_dir_path,
|
||||
federated_teacher_uri):
|
||||
tls_key_path = Path(temp_dir_path) / 'key.pem'
|
||||
_write_random_data(tls_key_path)
|
||||
certificate_file_path = Path(temp_dir_path) / 'fullchain.pem'
|
||||
_write_random_data(certificate_file_path)
|
||||
|
||||
empty_string_allow_origins = ""
|
||||
|
||||
porter_run_command = ('porter', 'run',
|
||||
'--dry-run',
|
||||
'--federated-only',
|
||||
'--teacher', federated_teacher_uri,
|
||||
'--tls-key-filepath', tls_key_path,
|
||||
'--tls-certificate-filepath', certificate_file_path,
|
||||
'--allow-origins', empty_string_allow_origins)
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert PORTER_RUN_MESSAGE.format(http_scheme="https", http_port=Porter.DEFAULT_PORT) in result.output
|
||||
# empty string translates to CORS not being enabled - empty origin string provides wild card comparison
|
||||
# with just header
|
||||
assert PORTER_CORS_ALLOWED_ORIGINS.format(allow_origins='') not in result.output
|
||||
|
||||
|
||||
def test_federated_cli_run_https_basic_auth(click_runner,
|
||||
federated_ursulas,
|
||||
federated_teacher_uri,
|
||||
|
@ -175,7 +226,7 @@ def test_federated_cli_run_https_basic_auth(click_runner,
|
|||
'--basic-auth-filepath', basic_auth_file)
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert "Basic Authentication enabled" in result.output
|
||||
assert PORTER_BASIC_AUTH_ENABLED in result.output
|
||||
|
||||
|
||||
def test_blockchain_porter_cli_run_simple(click_runner,
|
||||
|
@ -271,6 +322,37 @@ def test_blockchain_porter_cli_run_https(click_runner,
|
|||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert PORTER_RUN_MESSAGE.format(http_scheme="https", http_port=Porter.DEFAULT_PORT) in result.output
|
||||
# no CORS configured by default; empty origin string provides wild card comparison with just header
|
||||
assert PORTER_CORS_ALLOWED_ORIGINS.format(allow_origins='') not in result.output
|
||||
|
||||
|
||||
def test_blockchain_porter_cli_run_https_with_cors_origin(click_runner,
|
||||
blockchain_ursulas,
|
||||
testerchain,
|
||||
agency_local_registry,
|
||||
temp_dir_path,
|
||||
blockchain_teacher_uri):
|
||||
tls_key_path = Path(temp_dir_path) / 'key.pem'
|
||||
_write_random_data(tls_key_path)
|
||||
certificate_file_path = Path(temp_dir_path) / 'fullchain.pem'
|
||||
_write_random_data(certificate_file_path)
|
||||
|
||||
allow_origins = "*"
|
||||
|
||||
porter_run_command = ('porter', 'run',
|
||||
'--dry-run',
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--provider', TEST_PROVIDER_URI,
|
||||
'--registry-filepath', agency_local_registry.filepath,
|
||||
'--teacher', blockchain_teacher_uri,
|
||||
'--tls-key-filepath', tls_key_path,
|
||||
'--tls-certificate-filepath', certificate_file_path,
|
||||
'--allow-origins', allow_origins)
|
||||
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert PORTER_RUN_MESSAGE.format(http_scheme="https", http_port=Porter.DEFAULT_PORT) in result.output
|
||||
assert PORTER_CORS_ALLOWED_ORIGINS.format(allow_origins=allow_origins) in result.output
|
||||
|
||||
|
||||
def test_blockchain_porter_cli_run_https_basic_auth(click_runner,
|
||||
|
@ -297,7 +379,7 @@ def test_blockchain_porter_cli_run_https_basic_auth(click_runner,
|
|||
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code != 0
|
||||
assert BASIC_AUTH_REQUIRES_HTTPS in result.output
|
||||
assert PORTER_BASIC_AUTH_REQUIRES_HTTPS in result.output
|
||||
|
||||
# Basic Auth
|
||||
porter_run_command = ('porter', 'run',
|
||||
|
@ -312,7 +394,7 @@ def test_blockchain_porter_cli_run_https_basic_auth(click_runner,
|
|||
|
||||
result = click_runner.invoke(nucypher_cli, porter_run_command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert "Basic Authentication enabled" in result.output
|
||||
assert PORTER_BASIC_AUTH_ENABLED in result.output
|
||||
|
||||
|
||||
def _write_random_data(filepath: Path):
|
||||
|
|
Loading…
Reference in New Issue