Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cc4395e
misses for deploy instructions
blankdots Aug 8, 2019
745554b
add microbadge for docker readme
blankdots Aug 8, 2019
f42fec2
Add tests for swift_browser_ui.front.
sampsapenna Aug 7, 2019
cfaef76
Add unit tests for CSRF protection in _convenience.
sampsapenna Aug 7, 2019
01d7ed9
Remove return value check for check_csrf in api_check.
sampsapenna Aug 7, 2019
6095936
Add missing tests for missed api_check lines.
sampsapenna Aug 7, 2019
4c06b05
Increase test coverage in api tests.
sampsapenna Aug 7, 2019
8e6d22f
Add tests for token rescope.
sampsapenna Aug 8, 2019
53b5a30
Bump to v0.3.1
sampsapenna Aug 8, 2019
63333c9
Force at minimum one object in unicode null tests.
sampsapenna Aug 8, 2019
ad7aaa5
Add a locust file for stressing the test server.
sampsapenna Aug 9, 2019
227947e
Merge pull request #15 from CSCfi/feature/initial-performance-testing
blankdots Aug 9, 2019
7374205
Update gitignore to ignore some additional files
sampsapenna Aug 9, 2019
ea84c38
Add unit tests for the error middleware handler.
sampsapenna Aug 9, 2019
9a7e00d
Re-enable HTTPS as an option.
sampsapenna Aug 12, 2019
3434b55
Update documentation after introducing SSL.
sampsapenna Aug 12, 2019
234c6e6
Add server run and shutdown function tests.
sampsapenna Aug 12, 2019
2b0195c
Merge pull request #17 from CSCfi/feature/middleware-tests
blankdots Aug 19, 2019
24c521a
Add source for the SSL/TLS ciphers.
sampsapenna Aug 19, 2019
04fac28
Update ciphers, lock out TLSv1 and TLSv1_1 as deprecated
sampsapenna Aug 19, 2019
7436f42
Merge pull request #16 from CSCfi/feature/enable-standalone-ssl-support
blankdots Aug 19, 2019
84bd5c0
Bump to v0.3.2
sampsapenna Aug 19, 2019
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,8 @@ venv.bak/

src/server.py

# dia temporary files
*.dia~

# environment variable files
setenvs*
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
author = 'CSC Developers'

# The full version, including alpha/beta/rc tags
version = release = '0.3.1'
version = release = '0.3.2'


# -- General configuration ---------------------------------------------------
Expand Down
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
89 changes: 89 additions & 0 deletions performance_tests/locustfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Locust file for testing the backend API performace."""

from locust import HttpLocust, TaskSet


def login(l_instance):
"""Handle locust user login."""
l_instance.client.post(
"/login/websso",
{"token": "the_actual_token_doesn't_matter"}
)


def logout(l_instance):
"""Handle locust user logout."""
l_instance.client.get(
"/login/kill"
)


def api_containers(l_instance):
"""Get container listing."""
l_instance.client.get(
"/api/buckets"
)


def api_objects(l_instance):
"""Get object listing."""
l_instance.client.get(
"/api/objects?bucket=test-container-0"
)


def api_active(l_instance):
"""Get active project."""
l_instance.client.get(
"/api/active"
)


def api_projects(l_instance):
"""Get available projects."""
l_instance.client.get(
"/api/projects"
)


def api_username(l_instance):
"""Get the username."""
l_instance.client.get(
"/api/username"
)


def api_project_meta(l_instance):
"""Get the project metadata."""
l_instance.client.get(
"/api/get-project-meta"
)


class UserBehaviour(TaskSet):
"""Locust task class for swift-browser-ui user case."""

tasks = {
api_containers: 1,
api_objects: 5,
api_active: 1,
api_projects: 1,
api_username: 1,
api_project_meta: 2,
}

def on_start(self):
"""Handle website login."""
login(self)

def on_stop(self):
"""Handle website logout."""
logout(self)


class APIUser(HttpLocust):
"""Locust API user class."""

task_set = UserBehaviour
min_wait = 100
max_wait = 1000
2 changes: 1 addition & 1 deletion swift_browser_ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@


__name__ = 'swift_browser_ui'
__version__ = '0.3.1'
__version__ = '0.3.2'
__author__ = 'CSC Developers'
__license__ = 'MIT License'
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
96 changes: 96 additions & 0 deletions tests/test_middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Module for testing ``swift_browser_ui.middlewares``."""


from aiohttp.web import HTTPUnauthorized, HTTPForbidden, HTTPNotFound
from aiohttp.web import Response, HTTPClientError, FileResponse
import asynctest

from swift_browser_ui.middlewares import error_middleware
from swift_browser_ui.front import index


async def return_401_handler(with_exception):
"""Return an HTTP401 error."""
if with_exception:
raise HTTPUnauthorized()
return Response(
status=401
)


async def return_403_handler(with_exception):
"""Return an HTTP403 error."""
if with_exception:
raise HTTPForbidden()
return Response(
status=403
)


async def return_404_handler(with_exception):
"""Return or raise an HTTP404 error."""
if with_exception:
raise HTTPNotFound()
return Response(
status=404
)


async def return_400_handler(with_exception):
"""Return or raise an HTTP400 error."""
if with_exception:
raise HTTPClientError()
return Response(
status=400
)


class MiddlewareTestClass(asynctest.TestCase):
"""Testing the error middleware."""

async def test_401_return(self):
"""Test 401 middleware when the 401 status is returned."""
resp = await error_middleware(None, return_401_handler)
self.assertEqual(resp.status, 401)
self.assertIsInstance(resp, FileResponse)

async def test_401_exception(self):
"""Test 401 middleware when the 401 status is risen."""
resp = await error_middleware(True, return_401_handler)
self.assertEqual(resp.status, 401)
self.assertIsInstance(resp, FileResponse)

async def test_403_return(self):
"""Test 403 middleware when the 403 status is returned."""
resp = await error_middleware(None, return_403_handler)
self.assertEqual(resp.status, 403)
self.assertIsInstance(resp, FileResponse)

async def test_403_exception(self):
"""Test 403 middleware when the 403 status is risen."""
resp = await error_middleware(True, return_403_handler)
self.assertEqual(resp.status, 403)
self.assertIsInstance(resp, FileResponse)

async def test_404_return(self):
"""Test 404 middleware when the 404 status is returned."""
resp = await error_middleware(None, return_404_handler)
self.assertEqual(resp.status, 404)
self.assertIsInstance(resp, FileResponse)

async def test_404_exception(self):
"""Test 404 middlewrae when the 404 status is risen."""
resp = await error_middleware(True, return_404_handler)
self.assertEqual(resp.status, 404)
self.assertIsInstance(resp, FileResponse)

async def test_error_middleware_no_error(self):
"""Test the general error middleware with correct status."""
resp = await error_middleware(None, index)
self.assertEqual(resp.status, 200)
self.assertIsInstance(resp, FileResponse)

async def test_error_middleware_non_handled_raise(self):
"""Test the general error middleware with other status code."""
with self.assertRaises(HTTPClientError):
await error_middleware(True, return_400_handler)
Loading