summaryrefslogtreecommitdiff
path: root/nova/console
diff options
context:
space:
mode:
Diffstat (limited to 'nova/console')
-rw-r--r--nova/console/securityproxy/rfb.py190
-rw-r--r--nova/console/websocketproxy.py2
2 files changed, 191 insertions, 1 deletions
diff --git a/nova/console/securityproxy/rfb.py b/nova/console/securityproxy/rfb.py
new file mode 100644
index 0000000000..6aea4712f9
--- /dev/null
+++ b/nova/console/securityproxy/rfb.py
@@ -0,0 +1,190 @@
+# Copyright (c) 2014-2016 Red Hat, Inc
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import struct
+
+from oslo_config import cfg
+from oslo_log import log as logging
+import six
+
+from nova.console.rfb import auth
+from nova.console.rfb import auths
+from nova.console.securityproxy import base
+from nova import exception
+from nova.i18n import _, _LI
+
+LOG = logging.getLogger(__name__)
+
+CONF = cfg.CONF
+
+
+class RFBSecurityProxy(base.SecurityProxy):
+ """RFB Security Proxy Negotiation Helper.
+
+ This class proxies the initial setup of the RFB connection between the
+ client and the server. Then, when the RFB security negotiation step
+ arrives, it intercepts the communication, posing as a server with the
+ "None" authentication type to the client, and acting as a client (via
+ the methods below) to the server. After security negotiation, normal
+ proxying can be used.
+
+ Note: this code mandates RFB version 3.8, since this is supported by any
+ client and server impl written in the past 10+ years.
+
+ See the general RFB specification at:
+
+ https://tools.ietf.org/html/rfc6143
+ """
+
+ def __init__(self):
+ self.auth_schemes = auths.RFBAuthSchemeList()
+
+ def _make_var_str(self, message):
+ message_str = six.text_type(message)
+ message_bytes = message_str.encode('utf-8')
+ message_len = struct.pack("!I", len(message_bytes))
+ return message_len + message_bytes
+
+ def _fail(self, tenant_sock, compute_sock, message):
+ # Tell the client there's been a problem
+ result_code = struct.pack("!I", 1)
+ tenant_sock.sendall(result_code + self._make_var_str(message))
+
+ if compute_sock is not None:
+ # Tell the server that there's been a problem
+ # by sending the "Invalid" security type
+ compute_sock.sendall(auth.AUTH_STATUS_FAIL)
+
+ def _parse_version(self, version_str):
+ maj_str = version_str[4:7]
+ min_str = version_str[8:11]
+
+ return float("%d.%d" % (int(maj_str), int(min_str)))
+
+ def connect(self, tenant_sock, compute_sock):
+ """Initiate the RFB connection process.
+
+ This method performs the initial ProtocolVersion
+ and Security messaging, and returns the socket-like
+ object to use to communicate with the server securely.
+ If an error occurs SecurityProxyNegotiationFailed
+ will be raised.
+ """
+
+ def recv(sock, num):
+ b = sock.recv(num)
+ if len(b) != num:
+ reason = _("Incorrect read from socket, wanted %(wanted)d "
+ "bytes but got %(got)d. Socket returned "
+ "%(result)r") % {'wanted': num, 'got': len(b),
+ 'result': b}
+ raise exception.RFBAuthHandshakeFailed(reason=reason)
+ return b
+
+ # Negotiate version with compute server
+ compute_version = recv(compute_sock, auth.VERSION_LENGTH)
+ LOG.debug("Got version string '%s' from compute node",
+ compute_version[:-1])
+
+ if self._parse_version(compute_version) != 3.8:
+ reason = _("Security proxying requires RFB protocol "
+ "version 3.8, but server sent %s"), compute_version[:-1]
+ raise exception.SecurityProxyNegotiationFailed(reason=reason)
+ compute_sock.sendall(compute_version)
+
+ # Negotiate version with tenant
+ tenant_sock.sendall(compute_version)
+ tenant_version = recv(tenant_sock, auth.VERSION_LENGTH)
+ LOG.debug("Got version string '%s' from tenant",
+ tenant_version[:-1])
+
+ if self._parse_version(tenant_version) != 3.8:
+ reason = _("Security proxying requires RFB protocol version "
+ "3.8, but tenant asked for %s"), tenant_version[:-1]
+ raise exception.SecurityProxyNegotiationFailed(reason=reason)
+
+ # Negotiate security with server
+ permitted_auth_types_cnt = six.byte2int(recv(compute_sock, 1))
+
+ if permitted_auth_types_cnt == 0:
+ reason_len_raw = recv(compute_sock, 4)
+ reason_len = struct.unpack('!I', reason_len_raw)[0]
+ reason = recv(compute_sock, reason_len)
+
+ tenant_sock.sendall(auth.AUTH_STATUS_FAIL +
+ reason_len_raw + reason)
+
+ raise exception.SecurityProxyNegotiationFailed(reason=reason)
+
+ f = recv(compute_sock, permitted_auth_types_cnt)
+ permitted_auth_types = []
+ for auth_type in f:
+ if isinstance(auth_type, six.string_types):
+ auth_type = ord(auth_type)
+ permitted_auth_types.append(auth_type)
+
+ LOG.debug("The server sent security types %s", permitted_auth_types)
+
+ # Negotiate security with client before we say "ok" to the server
+ # send 1:[None]
+ tenant_sock.sendall(auth.AUTH_STATUS_PASS +
+ six.int2byte(auth.AuthType.NONE))
+ client_auth = six.byte2int(recv(tenant_sock, 1))
+
+ if client_auth != auth.AuthType.NONE:
+ self._fail(tenant_sock, compute_sock,
+ _("Only the security type None (%d) is supported") %
+ auth.AuthType.NONE)
+
+ reason = _("Client requested a security type other than "
+ " None (%(none_code)d): "
+ "%(auth_type)s") % {
+ 'auth_type': client_auth,
+ 'none_code': auth.AuthType.NONE}
+ raise exception.SecurityProxyNegotiationFailed(reason=reason)
+
+ try:
+ scheme = self.auth_schemes.find_scheme(permitted_auth_types)
+ except exception.RFBAuthNoAvailableScheme as e:
+ # Intentionally don't tell client what really failed
+ # as that's information leakage
+ self._fail(tenant_sock, compute_sock,
+ _("Unable to negotiate security with server"))
+ raise exception.SecurityProxyNegotiationFailed(
+ reason=_("No compute auth available: %s") % six.text_type(e))
+
+ compute_sock.sendall(six.int2byte(scheme.security_type()))
+
+ LOG.debug("Using security type %d with server, None with client",
+ scheme.security_type())
+
+ try:
+ compute_sock = scheme.security_handshake(compute_sock)
+ except exception.RFBAuthHandshakeFailed as e:
+ # Intentionally don't tell client what really failed
+ # as that's information leakage
+ self._fail(tenant_sock, None,
+ _("Unable to negotiate security with server"))
+ LOG.debug("Auth failed %s", six.text_type(e))
+ raise exception.SecurityProxyNegotiationFailed(
+ reason="Auth handshake failed")
+
+ LOG.info(_LI("Finished security handshake, resuming normal proxy "
+ "mode using secured socket"))
+
+ # we can just proxy the security result -- if the server security
+ # negotiation fails, we want the client to think it has failed
+
+ return compute_sock
diff --git a/nova/console/websocketproxy.py b/nova/console/websocketproxy.py
index b87abb17bb..d93d61ac6b 100644
--- a/nova/console/websocketproxy.py
+++ b/nova/console/websocketproxy.py
@@ -80,7 +80,7 @@ class TenantSock(object):
self.reqhandler.send_frames([encodeutils.safe_encode(data)])
def finish_up(self):
- self.reqhandler.send_frames([b''.join([self.queue])])
+ self.reqhandler.send_frames([b''.join(self.queue)])
def close(self):
self.finish_up()