2020-04-17 11:02:18 +00:00
|
|
|
#!/usr/bin/env python3
|
2015-02-27 14:00:06 +00:00
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
2015-04-29 05:41:37 +00:00
|
|
|
import logging
|
2015-03-27 09:22:38 +00:00
|
|
|
import django
|
2015-02-27 14:00:06 +00:00
|
|
|
|
|
|
|
DIR_PATH = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, '..', ''))
|
2020-03-16 13:59:45 +00:00
|
|
|
os.environ["DJANGO_SETTINGS_MODULE"] = "webvirtcloud.settings"
|
2015-02-27 14:00:06 +00:00
|
|
|
CERT = DIR_PATH + '/cert.pem'
|
|
|
|
|
|
|
|
if ROOT_PATH not in sys.path:
|
|
|
|
sys.path.append(ROOT_PATH)
|
2015-03-27 09:22:38 +00:00
|
|
|
|
|
|
|
django.setup()
|
|
|
|
|
2020-03-16 13:59:45 +00:00
|
|
|
# VENV_PATH = ROOT_PATH + '/venv/lib/python3.6/site-packages'
|
2015-02-27 14:00:06 +00:00
|
|
|
# if VENV_PATH not in sys.path:
|
2015-03-27 09:22:38 +00:00
|
|
|
# sys.path.append(VENV_PATH)
|
2015-02-27 14:00:06 +00:00
|
|
|
|
2015-03-27 09:22:38 +00:00
|
|
|
import re
|
2015-02-27 14:00:06 +00:00
|
|
|
import socket
|
2020-03-16 13:59:45 +00:00
|
|
|
from six.moves import http_cookies as Cookie
|
2015-04-29 05:41:37 +00:00
|
|
|
from webvirtcloud.settings import WS_PORT, WS_HOST, WS_CERT
|
2015-02-27 14:00:06 +00:00
|
|
|
from vrtManager.connection import CONN_SSH, CONN_SOCKET
|
2020-03-16 13:59:45 +00:00
|
|
|
from console.tunnel import Tunnel
|
2015-04-29 05:41:37 +00:00
|
|
|
from optparse import OptionParser
|
2020-01-24 07:09:46 +00:00
|
|
|
|
2015-04-29 05:41:37 +00:00
|
|
|
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()
|
|
|
|
|
2015-05-13 06:42:20 +00:00
|
|
|
FORMAT = "%(asctime)s - %(name)s - %(levelname)s : %(message)s"
|
2015-04-29 05:41:37 +00:00
|
|
|
if options.debug:
|
2015-05-13 06:42:20 +00:00
|
|
|
logging.basicConfig(level=logging.DEBUG, format=FORMAT)
|
|
|
|
options.verbose = True
|
2015-04-29 05:41:37 +00:00
|
|
|
elif options.verbose:
|
2015-05-13 06:42:20 +00:00
|
|
|
logging.basicConfig(level=logging.INFO, format=FORMAT)
|
2015-04-29 05:41:37 +00:00
|
|
|
else:
|
2015-05-13 06:42:20 +00:00
|
|
|
logging.basicConfig(level=logging.WARNING, format=FORMAT)
|
2015-03-27 09:22:38 +00:00
|
|
|
|
2015-02-27 14:00:06 +00:00
|
|
|
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:
|
2020-01-24 07:09:46 +00:00
|
|
|
print('Unable to import a websockify implementation,\n please install one')
|
2015-02-27 14:00:06 +00:00
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
USE_HANDLER = False
|
|
|
|
|
|
|
|
|
|
|
|
def get_connection_infos(token):
|
2015-03-27 09:22:38 +00:00
|
|
|
from instances.models import Instance
|
2015-02-27 14:00:06 +00:00
|
|
|
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(':'):
|
2015-04-29 05:41:37 +00:00
|
|
|
connhost = instance.compute.hostname.split(':')[0]
|
|
|
|
connport = instance.compute.hostname.split(':')[1]
|
2015-02-27 14:00:06 +00:00
|
|
|
else:
|
|
|
|
connhost = instance.compute.hostname
|
|
|
|
connport = 22
|
|
|
|
connuser = instance.compute.login
|
|
|
|
conntype = instance.compute.type
|
|
|
|
console_host = conn.get_console_listen_addr()
|
|
|
|
console_port = conn.get_console_port()
|
|
|
|
console_socket = conn.get_console_socket()
|
2019-09-10 06:48:31 +00:00
|
|
|
except Exception as e:
|
2018-08-08 11:51:25 +00:00
|
|
|
logging.error('Fail to retrieve console connection infos for token %s : %s' % (token, e))
|
2015-04-29 05:41:37 +00:00
|
|
|
raise
|
2015-02-27 14:00:06 +00:00
|
|
|
return (connhost, connport, connuser, conntype, console_host,
|
|
|
|
console_port, console_socket)
|
|
|
|
|
|
|
|
|
|
|
|
class CompatibilityMixIn(object):
|
|
|
|
def _new_client(self, daemon, socket_factory):
|
2020-03-16 13:59:45 +00:00
|
|
|
# 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
|
|
|
|
|
|
|
|
# cookie = Cookie.SimpleCookie()
|
|
|
|
# cookie.load(self.headers.getheader('cookie'))
|
|
|
|
# if 'token' not in cookie:
|
|
|
|
# self.msg('No token cookie found !')
|
|
|
|
# return False
|
|
|
|
# token = cookie['token'].value
|
2015-02-27 14:00:06 +00:00
|
|
|
|
|
|
|
(connhost, connport, connuser, conntype, console_host, console_port,
|
|
|
|
console_socket) = get_connection_infos(token)
|
|
|
|
|
2018-08-08 11:51:25 +00:00
|
|
|
cnx_debug_msg = "Connection infos :\n"
|
2015-04-29 05:41:37 +00:00
|
|
|
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)
|
|
|
|
|
2015-02-27 14:00:06 +00:00
|
|
|
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")
|
2018-08-08 10:59:19 +00:00
|
|
|
raise Exception(self.msg)
|
2015-02-27 14:00:06 +00:00
|
|
|
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 = Tunnel()
|
|
|
|
fd = tunnel.open(connhost, connuser, connport,
|
|
|
|
console_host, console_port, console_socket)
|
|
|
|
tsock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
except Exception as e:
|
|
|
|
self.msg("Fail to open tunnel : %s" % e)
|
|
|
|
raise
|
2018-08-08 10:59:19 +00:00
|
|
|
self.msg("Tunnel opened")
|
2015-02-27 14:00:06 +00:00
|
|
|
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:
|
|
|
|
if tunnel:
|
|
|
|
self.vmsg(
|
|
|
|
"%s:%s (via %s@%s:%s) : Target closed" %
|
|
|
|
(console_host, console_port, connuser, connhost, connport))
|
|
|
|
if tsock:
|
|
|
|
tsock.shutdown(socket.SHUT_RDWR)
|
|
|
|
tsock.close()
|
|
|
|
if tunnel:
|
|
|
|
tunnel.close()
|
|
|
|
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,
|
2015-04-29 05:41:37 +00:00
|
|
|
listen_host=options.host,
|
|
|
|
listen_port=options.port,
|
2015-02-27 14:00:06 +00:00
|
|
|
source_is_ipv6=False,
|
2015-04-29 05:41:37 +00:00
|
|
|
verbose=options.verbose,
|
|
|
|
cert=options.cert,
|
2015-02-27 14:00:06 +00:00
|
|
|
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
|
2015-04-29 05:41:37 +00:00
|
|
|
server = NovaWebSocketProxy(listen_host=options.host,
|
|
|
|
listen_port=options.port,
|
2015-02-27 14:00:06 +00:00
|
|
|
source_is_ipv6=False,
|
2015-04-29 05:41:37 +00:00
|
|
|
verbose=options.verbose,
|
|
|
|
cert=options.cert,
|
2015-02-27 14:00:06 +00:00
|
|
|
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()
|