#!/usr/bin/env python3 import os import sys import logging import django DIR_PATH = os.path.dirname(os.path.abspath(__file__)) ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, '..', '')) os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webvirtcloud.settings') CERT = DIR_PATH + '/cert.pem' if ROOT_PATH not in sys.path: sys.path.append(ROOT_PATH) django.setup() import re import socket #from six.moves import http_cookies as Cookie from http import cookies as Cookie from webvirtcloud.settings import WS_PORT, WS_HOST, WS_CERT from vrtManager.connection import CONN_SSH, CONN_SOCKET from console.sshtunnels import SSHTunnels from optparse import OptionParser parser = OptionParser() parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="Verbose mode", default=False) parser.add_option("-d", "--debug", dest="debug", action="store_true", help="Debug mode", default=False) parser.add_option("-H", "--host", dest="host", action="store", help="Listen host", default=WS_HOST) parser.add_option("-p", "--port", dest="port", action="store", help="Listen port", default=WS_PORT or 6080) parser.add_option("-c", "--cert", dest="cert", action="store", help="Certificate file path", default=WS_CERT or CERT) (options, args) = parser.parse_args() FORMAT = "%(asctime)s - %(name)s - %(levelname)s : %(message)s" if options.debug: logging.basicConfig(level=logging.DEBUG, format=FORMAT) options.verbose = True elif options.verbose: logging.basicConfig(level=logging.INFO, format=FORMAT) else: logging.basicConfig(level=logging.WARNING, format=FORMAT) try: from websockify import WebSocketProxy try: from websockify import ProxyRequestHandler except ImportError: USE_HANDLER = False else: USE_HANDLER = True except ImportError: try: from novnc.wsproxy import WebSocketProxy except ImportError: print('Unable to import a websockify implementation,\n please install one') sys.exit(1) else: USE_HANDLER = False def get_connection_infos(token): from instances.models import Instance from vrtManager.instance import wvmInstance try: temptoken = token.split('-', 1) host = int(temptoken[0]) uuid = temptoken[1] instance = Instance.objects.get(compute_id=host, uuid=uuid) conn = wvmInstance(instance.compute.hostname, instance.compute.login, instance.compute.password, instance.compute.type, instance.name) if instance.compute.hostname.count(':'): connhost = instance.compute.hostname.split(':')[0] connport = instance.compute.hostname.split(':')[1] else: connhost = instance.compute.hostname connport = 22 connuser = instance.compute.login conntype = instance.compute.type console_host = conn.get_console_listener_addr() console_port = conn.get_console_port() console_socket = conn.get_console_socket() except Exception as e: logging.error( 'Fail to retrieve console connection infos for token %s : %s' % (token, e)) raise return (connhost, connport, connuser, conntype, console_host, console_port, console_socket) class CompatibilityMixIn(object): def _new_client(self, daemon, socket_factory): # NoVNC uses it's own convention that forward token # from the request to a cookie header, we should check # also for this behavior hcookie = self.headers.get('cookie') if hcookie: cookie = Cookie.SimpleCookie() for hcookie_part in hcookie.split(';'): hcookie_part = hcookie_part.lstrip() try: cookie.load(hcookie_part) except Cookie.CookieError: # NOTE(stgleb): Do not print out cookie content # for security reasons. self.msg('Found malformed cookie') else: if 'token' in cookie: token = cookie['token'].value (connhost, connport, connuser, conntype, console_host, console_port, console_socket) = get_connection_infos(token) cnx_debug_msg = "Connection infos :\n" cnx_debug_msg += "- connhost : '%s'\n" % connhost cnx_debug_msg += "- connport : '%s'\n" % connport cnx_debug_msg += "- connuser : '%s'\n" % connuser cnx_debug_msg += "- conntype : '%s'\n" % conntype cnx_debug_msg += "- console_host : '%s'\n" % console_host cnx_debug_msg += "- console_port : '%s'\n" % console_port cnx_debug_msg += "- console_socket : '%s'\n" % console_socket logging.debug(cnx_debug_msg) if console_socket and conntype == CONN_SOCKET: # Local socket on local host self.msg('Try to open local socket %s' % console_socket) tsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) tsock.connect(console_socket) elif console_socket or re.match('^127\.', console_host): # Need tunnel to physical host if conntype != CONN_SSH: self.msg("Need a tunnel to access console but can't mount " + "one because it's not a SSH host") raise Exception(self.msg) try: # generate a string with all placeholders to avoid TypeErrors # in sprintf # https://github.com/retspen/webvirtmgr/pull/497 error_msg = "Try to open tunnel on %s@%s:%s on console %s:%s " error_msg += "(or socket %s)" self.msg(error_msg % (connuser, connhost, connport, console_host, console_port, console_socket)) tunnel = SSHTunnels(connhost, connuser, connport, console_host, console_port, console_socket) fd = tunnel.open_new() tunnel.unlock() tsock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) except Exception as e: self.msg("Fail to open tunnel : %s" % e) raise self.msg("Tunnel opened") else: # Direct access self.msg("connecting to: %s:%s" % (connhost, console_port)) tsock = socket_factory(connhost, console_port, connect=True) tunnel = None if self.verbose and not daemon: print(self.traffic_legend) # Start proxying try: self.msg("Start proxying") self.do_proxy(tsock) except Exception: if tunnel: self.vmsg( "%s:%s (via %s@%s:%s) : Websocket client or Target closed" % (console_host, console_port, connuser, connhost, connport)) if tsock: tsock.shutdown(socket.SHUT_RDWR) tsock.close() tunnel.close_all() raise if USE_HANDLER: class NovaProxyRequestHandler(ProxyRequestHandler, CompatibilityMixIn): def msg(self, *args, **kwargs): self.log_message(*args, **kwargs) def vmsg(self, *args, **kwargs): if self.verbose: self.msg(*args, **kwargs) def new_websocket_client(self): """ Called after a new WebSocket connection has been established. """ # Setup variable for compatibility daemon = self.server.daemon socket_factory = self.server.socket self._new_client(daemon, socket_factory) else: class NovaWebSocketProxy(WebSocketProxy, CompatibilityMixIn): def new_client(self): """ Called after a new WebSocket connection has been established. """ # Setup variable for compatibility daemon = self.daemon socket_factory = self.socket self._new_client(daemon, socket_factory) if __name__ == '__main__': if USE_HANDLER: # Create the WebSocketProxy with NovaProxyRequestHandler handler server = WebSocketProxy(RequestHandlerClass=NovaProxyRequestHandler, listen_host=options.host, listen_port=options.port, source_is_ipv6=False, verbose=options.verbose, cert=options.cert, key=None, ssl_only=False, daemon=False, record=False, web=False, traffic=False, target_host='ignore', target_port='ignore', wrap_mode='exit', wrap_cmd=None) else: # Create the NovaWebSockets proxy server = NovaWebSocketProxy(listen_host=options.host, listen_port=options.port, source_is_ipv6=False, verbose=options.verbose, cert=options.cert, key=None, ssl_only=False, daemon=False, record=False, web=False, target_host='ignore', target_port='ignore', wrap_mode='exit', wrap_cmd=None) server.start_server()