Add very simple iteration of basic authentication to Porter.

pull/2664/head
derekpierre 2021-06-21 14:39:10 -04:00
parent ed17df1be3
commit 5485484e36
6 changed files with 71 additions and 15 deletions

View File

@ -0,0 +1,12 @@
FROM nucypher:latest
# Update
RUN apt update -y && apt upgrade -y
WORKDIR /code
COPY . /code
# Porter requirements
RUN pip3 install .[porter]
CMD ["/bin/bash"]

View File

@ -6,7 +6,7 @@ services:
image: nucypher:latest
build:
context: ../../..
dockerfile: deploy/docker/Dockerfile
dockerfile: deploy/docker/porter/Dockerfile
ports:
# Default Porter port
- "80:9155"
@ -22,16 +22,37 @@ services:
image: nucypher:latest
build:
context: ../../..
dockerfile: deploy/docker/Dockerfile
dockerfile: deploy/docker/porter/Dockerfile
ports:
# Default Porter port
- "443:9155"
volumes:
- .:/code
- ~/.local/share/nucypher:/nucypher
- "${TLS_DIR}:/etc/porter-tls/"
- "${TLS_DIR}:/etc/porter/tls/"
command: [ "nucypher", "porter", "run",
"--provider", "${WEB3_PROVIDER_URI}",
"--network", "${NUCYPHER_NETWORK}",
"--tls-key-filepath", "/etc/porter-tls/key.pem",
"--tls-certificate-filepath", "/etc/porter-tls/cert.pem"]
"--tls-key-filepath", "/etc/porter/tls/key.pem",
"--tls-certificate-filepath", "/etc/porter/tls/cert.pem"]
porter-https-auth:
restart: on-failure
image: nucypher:latest
build:
context: ../../..
dockerfile: deploy/docker/porter/Dockerfile
ports:
# Default Porter port
- "443:9155"
volumes:
- .:/code
- ~/.local/share/nucypher:/nucypher
- "${TLS_DIR}:/etc/porter/tls/"
- "${HTPASSWD_FILE}:/etc/porter/auth/htpasswd"
command: [ "nucypher", "porter", "run",
"--provider", "${WEB3_PROVIDER_URI}",
"--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"]

View File

@ -21,7 +21,8 @@ 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
from nucypher.cli.literature import BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED, PORTER_RUN_MESSAGE, \
BASIC_AUTH_REQUIRES_HTTPS
from nucypher.cli.options import (
option_network,
option_provider_uri,
@ -96,6 +97,7 @@ def exec_work_order(general_config, porter_uri, ursula, work_order):
@click.option('--http-port', help="Porter HTTP/HTTPS port for JSON endpoint", type=NETWORK_PORT, default=Porter.DEFAULT_PORT)
@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('--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,
@ -108,11 +110,24 @@ def run(general_config,
http_port,
tls_certificate_filepath,
tls_key_filepath,
basic_auth_filepath,
dry_run,
eager):
"""Start Porter's Web controller."""
emitter = setup_emitter(general_config, banner=Porter.BANNER)
# 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)
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)
if federated_only:
if not teacher_uri:
raise click.BadOptionUsage(option_name='--teacher',
@ -157,17 +172,15 @@ def run(general_config,
rpc_controller.start()
return
# 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)
emitter.message(f"Network: {PORTER.domain.capitalize()}", color='green')
if not federated_only:
emitter.message(f"Provider: {provider_uri}", color='green')
controller = PORTER.make_web_controller(crash_on_error=False)
http_scheme = "https" if tls_key_filepath and tls_certificate_filepath else "http"
if basic_auth_filepath:
emitter.message(f"Basic Authentication enabled", color='green')
controller = PORTER.make_web_controller(htpasswd_filepath=basic_auth_filepath, crash_on_error=False)
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)
return controller.start(port=http_port,

View File

@ -723,3 +723,5 @@ 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"
BASIC_AUTH_REQUIRES_HTTPS = "Basic authentication can only be used with HTTPS. --tls-key-filepath and --tls-certificate-filepath must also be provided"

View File

@ -19,6 +19,7 @@ from typing import List, Optional, Iterable
from constant_sorrow.constants import NO_CONTROL_PROTOCOL, NO_BLOCKCHAIN_CONNECTION
from eth_typing import ChecksumAddress
from flask import request, Response
from flask_htpasswd import HtPasswdAuth
from umbral.keys import UmbralPublicKey
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
@ -198,7 +199,7 @@ the Pipe for nucypher network operations
self.controller = controller
return controller
def make_web_controller(self, crash_on_error: bool = False):
def make_web_controller(self, crash_on_error: bool = False, htpasswd_filepath: str = None):
controller = WebController(app_name=self.APP_NAME,
crash_on_error=crash_on_error,
interface=self._interface_class(porter=self))
@ -206,6 +207,11 @@ the Pipe for nucypher network operations
# Register Flask Decorator
porter_flask_control = controller.make_control_transport()
if htpasswd_filepath:
porter_flask_control.config['FLASK_HTPASSWD_PATH'] = htpasswd_filepath
# ensure basic auth required for all endpoints
porter_flask_control.config['FLASK_AUTH_ALL'] = True
_ = HtPasswdAuth(app=porter_flask_control)
#
# Porter Control HTTP Endpoints

View File

@ -135,6 +135,7 @@ DEPLOY_REQUIRES = [
URSULA_REQUIRES = ['prometheus_client', 'sentry-sdk'] # TODO: Consider renaming to 'monitor', etc.
ALICE_REQUIRES = ['qrcode']
BOB_REQUIRES = ['qrcode']
PORTER_REQUIRES = ['flask-htpasswd'] # needed for basic authentication
EXTRAS = {
@ -146,7 +147,8 @@ EXTRAS = {
# User
'ursula': URSULA_REQUIRES,
'alice': ALICE_REQUIRES,
'bob': BOB_REQUIRES
'bob': BOB_REQUIRES,
'porter': PORTER_REQUIRES
}