summaryrefslogtreecommitdiff
path: root/nova/console
diff options
context:
space:
mode:
authorDaniel P. Berrange <berrange@redhat.com>2016-07-21 11:56:52 +0100
committerStephen Finucane <sfinucan@redhat.com>2018-01-12 10:00:42 +0000
commit30ceaaff5db79746944cd596c4ed3743dc0f46af (patch)
treee0ffdfd01ee7aa054698ba6061ddefa70d4c1d05 /nova/console
parentc5a1a9e711b9d22f70403c338629e370ffa98d5a (diff)
downloadnova-30ceaaff5db79746944cd596c4ed3743dc0f46af.tar.gz
console: Provide an RFB security proxy implementation
Instead of doing straight passthrough of the RFB protocol from the tenant sock to the compute socket, insert an RFB security proxy. This will MITM the initial RFB protocol handshake in order to negotiate an authentication scheme with the compute node that is distinct from that used by the tenant. Based on earlier work by Solly Ross <sross@redhat.com> Change-Id: I9cc9a380500715e60bd05aa5c29ee46bc6f8d6c2 Co-authored-by: Stephen Finucane <sfinucan@redhat.com> Implements: bp websocket-proxy-to-host-security
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()