From 99a8f5f21a886a15fdfd4ae41a4edcbd3b47e7b8 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 30 Aug 2014 01:32:48 -0600 Subject: [PATCH 1/6] Added create_cert.sh and ssl_options to enable SSL --- gns3server/cert_utils/create_cert.sh | 82 ++++++++++++++++++++++++++++ gns3server/server.py | 8 +++ 2 files changed, 90 insertions(+) create mode 100755 gns3server/cert_utils/create_cert.sh diff --git a/gns3server/cert_utils/create_cert.sh b/gns3server/cert_utils/create_cert.sh new file mode 100755 index 00000000..a4e20a81 --- /dev/null +++ b/gns3server/cert_utils/create_cert.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Bash shell script for generating self-signed certs. Run this in a folder, as it +# generates a few files. Large portions of this script were taken from the +# following artcile: +# +# http://usrportage.de/archives/919-Batch-generating-SSL-certificates.html +# +# Additional alterations by: Brad Landers +# Date: 2012-01-27 +# https://gist.github.com/bradland/1690807 + +# Script accepts a single argument, the fqdn for the cert + +DOMAIN="$1" +if [ -z "$DOMAIN" ]; then + DOMAIN="gns3server.localdomain.com" +fi + +fail_if_error() { + [ $1 != 0 ] && { + unset PASSPHRASE + exit 10 + } +} + +# Generate a passphrase +export PASSPHRASE=$(head -c 500 /dev/urandom | tr -dc a-z0-9A-Z | head -c 128; echo) + +# Certificate details; replace items in angle brackets with your own info +subj=" +C=CA +ST=Alberta +O=GNS3 +localityName=Calgary +commonName=gns3server.localdomain.com +organizationalUnitName=GNS3Server +emailAddress=gns3cert@gns3.com +" + +# Generate the server private key +openssl genrsa -aes256 -out $DOMAIN.key -passout env:PASSPHRASE 2048 +fail_if_error $? + +#openssl rsa -outform der -in $DOMAIN.pem -out $DOMAIN.key -passin env:PASSPHRASE + +# Generate the CSR +openssl req \ + -new \ + -batch \ + -subj "$(echo -n "$subj" | tr "\n" "/")" \ + -key $DOMAIN.key \ + -out $DOMAIN.csr \ + -passin env:PASSPHRASE +fail_if_error $? +cp $DOMAIN.key $DOMAIN.key.org +fail_if_error $? + +# Strip the password so we don't have to type it every time we restart Apache +openssl rsa -in $DOMAIN.key.org -out $DOMAIN.key -passin env:PASSPHRASE +fail_if_error $? + +# Generate the cert (good for 10 years) +openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt +fail_if_error $? \ No newline at end of file diff --git a/gns3server/server.py b/gns3server/server.py index d4869e53..f23f6abb 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -159,6 +159,14 @@ class Server(object): zmq.__version__, zmq.zmq_version())) kwargs = {"address": self._host} + + ssl_options={ + "certfile": "/home/michaelgale/nas/workspace/gns3-server/gns3server/certs/gns3server.localdomain.com.crt", + "keyfile": "/home/michaelgale/nas/workspace/gns3-server/gns3server/certs/gns3server.localdomain.com.key", + } + + kwargs['ssl_options'] = ssl_options + if parse_version(tornado.version) >= parse_version("3.1"): kwargs["max_buffer_size"] = 524288000 # 500 MB file upload limit tornado_app.listen(self._port, **kwargs) From bcf0aae531d4b09787d1bc9a2ed6f467e32afa4a Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 2 Sep 2014 22:17:06 -0600 Subject: [PATCH 2/6] Added HOME support and cert dir to create_cert script --- gns3server/cert_utils/create_cert.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/gns3server/cert_utils/create_cert.sh b/gns3server/cert_utils/create_cert.sh index a4e20a81..89a5bc67 100755 --- a/gns3server/cert_utils/create_cert.sh +++ b/gns3server/cert_utils/create_cert.sh @@ -29,6 +29,11 @@ # Script accepts a single argument, the fqdn for the cert +DST_DIR="$HOME/.conf/GNS3Certs/" +OLD_DIR=`pwd` + +#GNS3 Server expects to find certs with the default FQDN below. If you create +#different certs you will need to update server.py DOMAIN="$1" if [ -z "$DOMAIN" ]; then DOMAIN="gns3server.localdomain.com" @@ -37,10 +42,16 @@ fi fail_if_error() { [ $1 != 0 ] && { unset PASSPHRASE + cd $OLD_DIR exit 10 } } +mkdir -p $DST_DIR +fail_if_error $? +cd $DST_DIR + + # Generate a passphrase export PASSPHRASE=$(head -c 500 /dev/urandom | tr -dc a-z0-9A-Z | head -c 128; echo) @@ -56,7 +67,7 @@ emailAddress=gns3cert@gns3.com " # Generate the server private key -openssl genrsa -aes256 -out $DOMAIN.key -passout env:PASSPHRASE 2048 +openssl genrsa -aes256 -out $DST_DIR/$DOMAIN.key -passout env:PASSPHRASE 2048 fail_if_error $? #openssl rsa -outform der -in $DOMAIN.pem -out $DOMAIN.key -passin env:PASSPHRASE @@ -79,4 +90,6 @@ fail_if_error $? # Generate the cert (good for 10 years) openssl x509 -req -days 3650 -in $DOMAIN.csr -signkey $DOMAIN.key -out $DOMAIN.crt -fail_if_error $? \ No newline at end of file +fail_if_error $? + +cd $OLD_DIR \ No newline at end of file From a95cc678e91e301a27eab4c33885b364ada36d67 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 2 Sep 2014 22:33:45 -0600 Subject: [PATCH 3/6] Added server.py ssl mode dependant on cert existence --- gns3server/cert_utils/create_cert.sh | 1 + gns3server/server.py | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/gns3server/cert_utils/create_cert.sh b/gns3server/cert_utils/create_cert.sh index 89a5bc67..e08e2714 100755 --- a/gns3server/cert_utils/create_cert.sh +++ b/gns3server/cert_utils/create_cert.sh @@ -47,6 +47,7 @@ fail_if_error() { } } + mkdir -p $DST_DIR fail_if_error $? cd $DST_DIR diff --git a/gns3server/server.py b/gns3server/server.py index f23f6abb..2bc4a893 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -33,6 +33,7 @@ import tornado.ioloop import tornado.web import tornado.autoreload import pkg_resources +from os.path import expanduser from pkg_resources import parse_version from .config import Config @@ -136,6 +137,24 @@ class Server(object): JSONRPCWebSocket.register_destination(destination, instance.name) instance.start() # starts the new process + + def _get_cert_info(self): + """ + Finds the cert and key file needed for SSL + """ + + home = expanduser("~") + ssl_dir = "%s/.conf/GNS3Certs/" % (home) + log.debug("Looking for SSL certs in: %s" % (ssl_dir)) + + keyfile = "%s/gns3server.localdomain.com.key" % (ssl_dir) + certfile = "%s/gns3server.localdomain.com.crt" % (ssl_dir) + + if os.path.isfile(keyfile) and os.path.isfile(certfile): + return { "certfile" : certfile, + "keyfile" : keyfile, + } + def run(self): """ Starts the Tornado web server and ZeroMQ server. @@ -160,12 +179,11 @@ class Server(object): zmq.zmq_version())) kwargs = {"address": self._host} - ssl_options={ - "certfile": "/home/michaelgale/nas/workspace/gns3-server/gns3server/certs/gns3server.localdomain.com.crt", - "keyfile": "/home/michaelgale/nas/workspace/gns3-server/gns3server/certs/gns3server.localdomain.com.key", - } + ssl_options = self._get_cert_info() - kwargs['ssl_options'] = ssl_options + if ssl_options: + log.info("Certs found - starting in SSL mode") + kwargs['ssl_options'] = ssl_options if parse_version(tornado.version) >= parse_version("3.1"): kwargs["max_buffer_size"] = 524288000 # 500 MB file upload limit From 382e693fc85bd015725bacdab36be9d5cf7287b1 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 3 Sep 2014 00:05:06 -0600 Subject: [PATCH 4/6] Added authentication handler for basic auth check --- gns3server/handlers/auth_handler.py | 71 ++++++++++++++++++++++++++ gns3server/handlers/version_handler.py | 4 +- gns3server/server.py | 18 +++++-- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 gns3server/handlers/auth_handler.py diff --git a/gns3server/handlers/auth_handler.py b/gns3server/handlers/auth_handler.py new file mode 100644 index 00000000..0bedb40b --- /dev/null +++ b/gns3server/handlers/auth_handler.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2014 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Simple file upload & listing handler. +""" + + +import os +import tornado.web + +import logging +log = logging.getLogger(__name__) + +class GNS3BaseHandler(tornado.web.RequestHandler): + def get_current_user(self): + user = self.get_secure_cookie("user") + if not user: + return None + + if self.settings['required_user'] == user.decode("utf-8"): + return user + +class LoginHandler(tornado.web.RequestHandler): + def get(self): + self.write('
' + 'Name: ' + 'Password: ' + '' + '
') + + try: + redirect_to = self.get_argument("next") + self.set_secure_cookie("login_success_redirect_to", redirect_to) + except tornado.web.MissingArgumentError: + pass + + def post(self): + + user = self.get_argument("name") + password = self.get_argument("password") + + if self.settings['required_user'] == user and self.settings['required_pass'] == password: + self.set_secure_cookie("user", user) + auth_status = "successful" + else: + self.set_secure_cookie("user", "None") + auth_status = "failure" + + log.info("Authentication attempt %s: %s" %(auth_status, user)) + + try: + redirect_to = self.get_secure_cookie("login_success_redirect_to") + except tornado.web.MissingArgumentError: + redirect_to = "/" + + self.redirect(redirect_to) \ No newline at end of file diff --git a/gns3server/handlers/version_handler.py b/gns3server/handlers/version_handler.py index c85aa31c..3b338bd2 100644 --- a/gns3server/handlers/version_handler.py +++ b/gns3server/handlers/version_handler.py @@ -16,11 +16,13 @@ # along with this program. If not, see . import tornado.web +from .auth_handler import GNS3BaseHandler from ..version import __version__ -class VersionHandler(tornado.web.RequestHandler): +class VersionHandler(GNS3BaseHandler): + @tornado.web.authenticated def get(self): response = {'version': __version__} self.write(response) diff --git a/gns3server/server.py b/gns3server/server.py index 2bc4a893..275123ad 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -34,12 +34,15 @@ import tornado.web import tornado.autoreload import pkg_resources from os.path import expanduser +import base64 +import uuid from pkg_resources import parse_version from .config import Config from .handlers.jsonrpc_websocket import JSONRPCWebSocket from .handlers.version_handler import VersionHandler from .handlers.file_upload_handler import FileUploadHandler +from .handlers.auth_handler import LoginHandler from .builtins.server_version import server_version from .builtins.interfaces import interfaces from .modules import MODULES @@ -47,12 +50,12 @@ from .modules import MODULES import logging log = logging.getLogger(__name__) - class Server(object): # built-in handlers handlers = [(r"/version", VersionHandler), - (r"/upload", FileUploadHandler)] + (r"/upload", FileUploadHandler), + (r"/login", LoginHandler)] def __init__(self, host, port, ipc=False): @@ -160,6 +163,15 @@ class Server(object): Starts the Tornado web server and ZeroMQ server. """ + # FIXME: debug mode! + settings = { + "debug":True, + "cookie_secret": base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), + "login_url": "/login", + "required_user" : "test123", + "required_pass" : "test456", + } + router = self._create_zmq_router() # Add our JSON-RPC Websocket handler to Tornado self.handlers.extend([(r"/", JSONRPCWebSocket, dict(zmq_router=router))]) @@ -169,7 +181,7 @@ class Server(object): templates_dir = pkg_resources.resource_filename("gns3server", "templates") tornado_app = tornado.web.Application(self.handlers, template_path=templates_dir, - debug=True) # FIXME: debug mode! + **settings) # FIXME: debug mode! try: print("Starting server on {}:{} (Tornado v{}, PyZMQ v{}, ZMQ v{})".format(self._host, From b84dda3c8ec309423fd4f4d6400cf9da082bf5a7 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 3 Sep 2014 22:12:34 -0600 Subject: [PATCH 5/6] HTTP auth added to file_upload and jsonrpc --- gns3server/handlers/auth_handler.py | 11 +++++++++++ gns3server/handlers/file_upload_handler.py | 5 ++++- gns3server/handlers/jsonrpc_websocket.py | 13 +++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/gns3server/handlers/auth_handler.py b/gns3server/handlers/auth_handler.py index 0bedb40b..f136ab02 100644 --- a/gns3server/handlers/auth_handler.py +++ b/gns3server/handlers/auth_handler.py @@ -22,6 +22,7 @@ Simple file upload & listing handler. import os import tornado.web +import tornado.websocket import logging log = logging.getLogger(__name__) @@ -35,6 +36,16 @@ class GNS3BaseHandler(tornado.web.RequestHandler): if self.settings['required_user'] == user.decode("utf-8"): return user +class GNS3WebSocketBaseHandler(tornado.websocket.WebSocketHandler): + def get_current_user(self): + user = self.get_secure_cookie("user") + if not user: + return None + + if self.settings['required_user'] == user.decode("utf-8"): + return user + + class LoginHandler(tornado.web.RequestHandler): def get(self): self.write('
' diff --git a/gns3server/handlers/file_upload_handler.py b/gns3server/handlers/file_upload_handler.py index c819a401..15673604 100644 --- a/gns3server/handlers/file_upload_handler.py +++ b/gns3server/handlers/file_upload_handler.py @@ -23,6 +23,7 @@ Simple file upload & listing handler. import os import stat import tornado.web +from .auth_handler import GNS3BaseHandler from ..version import __version__ from ..config import Config @@ -30,7 +31,7 @@ import logging log = logging.getLogger(__name__) -class FileUploadHandler(tornado.web.RequestHandler): +class FileUploadHandler(GNS3BaseHandler): """ File upload handler. @@ -54,6 +55,7 @@ class FileUploadHandler(tornado.web.RequestHandler): except OSError as e: log.error("could not create the upload directory {}: {}".format(self._upload_dir, e)) + @tornado.web.authenticated def get(self): """ Invoked on GET request. @@ -70,6 +72,7 @@ class FileUploadHandler(tornado.web.RequestHandler): path=path, items=items) + @tornado.web.authenticated def post(self): """ Invoked on POST request. diff --git a/gns3server/handlers/jsonrpc_websocket.py b/gns3server/handlers/jsonrpc_websocket.py index 5b18496c..a226be78 100644 --- a/gns3server/handlers/jsonrpc_websocket.py +++ b/gns3server/handlers/jsonrpc_websocket.py @@ -22,6 +22,7 @@ JSON-RPC protocol over Websockets. import zmq import uuid import tornado.websocket +from .auth_handler import GNS3WebSocketBaseHandler from tornado.escape import json_decode from ..jsonrpc import JSONRPCParseError from ..jsonrpc import JSONRPCInvalidRequest @@ -33,7 +34,7 @@ import logging log = logging.getLogger(__name__) -class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): +class JSONRPCWebSocket(GNS3WebSocketBaseHandler): """ STOMP protocol over Tornado Websockets with message routing to ZeroMQ dealer clients. @@ -116,7 +117,15 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): """ log.info("Websocket client {} connected".format(self.session_id)) - self.clients.add(self) + + authenticated_user = self.get_current_user() + + if authenticated_user: + self.clients.add(self) + log.info("Websocket authenticated user: %s" % (authenticated_user)) + else: + self.close() + log.info("Websocket non-authenticated user attempt: %s" % (authenticated_user)) def on_message(self, message): """ From 6f9e0f6d2e2cc2544d460567cba74f601d91ae0a Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 3 Sep 2014 22:19:59 -0600 Subject: [PATCH 6/6] Moved certs to .config --- gns3server/cert_utils/create_cert.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/cert_utils/create_cert.sh b/gns3server/cert_utils/create_cert.sh index e08e2714..57427088 100755 --- a/gns3server/cert_utils/create_cert.sh +++ b/gns3server/cert_utils/create_cert.sh @@ -29,7 +29,7 @@ # Script accepts a single argument, the fqdn for the cert -DST_DIR="$HOME/.conf/GNS3Certs/" +DST_DIR="$HOME/.config/GNS3Certs/" OLD_DIR=`pwd` #GNS3 Server expects to find certs with the default FQDN below. If you create