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__))
|
2022-11-02 05:54:35 +00:00
|
|
|
ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, "..", ""))
|
|
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings")
|
|
|
|
CERT = DIR_PATH + "/cert.pem"
|
2015-02-27 14:00:06 +00:00
|
|
|
|
|
|
|
if ROOT_PATH not in sys.path:
|
|
|
|
sys.path.append(ROOT_PATH)
|
2015-03-27 09:22:38 +00:00
|
|
|
|
|
|
|
django.setup()
|
|
|
|
|
|
|
|
import re
|
2015-02-27 14:00:06 +00:00
|
|
|
import socket
|
2022-11-02 05:54:35 +00:00
|
|
|
|
|
|
|
# from six.moves import http_cookies as Cookie
|
2022-07-21 10:31:37 +00:00
|
|
|
from http import cookies as Cookie
|
2020-08-13 15:55:40 +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-07-14 12:44:20 +00:00
|
|
|
from console.sshtunnels import SSHTunnels
|
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()
|
|
|
|
|
2022-11-02 05:54:35 +00:00
|
|
|
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,
|
|
|
|
)
|
2015-04-29 05:41:37 +00:00
|
|
|
|
|
|
|
(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
|
2022-11-02 05:54:35 +00:00
|
|
|
|
2015-02-27 14:00:06 +00:00
|
|
|
try:
|
|
|
|
from websockify import ProxyRequestHandler
|
|
|
|
except ImportError:
|
|
|
|
USE_HANDLER = False
|
|
|
|
else:
|
|
|
|
USE_HANDLER = True
|
|
|
|
except ImportError:
|
|
|
|
try:
|
|
|
|
from novnc.wsproxy import WebSocketProxy
|
|
|
|
except ImportError:
|
2022-11-02 05:54:35 +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:
|
2022-11-02 05:54:35 +00:00
|
|
|
temptoken = token.split("-", 1)
|
2015-02-27 14:00:06 +00:00
|
|
|
host = int(temptoken[0])
|
|
|
|
uuid = temptoken[1]
|
|
|
|
instance = Instance.objects.get(compute_id=host, uuid=uuid)
|
2022-11-02 05:54:35 +00:00
|
|
|
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]
|
2015-02-27 14:00:06 +00:00
|
|
|
else:
|
|
|
|
connhost = instance.compute.hostname
|
|
|
|
connport = 22
|
|
|
|
connuser = instance.compute.login
|
|
|
|
conntype = instance.compute.type
|
2022-08-22 12:12:33 +00:00
|
|
|
console_host = conn.get_console_listener_addr()
|
2015-02-27 14:00:06 +00:00
|
|
|
console_port = conn.get_console_port()
|
|
|
|
console_socket = conn.get_console_socket()
|
2019-09-10 06:48:31 +00:00
|
|
|
except Exception as e:
|
2020-07-14 12:44:20 +00:00
|
|
|
logging.error(
|
2022-11-02 05:54:35 +00:00
|
|
|
"Fail to retrieve console connection infos for token %s : %s" % (token, e)
|
|
|
|
)
|
2015-04-29 05:41:37 +00:00
|
|
|
raise
|
2022-11-02 05:54:35 +00:00
|
|
|
return (
|
|
|
|
connhost,
|
|
|
|
connport,
|
|
|
|
connuser,
|
|
|
|
conntype,
|
|
|
|
console_host,
|
|
|
|
console_port,
|
|
|
|
console_socket,
|
|
|
|
)
|
2015-02-27 14:00:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2022-11-02 05:54:35 +00:00
|
|
|
hcookie = self.headers.get("cookie")
|
2020-07-14 12:44:20 +00:00
|
|
|
|
2020-03-16 13:59:45 +00:00
|
|
|
if hcookie:
|
|
|
|
cookie = Cookie.SimpleCookie()
|
2022-11-02 05:54:35 +00:00
|
|
|
for hcookie_part in hcookie.split(";"):
|
2020-03-16 13:59:45 +00:00
|
|
|
hcookie_part = hcookie_part.lstrip()
|
|
|
|
try:
|
|
|
|
cookie.load(hcookie_part)
|
|
|
|
except Cookie.CookieError:
|
|
|
|
# NOTE(stgleb): Do not print out cookie content
|
|
|
|
# for security reasons.
|
2022-11-02 05:54:35 +00:00
|
|
|
self.msg("Found malformed cookie")
|
2020-03-16 13:59:45 +00:00
|
|
|
else:
|
2022-11-02 05:54:35 +00:00
|
|
|
if "token" in cookie:
|
|
|
|
token = cookie["token"].value
|
|
|
|
|
|
|
|
(
|
|
|
|
connhost,
|
|
|
|
connport,
|
|
|
|
connuser,
|
|
|
|
conntype,
|
|
|
|
console_host,
|
|
|
|
console_port,
|
|
|
|
console_socket,
|
|
|
|
) = get_connection_infos(token)
|
2015-02-27 14:00:06 +00:00
|
|
|
|
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
|
2022-11-02 05:54:35 +00:00
|
|
|
self.msg("Try to open local socket %s" % console_socket)
|
2015-02-27 14:00:06 +00:00
|
|
|
tsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
|
|
tsock.connect(console_socket)
|
2022-11-02 05:54:35 +00:00
|
|
|
elif console_socket or re.match("^127\.", console_host):
|
2015-02-27 14:00:06 +00:00
|
|
|
# Need tunnel to physical host
|
|
|
|
if conntype != CONN_SSH:
|
2022-11-02 05:54:35 +00:00
|
|
|
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)"
|
2022-11-02 05:54:35 +00:00
|
|
|
self.msg(
|
|
|
|
error_msg
|
|
|
|
% (
|
|
|
|
connuser,
|
|
|
|
connhost,
|
|
|
|
connport,
|
|
|
|
console_host,
|
|
|
|
console_port,
|
|
|
|
console_socket,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
tunnel = SSHTunnels(
|
|
|
|
connhost,
|
|
|
|
connuser,
|
|
|
|
connport,
|
|
|
|
console_host,
|
|
|
|
console_port,
|
|
|
|
console_socket,
|
|
|
|
)
|
2020-07-14 12:44:20 +00:00
|
|
|
fd = tunnel.open_new()
|
|
|
|
tunnel.unlock()
|
2015-02-27 14:00:06 +00:00
|
|
|
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)
|
2020-07-14 12:44:20 +00:00
|
|
|
except Exception:
|
2015-02-27 14:00:06 +00:00
|
|
|
if tunnel:
|
|
|
|
self.vmsg(
|
2022-11-02 05:54:35 +00:00
|
|
|
"%s:%s (via %s@%s:%s) : Websocket client or Target closed"
|
|
|
|
% (console_host, console_port, connuser, connhost, connport)
|
|
|
|
)
|
2015-02-27 14:00:06 +00:00
|
|
|
if tsock:
|
|
|
|
tsock.shutdown(socket.SHUT_RDWR)
|
|
|
|
tsock.close()
|
2020-07-14 12:44:20 +00:00
|
|
|
tunnel.close_all()
|
2015-02-27 14:00:06 +00:00
|
|
|
raise
|
|
|
|
|
2022-11-02 05:54:35 +00:00
|
|
|
|
2015-02-27 14:00:06 +00:00
|
|
|
if USE_HANDLER:
|
2022-11-02 05:54:35 +00:00
|
|
|
|
2015-02-27 14:00:06 +00:00
|
|
|
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)
|
2022-11-02 05:54:35 +00:00
|
|
|
|
2015-02-27 14:00:06 +00:00
|
|
|
else:
|
|
|
|
|
2022-11-02 05:54:35 +00:00
|
|
|
class NovaWebSocketProxy(WebSocketProxy, CompatibilityMixIn):
|
2015-02-27 14:00:06 +00:00
|
|
|
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)
|
|
|
|
|
2022-11-02 05:54:35 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2015-02-27 14:00:06 +00:00
|
|
|
if USE_HANDLER:
|
|
|
|
# Create the WebSocketProxy with NovaProxyRequestHandler handler
|
2022-11-02 05:54:35 +00:00
|
|
|
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,
|
|
|
|
)
|
2015-02-27 14:00:06 +00:00
|
|
|
else:
|
|
|
|
# Create the NovaWebSockets proxy
|
2022-11-02 05:54:35 +00:00
|
|
|
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,
|
|
|
|
)
|
2015-02-27 14:00:06 +00:00
|
|
|
server.start_server()
|