Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions docs/source/instructions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@ For the Pouta production environment for testing unsecurely without trust::

Setting up TLS termination proxy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The backend itself is not meant to be run as standalone in a production
environment. Therefore in a running config a TLS termination proxy should be
used to make the service secure. Setting up TLS termination is outside the
scope of this documentation, but a few useful links are provided along with
the necessary configs regarding this service. [#]_ [#]_
The backend can be run in secure mode, i.e. with HTTPS enabled, but for
scaling up a TLS termination proxy is recommended. Setting up TLS termination
is outside the scope of this documentation, but a few useful links are
provided along with the necessary configs regarding this service. [#]_ [#]_

Scaling up the service
----------------------
Expand Down
9 changes: 9 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ The following command line arguments are available for server startup.
trusted_dashboards in the specified address.
--set-origin-address TEXT Set the address that the program will be
redirected to from WebSSO
--secure Enable secure running, i.e. enable HTTPS.
--ssl-cert-file TEXT Specify the certificate to use with SSL.
--ssl-cert-key TEXT Specify the certificate key to use with SSL.
--help Show this message and exit.


Expand All @@ -81,5 +84,11 @@ The following command line arguments are available for server startup.
authentication endpoint, i.e. if the program has
been listed on the respective Openstack keystone
trusted_dashboard list. [#]_
--secure Enable HTTPS on the server, to enable secure
requests if there's no TLS termination proxy.
--ssl-cert-file TEXT Specify SSL certificate file. Required when
running in secure mode.
--ssl-cert-key TEXT Specify SSL certificate key. Required when
running in secure mode.

.. [#] https://docs.openstack.org/keystone/pike/advanced-topics/federation/websso.html
54 changes: 36 additions & 18 deletions swift_browser_ui/server.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""swift_browser_ui server related convenience functions."""

# Generic imports
# import ssl
import logging
import time
import sys
import asyncio
import hashlib
import os
import ssl

import uvloop
import cryptography.fernet
Expand Down Expand Up @@ -124,23 +124,41 @@ async def servinit():
return app


# def run_server_secure(app):
# """
# Run the server securely with a given ssl context.

# While this function is incomplete, the project is safe to run in
# production only via a TLS termination proxy with e.g. NGINX.
# """
# Setup ssl context
# sslcontext = ssl.create_default_context()
# sslcontext.set_ciphers(
# 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE' +
# '-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-' +
# 'AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-' +
# 'SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-' +
# 'RSA-AES128-SHA256'
# )
# aiohttp.web.run_app(app, ssl_context=sslcontext)
def run_server_secure(app, cert_file, cert_key):
"""
Run the server securely with a given ssl context.

While this function is incomplete, the project is safe to run in
production only via a TLS termination proxy with e.g. NGINX.
"""
# The chiphers are from the Mozilla project wiki, as a recommendation for
# the most secure and up-to-date build.
# https://wiki.mozilla.org/Security/Server_Side_TLS
logger = logging.getLogger("swift-browser-ui")
logger.debug("Running server securely.")
logger.debug("Setting up SSL context for the server.")
sslcontext = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
cipher_str = (
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE" +
"-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA" +
"-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM" +
"-SHA256:DHE-RSA-AES256-GCM-SHA384"
)
logger.debug(
"Setting following ciphers for SSL context: \n%s",
cipher_str
)
sslcontext.set_ciphers(cipher_str)
sslcontext.options |= ssl.OP_NO_TLSv1
sslcontext.options |= ssl.OP_NO_TLSv1_1
logger.debug("Loading cert chain.")
sslcontext.load_cert_chain(cert_file, cert_key)
aiohttp.web.run_app(
app,
access_log=aiohttp.web.logging.getLogger('aiohttp.access'),
port=setd['port'],
ssl_context=sslcontext,
)


def run_server_insecure(app):
Expand Down
21 changes: 19 additions & 2 deletions swift_browser_ui/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from .__init__ import __version__
from .settings import setd, set_key, FORMAT
from .server import servinit, run_server_insecure
from .server import servinit, run_server_insecure, run_server_secure
from ._convenience import setup_logging as conv_setup_logging


Expand Down Expand Up @@ -92,13 +92,28 @@ def cli(verbose, debug, logfile):
@click.option(
'--set-session-devmode', is_flag=True, default=False, hidden=True,
)
@click.option(
'--secure', is_flag=True, default=False,
help="Enable secure running, i.e. enable HTTPS."
)
@click.option(
'--ssl-cert-file', default=None, type=str,
help="Specify the certificate to use with SSL."
)
@click.option(
'--ssl-cert-key', default=None, type=str,
help="Specify the certificate key to use with SSL."
)
def start(
port,
auth_endpoint_url,
has_trust,
dry_run,
set_origin_address,
set_session_devmode,
secure,
ssl_cert_file,
ssl_cert_key,
):
"""Start the browser backend and server."""
logging.debug(
Expand Down Expand Up @@ -129,8 +144,10 @@ def start(
logging.debug(
"Running settings directory:%s", str(setd)
)
if not dry_run:
if not dry_run and not secure:
run_server_insecure(servinit())
if not dry_run and secure:
run_server_secure(servinit(), ssl_cert_file, ssl_cert_key)


def main():
Expand Down
52 changes: 51 additions & 1 deletion tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@


import os
import unittest
import ssl

from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
import aiohttp
import asynctest

from swift_browser_ui.server import servinit
from swift_browser_ui.server import servinit, run_server_insecure
from swift_browser_ui.server import kill_sess_on_shutdown, run_server_secure
from swift_browser_ui.settings import setd

from .creation import get_request_with_mock_openstack


# Set static folder in settings so it can be tested
setd['static_directory'] = os.getcwd() + '/swift_browser_ui_frontend'
Expand All @@ -29,6 +35,50 @@ async def test_servinit(self):
self.assertTrue(app is not None)


class TestServerShutdownHandler(asynctest.TestCase):
"""Test case for the server graceful shutdown handler."""

async def test_kill_sess_on_shutdown(self):
"""Test kill_sess_on_shutdown function."""
session, req = get_request_with_mock_openstack()

await kill_sess_on_shutdown(req.app)

self.assertNotIn(session, req.app["Creds"].keys())


class TestRunServerFunctions(unittest.TestCase):
"""Test class for server run functions."""

@staticmethod
def mock_ssl_context_creation(purpose=None):
"""Return a MagicMock instance of an ssl context."""
return unittest.mock.MagicMock(ssl.create_default_context)()

def test_run_server_secure(self):
"""Test run_server_secure function."""
run_app_mock = unittest.mock.MagicMock(aiohttp.web.run_app)
patch_run_app = unittest.mock.patch(
"swift_browser_ui.server.aiohttp.web.run_app", run_app_mock
)
patch_ssl_defcontext = unittest.mock.patch(
"swift_browser_ui.server.ssl.create_default_context",
self.mock_ssl_context_creation
)
with patch_run_app, patch_ssl_defcontext:
run_server_secure(None, None, None)
run_app_mock.assert_called_once()

def test_run_server_insecure(self):
"""Test run_server_insecure function."""
run_app_mock = unittest.mock.MagicMock(aiohttp.web.run_app)
with unittest.mock.patch(
"swift_browser_ui.server.aiohttp.web.run_app", run_app_mock
):
run_server_insecure(None)
run_app_mock.assert_called_once()


# After testing the server initialization, we can use the correctly starting
# server for testing other modules.
class AppTestCase(AioHTTPTestCase):
Expand Down