mirror of https://github.com/nucypher/nucypher.git
Add very simple iteration of basic authentication to Porter.
parent
ed17df1be3
commit
5485484e36
|
@ -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"]
|
|
@ -6,7 +6,7 @@ services:
|
||||||
image: nucypher:latest
|
image: nucypher:latest
|
||||||
build:
|
build:
|
||||||
context: ../../..
|
context: ../../..
|
||||||
dockerfile: deploy/docker/Dockerfile
|
dockerfile: deploy/docker/porter/Dockerfile
|
||||||
ports:
|
ports:
|
||||||
# Default Porter port
|
# Default Porter port
|
||||||
- "80:9155"
|
- "80:9155"
|
||||||
|
@ -22,16 +22,37 @@ services:
|
||||||
image: nucypher:latest
|
image: nucypher:latest
|
||||||
build:
|
build:
|
||||||
context: ../../..
|
context: ../../..
|
||||||
dockerfile: deploy/docker/Dockerfile
|
dockerfile: deploy/docker/porter/Dockerfile
|
||||||
ports:
|
ports:
|
||||||
# Default Porter port
|
# Default Porter port
|
||||||
- "443:9155"
|
- "443:9155"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/code
|
- .:/code
|
||||||
- ~/.local/share/nucypher:/nucypher
|
- ~/.local/share/nucypher:/nucypher
|
||||||
- "${TLS_DIR}:/etc/porter-tls/"
|
- "${TLS_DIR}:/etc/porter/tls/"
|
||||||
command: [ "nucypher", "porter", "run",
|
command: [ "nucypher", "porter", "run",
|
||||||
"--provider", "${WEB3_PROVIDER_URI}",
|
"--provider", "${WEB3_PROVIDER_URI}",
|
||||||
"--network", "${NUCYPHER_NETWORK}",
|
"--network", "${NUCYPHER_NETWORK}",
|
||||||
"--tls-key-filepath", "/etc/porter-tls/key.pem",
|
"--tls-key-filepath", "/etc/porter/tls/key.pem",
|
||||||
"--tls-certificate-filepath", "/etc/porter-tls/cert.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"]
|
||||||
|
|
|
@ -21,7 +21,8 @@ import click
|
||||||
from nucypher.blockchain.eth.networks import NetworksInventory
|
from nucypher.blockchain.eth.networks import NetworksInventory
|
||||||
from nucypher.characters.lawful import Ursula
|
from nucypher.characters.lawful import Ursula
|
||||||
from nucypher.cli.config import group_general_config
|
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 (
|
from nucypher.cli.options import (
|
||||||
option_network,
|
option_network,
|
||||||
option_provider_uri,
|
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('--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-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('--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('--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)
|
@click.option('--eager', help="Start learning and scraping the network before starting up other services", is_flag=True, default=True)
|
||||||
def run(general_config,
|
def run(general_config,
|
||||||
|
@ -108,11 +110,24 @@ def run(general_config,
|
||||||
http_port,
|
http_port,
|
||||||
tls_certificate_filepath,
|
tls_certificate_filepath,
|
||||||
tls_key_filepath,
|
tls_key_filepath,
|
||||||
|
basic_auth_filepath,
|
||||||
dry_run,
|
dry_run,
|
||||||
eager):
|
eager):
|
||||||
"""Start Porter's Web controller."""
|
"""Start Porter's Web controller."""
|
||||||
emitter = setup_emitter(general_config, banner=Porter.BANNER)
|
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 federated_only:
|
||||||
if not teacher_uri:
|
if not teacher_uri:
|
||||||
raise click.BadOptionUsage(option_name='--teacher',
|
raise click.BadOptionUsage(option_name='--teacher',
|
||||||
|
@ -157,17 +172,15 @@ def run(general_config,
|
||||||
rpc_controller.start()
|
rpc_controller.start()
|
||||||
return
|
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')
|
emitter.message(f"Network: {PORTER.domain.capitalize()}", color='green')
|
||||||
if not federated_only:
|
if not federated_only:
|
||||||
emitter.message(f"Provider: {provider_uri}", color='green')
|
emitter.message(f"Provider: {provider_uri}", color='green')
|
||||||
|
|
||||||
controller = PORTER.make_web_controller(crash_on_error=False)
|
if basic_auth_filepath:
|
||||||
http_scheme = "https" if tls_key_filepath and tls_certificate_filepath else "http"
|
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)
|
message = PORTER_RUN_MESSAGE.format(http_scheme=http_scheme, http_port=http_port)
|
||||||
emitter.message(message, color='green', bold=True)
|
emitter.message(message, color='green', bold=True)
|
||||||
return controller.start(port=http_port,
|
return controller.start(port=http_port,
|
||||||
|
|
|
@ -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}"
|
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"
|
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"
|
||||||
|
|
|
@ -19,6 +19,7 @@ from typing import List, Optional, Iterable
|
||||||
from constant_sorrow.constants import NO_CONTROL_PROTOCOL, NO_BLOCKCHAIN_CONNECTION
|
from constant_sorrow.constants import NO_CONTROL_PROTOCOL, NO_BLOCKCHAIN_CONNECTION
|
||||||
from eth_typing import ChecksumAddress
|
from eth_typing import ChecksumAddress
|
||||||
from flask import request, Response
|
from flask import request, Response
|
||||||
|
from flask_htpasswd import HtPasswdAuth
|
||||||
from umbral.keys import UmbralPublicKey
|
from umbral.keys import UmbralPublicKey
|
||||||
|
|
||||||
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
|
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
|
||||||
|
@ -198,7 +199,7 @@ the Pipe for nucypher network operations
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
return 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,
|
controller = WebController(app_name=self.APP_NAME,
|
||||||
crash_on_error=crash_on_error,
|
crash_on_error=crash_on_error,
|
||||||
interface=self._interface_class(porter=self))
|
interface=self._interface_class(porter=self))
|
||||||
|
@ -206,6 +207,11 @@ the Pipe for nucypher network operations
|
||||||
|
|
||||||
# Register Flask Decorator
|
# Register Flask Decorator
|
||||||
porter_flask_control = controller.make_control_transport()
|
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
|
# Porter Control HTTP Endpoints
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -135,6 +135,7 @@ DEPLOY_REQUIRES = [
|
||||||
URSULA_REQUIRES = ['prometheus_client', 'sentry-sdk'] # TODO: Consider renaming to 'monitor', etc.
|
URSULA_REQUIRES = ['prometheus_client', 'sentry-sdk'] # TODO: Consider renaming to 'monitor', etc.
|
||||||
ALICE_REQUIRES = ['qrcode']
|
ALICE_REQUIRES = ['qrcode']
|
||||||
BOB_REQUIRES = ['qrcode']
|
BOB_REQUIRES = ['qrcode']
|
||||||
|
PORTER_REQUIRES = ['flask-htpasswd'] # needed for basic authentication
|
||||||
|
|
||||||
EXTRAS = {
|
EXTRAS = {
|
||||||
|
|
||||||
|
@ -146,7 +147,8 @@ EXTRAS = {
|
||||||
# User
|
# User
|
||||||
'ursula': URSULA_REQUIRES,
|
'ursula': URSULA_REQUIRES,
|
||||||
'alice': ALICE_REQUIRES,
|
'alice': ALICE_REQUIRES,
|
||||||
'bob': BOB_REQUIRES
|
'bob': BOB_REQUIRES,
|
||||||
|
'porter': PORTER_REQUIRES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue