def new_websocket_client(self): """Called after a new WebSocket connection has been established.""" # Reopen the eventlet hub to make sure we don't share an epoll # fd with parent and/or siblings, which would be bad from eventlet import hubs hubs.use_hub() # The nova expected behavior is to have token # passed to the method GET of the request query = urlparse.urlparse(self.path).query token = urlparse.parse_qs(query).get("token", [""]).pop() if not token: # 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.getheader('cookie') if hcookie: cookie = Cookie.SimpleCookie() cookie.load(hcookie) if 'token' in cookie: token = cookie['token'].value ctxt = context.get_admin_context() rpcapi = consoleauth_rpcapi.ConsoleAuthAPI() connect_info = rpcapi.check_token(ctxt, token=token) if not connect_info: raise exception.InvalidToken(token=token) self.msg(_('connect info: %s'), str(connect_info)) host = connect_info['host'] port = int(connect_info['port']) # Connect to the target self.msg( _("connecting to: %(host)s:%(port)s") % { 'host': host, 'port': port }) tsock = self.socket(host, port, connect=True) # Handshake as necessary if connect_info.get('internal_access_path'): tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" % connect_info['internal_access_path']) while True: data = tsock.recv(4096, socket.MSG_PEEK) if data.find("\r\n\r\n") != -1: if data.split("\r\n")[0].find("200") == -1: raise Exception(_("Invalid Connection Info")) tsock.recv(len(data)) break # Start proxying try: self.do_proxy(tsock) except Exception: if tsock: tsock.shutdown(socket.SHUT_RDWR) tsock.close() self.vmsg( _("%(host)s:%(port)s: Target closed") % { 'host': host, 'port': port }) raise
def new_websocket_client(self): """Called after a new WebSocket connection has been established.""" # Reopen the eventlet hub to make sure we don't share an epoll # fd with parent and/or siblings, which would be bad from eventlet import hubs hubs.use_hub() # The nova expected behavior is to have token # passed to the method GET of the request parse = urlparse.urlparse(self.path) if parse.scheme not in ('http', 'https'): # From a bug in urlparse in Python < 2.7.4 we cannot support # special schemes (cf: http://bugs.python.org/issue9374) if sys.version_info < (2, 7, 4): raise exception.NovaException( _("We do not support scheme '%s' under Python < 2.7.4, " "please use http or https") % parse.scheme) query = parse.query token = urlparse.parse_qs(query).get("token", [""]).pop() if not token: # 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.getheader('cookie') if hcookie: cookie = Cookie.SimpleCookie() cookie.load(hcookie) if 'token' in cookie: token = cookie['token'].value ctxt = context.get_admin_context() rpcapi = consoleauth_rpcapi.ConsoleAuthAPI() connect_info = rpcapi.check_token(ctxt, token=token) if not connect_info: raise exception.InvalidToken(token=token) # Verify Origin expected_origin_hostname = self.headers.getheader('Host') if ':' in expected_origin_hostname: e = expected_origin_hostname if '[' in e and ']' in e: expected_origin_hostname = e.split(']')[0][1:] else: expected_origin_hostname = e.split(':')[0] origin_url = self.headers.getheader('Origin') # missing origin header indicates non-browser client which is OK if origin_url is not None: origin = urlparse.urlparse(origin_url) origin_hostname = origin.hostname origin_scheme = origin.scheme if origin_hostname == '' or origin_scheme == '': detail = _("Origin header not valid.") raise exception.ValidationError(detail=detail) if expected_origin_hostname != origin_hostname: detail = _("Origin header does not match this host.") raise exception.ValidationError(detail=detail) if not self.verify_origin_proto(connect_info, origin_scheme): detail = _("Origin header protocol does not match this host.") raise exception.ValidationError(detail=detail) self.msg(_('connect info: %s'), str(connect_info)) host = connect_info['host'] port = int(connect_info['port']) # Connect to the target self.msg(_("connecting to: %(host)s:%(port)s") % {'host': host, 'port': port}) tsock = self.socket(host, port, connect=True) # Handshake as necessary if connect_info.get('internal_access_path'): tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" % connect_info['internal_access_path']) while True: data = tsock.recv(4096, socket.MSG_PEEK) if data.find("\r\n\r\n") != -1: if data.split("\r\n")[0].find("200") == -1: raise exception.InvalidConnectionInfo() tsock.recv(len(data)) break # Start proxying try: self.do_proxy(tsock) except Exception: if tsock: tsock.shutdown(socket.SHUT_RDWR) tsock.close() self.vmsg(_("%(host)s:%(port)s: Target closed") % {'host': host, 'port': port}) raise
class ConsoleAuthTokensExtensionTestV21(test.NoDBTestCase): controller_class = console_auth_tokens_v21 _EXPECTED_OUTPUT = { 'console': { 'instance_uuid': fakes.FAKE_UUID, 'host': 'fake_host', 'port': '1234', 'internal_access_path': 'fake_access_path' } } # The database backend returns a ConsoleAuthToken.to_dict() and o.vo # StringField are unicode. And the port is an IntegerField. _EXPECTED_OUTPUT_DB = copy.deepcopy(_EXPECTED_OUTPUT) _EXPECTED_OUTPUT_DB['console'].update({ 'host': u'fake_host', 'port': 1234, 'internal_access_path': u'fake_access_path' }) def setUp(self): super(ConsoleAuthTokensExtensionTestV21, self).setUp() self.controller = self.controller_class.ConsoleAuthTokensController() self.req = fakes.HTTPRequest.blank('', use_admin_context=True) self.context = self.req.environ['nova.context'] @ddt.data(True, False) @mock.patch('nova.objects.ConsoleAuthToken.validate', return_value=objects.ConsoleAuthToken( instance_uuid=fakes.FAKE_UUID, host='fake_host', port='1234', internal_access_path='fake_access_path', console_type='rdp-html5', token=fakes.FAKE_UUID)) @mock.patch.object(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token', return_value={ 'instance_uuid': fakes.FAKE_UUID, 'host': 'fake_host', 'port': '1234', 'internal_access_path': 'fake_access_path', 'console_type': 'rdp-html5' }) def test_get_console_connect_info(self, enable_consoleauth, mock_check_token, mock_validate): self.flags(enable_consoleauth=enable_consoleauth, group='workarounds') output = self.controller.show(self.req, fakes.FAKE_UUID) if enable_consoleauth: self.assertEqual(self._EXPECTED_OUTPUT, output) mock_check_token.assert_called_once_with(self.context, fakes.FAKE_UUID) mock_validate.assert_not_called() else: self.assertEqual(self._EXPECTED_OUTPUT_DB, output) mock_validate.assert_called_once_with(self.context, fakes.FAKE_UUID) mock_check_token.assert_not_called() @ddt.data(True, False) @mock.patch('nova.objects.ConsoleAuthToken.validate', side_effect=exception.InvalidToken(token='***')) @mock.patch.object(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token', return_value=None) def test_get_console_connect_info_token_not_found(self, enable_consoleauth, mock_check_token, mock_validate): self.flags(enable_consoleauth=enable_consoleauth, group='workarounds') self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, self.req, fakes.FAKE_UUID) if enable_consoleauth: mock_check_token.assert_called_once_with(self.context, fakes.FAKE_UUID) mock_validate.assert_not_called() else: mock_validate.assert_called_once_with(self.context, fakes.FAKE_UUID) mock_check_token.assert_not_called() @ddt.data(True, False) @mock.patch('nova.objects.ConsoleAuthToken.validate', return_value=objects.ConsoleAuthToken( instance_uuid=fakes.FAKE_UUID, host='fake_host', port='1234', internal_access_path='fake_access_path', console_type='unauthorized_console_type', token=fakes.FAKE_UUID)) @mock.patch.object(consoleauth_rpcapi.ConsoleAuthAPI, 'check_token', return_value={ 'instance_uuid': fakes.FAKE_UUID, 'host': 'fake_host', 'port': '1234', 'internal_access_path': 'fake_access_path', 'console_type': 'unauthorized_console_type' }) def test_get_console_connect_info_nonrdp_console_type( self, enable_consoleauth, mock_check_token, mock_validate): self.flags(enable_consoleauth=enable_consoleauth, group='workarounds') self.assertRaises(webob.exc.HTTPUnauthorized, self.controller.show, self.req, fakes.FAKE_UUID) if enable_consoleauth: mock_check_token.assert_called_once_with(self.context, fakes.FAKE_UUID) mock_validate.assert_not_called() else: mock_validate.assert_called_once_with(self.context, fakes.FAKE_UUID) mock_check_token.assert_not_called()