VYPR
High severityNVD Advisory· Published Mar 22, 2013· Updated Apr 29, 2026

CVE-2013-0335

CVE-2013-0335

Description

OpenStack Compute (Nova) Grizzly, Folsom (2012.2), and Essex (2012.1) allows remote authenticated users to gain access to a VM in opportunistic circumstances by using the VNC token for a deleted VM that was bound to the same VNC port.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
NovaPyPI
< 12.0.0a012.0.0a0

Affected products

6
  • cpe:2.3:a:openstack:essex:2012.1:*:*:*:*:*:*:*
  • cpe:2.3:a:openstack:folsom:2012.2:*:*:*:*:*:*:*
  • cpe:2.3:a:openstack:grizzly:2012.2:*:*:*:*:*:*:*
  • cpe:2.3:o:canonical:ubuntu_linux:11.10:*:*:*:*:*:*:*+ 2 more
    • cpe:2.3:o:canonical:ubuntu_linux:11.10:*:*:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:12.04:-:lts:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:12.10:*:*:*:*:*:*:*

Patches

5
48e81f1554ce

Fixed broken vncproxy flush tokens patch

https://github.com/openstack/novaRafi KhardalianMar 7, 2013via ghsa
3 files changed · +15 10
  • nova/compute/api.py+5 3 modified
    @@ -1561,12 +1561,14 @@ def get_vnc_console(self, context, instance, console_type):
             return {'url': connect_info['access_url']}
     
         @wrap_check_policy
    -    def validate_vnc_console(self, context, instance_id, host, port):
    +    def validate_vnc_console(self, context, instance_id, host, port,
    +                             console_type):
             """Validate VNC Console for an instance."""
             instance = self.get(context, instance_id)
             output = self._call_compute_message('get_vnc_console',
    -                                            context,
    -                                            instance)
    +                context,
    +                instance,
    +                params={"console_type": console_type})
             return (port == output['port'] and host == output['host'])
     
         @wrap_check_policy
    
  • nova/consoleauth/manager.py+5 4 modified
    @@ -84,22 +84,23 @@ def authorize_console(self, context, token, console_type, host, port,
     
             LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals())
     
    -    def _validate_console(self, token):
    +    def _validate_console(self, context, token):
             console_valid = False
             token_dict = self.tokens[token]
             try:
                 console_valid = self.compute_api.validate_vnc_console(context,
    -                                                token_dict['instance_uuid'],
    +                                                token_dict['instance_id'],
                                                     token_dict['host'],
    -                                                token_dict['port'])
    +                                                token_dict['port'],
    +                                                token_dict['console_type'])
             except exception.InstanceNotFound:
                 pass
             return console_valid
     
         def check_token(self, context, token):
             token_valid = token in self.tokens
             LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals())
    -        if token_valid and self._validate_console(token):
    +        if token_valid and self._validate_console(context, token):
                 return self.tokens[token]
     
         def delete_tokens_for_instance(self, context, instance_id):
    
  • nova/tests/test_compute.py+5 3 modified
    @@ -767,7 +767,8 @@ def fake(*args, **kwargs):
             console_valid = self.compute_api.validate_vnc_console(self.context,
                                                    instance['uuid'],
                                                    'myhost',
    -                                               '5900')
    +                                               '5900',
    +                                               'novnc')
             self.assertTrue(console_valid)
             self.compute.terminate_instance(self.context, instance['uuid'])
     
    @@ -783,7 +784,8 @@ def fake(*args, **kwargs):
             console_valid = self.compute_api.validate_vnc_console(self.context,
                                                    instance['uuid'],
                                                    'myhost',
    -                                               '5900')
    +                                               '5900',
    +                                               'novnc')
             self.assertFalse(console_valid)
             self.compute.terminate_instance(self.context, instance['uuid'])
     
    @@ -793,7 +795,7 @@ def test_validate_vnc_console_deleted_instance(self):
             self.compute.run_instance(self.context, instance['uuid'])
             self.assertRaises(exception.InstanceNotFound,
                                 self.compute_api.validate_vnc_console,
    -                            self.context, 5555, 'myhost', '5900')
    +                            self.context, 5555, 'myhost', '5900', 'novnc')
             self.compute.terminate_instance(self.context, instance['uuid'])
     
         def test_xvpvnc_vnc_console(self):
    
e98928cf7764

Flush tokens on instance delete

https://github.com/openstack/novaLoganathan ParthipanFeb 23, 2013via ghsa
5 files changed · +92 2
  • nova/compute/api.py+10 0 modified
    @@ -1554,11 +1554,21 @@ def get_vnc_console(self, context, instance, console_type):
                                'console_type': console_type,
                                'host': connect_info['host'],
                                'port': connect_info['port'],
    +                           'instance_id': instance['id'],
                                'internal_access_path':
                                        connect_info['internal_access_path']}})
     
             return {'url': connect_info['access_url']}
     
    +    @wrap_check_policy
    +    def validate_vnc_console(self, context, instance_id, host, port):
    +        """Validate VNC Console for an instance."""
    +        instance = self.get(context, instance_id)
    +        output = self._call_compute_message('get_vnc_console',
    +                                            context,
    +                                            instance)
    +        return (port == output['port'] and host == output['host'])
    +
         @wrap_check_policy
         def get_console_output(self, context, instance, tail_length=None):
             """Get console output for an an instance."""
    
  • nova/compute/manager.py+4 0 modified
    @@ -749,6 +749,10 @@ def _delete_instance(self, context, instance):
                                   terminated_at=utils.utcnow())
     
             self.db.instance_destroy(context, instance_id)
    +        if FLAGS.vnc_enabled:
    +            rpc.cast(context, '%s' % FLAGS.consoleauth_topic,
    +                 {'method': 'delete_tokens_for_instance',
    +                  'args': {'instance_id': instance['id']}})
             self._notify_about_instance_usage(instance, "delete.end")
     
         @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
    
  • nova/consoleauth/manager.py+30 2 modified
    @@ -27,6 +27,8 @@
     from nova import manager
     from nova.openstack.common import cfg
     from nova import utils
    +from nova import exception
    +from nova import compute
     
     
     LOG = logging.getLogger(__name__)
    @@ -49,6 +51,7 @@ class ConsoleAuthManager(manager.Manager):
     
         def __init__(self, scheduler_driver=None, *args, **kwargs):
             super(ConsoleAuthManager, self).__init__(*args, **kwargs)
    +        self.compute_api = compute.API()
             self.tokens = {}
             utils.LoopingCall(self._delete_expired_tokens).start(1)
     
    @@ -64,18 +67,43 @@ def _delete_expired_tokens(self):
                 del self.tokens[k]
     
         def authorize_console(self, context, token, console_type, host, port,
    -                          internal_access_path):
    +                          instance_id, internal_access_path):
             self.tokens[token] = {'token': token,
                                   'console_type': console_type,
                                   'host': host,
                                   'port': port,
    +                              'instance_id': instance_id,
                                   'internal_access_path': internal_access_path,
                                   'last_activity_at': time.time()}
             token_dict = self.tokens[token]
    +        if instance_id is not None:
    +            tokens = self.tokens[instance_id]
    +            tokens.append(token)
    +            self.tokens[instance_id] = tokens
    +
             LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals())
     
    +    def _validate_console(self, context, token):
    +        console_valid = False
    +        token_dict = self.tokens[token]
    +        try:
    +            console_valid = self.compute_api.validate_vnc_console(context,
    +                                                token_dict['instance_id'],
    +                                                token_dict['host'],
    +                                                token_dict['port'])
    +        except exception.InstanceNotFound:
    +            pass
    +        return console_valid
    +
         def check_token(self, context, token):
             token_valid = token in self.tokens
             LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals())
    -        if token_valid:
    +        if token_valid and _validate_console(token):
                 return self.tokens[token]
    +
    +    def delete_tokens_for_instance(self, context, instance_id):
    +        for token in self.tokens[instance_id]:
    +            token_dict = self.tokens[token]
    +            token_dict['last_activity_at'] = 0
    +            self.tokens[token] = token_dict
    +        del self.tokens[instance_id]
    
  • nova/tests/test_compute.py+41 0 modified
    @@ -754,6 +754,47 @@ def test_novnc_vnc_console(self):
             self.assert_(console)
             self.compute.terminate_instance(self.context, instance['uuid'])
     
    +    def test_validate_vnc_console(self):
    +        """Check if a vnc console is really for the instance"""
    +        def fake(*args, **kwargs):
    +            vnc = {'host': 'myhost', 'port': '5900'}
    +            return vnc
    +        self.stubs.Set(self.compute_api, '_call_compute_message', fake)
    +        instance = self._create_instance_full()
    +        self.compute.run_instance(self.context, instance['id'])
    +
    +        console_valid = self.compute_api.validate_vnc_console(self.context,
    +                                               instance['id'],
    +                                               'myhost',
    +                                               '5900')
    +        self.assertTrue(console_valid)
    +        self.compute.terminate_instance(self.context, instance['id'])
    +
    +    def test_validate_vnc_console_wrong_port(self):
    +        """Check if a vnc console is really for the instance"""
    +        def fake(*args, **kwargs):
    +            vnc = {'host': 'myhost', 'port': '5901'}
    +            return vnc
    +        self.stubs.Set(self.compute_api, '_call_compute_message', fake)
    +        instance = self._create_instance_full()
    +        self.compute.run_instance(self.context, instance['id'])
    +
    +        console_valid = self.compute_api.validate_vnc_console(self.context,
    +                                               instance['id'],
    +                                               'myhost',
    +                                               '5900')
    +        self.assertFalse(console_valid)
    +        self.compute.terminate_instance(self.context, instance['id'])
    +
    +    def test_validate_vnc_console_deleted_instance(self):
    +        """Check if a vnc console is really for the instance"""
    +        instance = self._create_instance_full()
    +        self.compute.run_instance(self.context, instance['id'])
    +        self.assertRaises(exception.InstanceNotFound,
    +                            self.compute_api.validate_vnc_console,
    +                            self.context, 5555, 'myhost', '5900')
    +        self.compute.terminate_instance(self.context, instance['id'])
    +
         def test_xvpvnc_vnc_console(self):
             """Make sure we can a vnc console for an instance."""
             instance = self._create_fake_instance()
    
  • nova/tests/test_consoleauth.py+7 0 modified
    @@ -47,6 +47,13 @@ def test_tokens_expire(self):
             """Test that tokens expire correctly."""
             token = 'mytok'
             self.flags(console_token_ttl=1)
    +
    +        def fake_validate_console(*args, **kwargs):
    +            return True
    +        self.stubs.Set(self.manager,
    +                       "_validate_console",
    +                       fake_validate_console)
    +
             self.manager.authorize_console(self.context, token, 'novnc',
                                            '127.0.0.1', 'host', '')
             self.assertTrue(self.manager.check_token(self.context, token))
    
05a3374992bc

VNC Token Validation

https://github.com/openstack/novaJohn HerndonFeb 22, 2013via ghsa
10 files changed · +200 8
  • nova/common/memorycache.py+5 0 modified
    @@ -62,3 +62,8 @@ def incr(self, key, delta=1):
             new_value = int(value) + delta
             self.cache[key] = (self.cache[key][0], str(new_value))
             return new_value
    +
    +    def delete(self, key, time=0):
    +        """Deletes the value associated with a key."""
    +        if key in self.cache:
    +            del self.cache[key]
    
  • nova/compute/api.py+2 1 modified
    @@ -1853,7 +1853,8 @@ def get_vnc_console(self, context, instance, console_type):
     
             self.consoleauth_rpcapi.authorize_console(context,
                     connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['port'], connect_info['internal_access_path'],
    +                instance["uuid"])
     
             return {'url': connect_info['access_url']}
     
    
  • nova/compute/manager.py+12 0 modified
    @@ -52,6 +52,7 @@
     from nova.compute import task_states
     from nova.compute import utils as compute_utils
     from nova.compute import vm_states
    +from nova import consoleauth
     import nova.context
     from nova import exception
     from nova import flags
    @@ -235,6 +236,7 @@ def __init__(self, compute_driver=None, *args, **kwargs):
             self.compute_api = compute.API()
             self.compute_rpcapi = compute_rpcapi.ComputeAPI()
             self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI()
    +        self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI()
     
             super(ComputeManager, self).__init__(service_name="compute",
                                                  *args, **kwargs)
    @@ -926,6 +928,10 @@ def _delete_instance(self, context, instance):
             self._notify_about_instance_usage(context, instance, "delete.end",
                     system_metadata=system_meta)
     
    +        if FLAGS.vnc_enabled:
    +            self.consoleauth_rpcapi.delete_tokens_for_instance(context,
    +                                                        instance["uuid"])
    +
         @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
         @wrap_instance_fault
         def terminate_instance(self, context, instance):
    @@ -1988,6 +1994,12 @@ def _attach_volume_boot(self, context, instance, volume, mountpoint):
             self.volume_api.attach(context, volume, instance_uuid, mountpoint)
             return connection_info
     
    +    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
    +    @wrap_instance_fault
    +    def validate_console_port(self, ctxt, instance, port, console_type):
    +        console_info = self.driver.get_vnc_console(instance)
    +        return console_info['port'] == port
    +
         @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
         @reverts_task_state
         @wrap_instance_fault
    
  • nova/compute/rpcapi.py+7 0 modified
    @@ -259,6 +259,13 @@ def get_vnc_console(self, ctxt, instance, console_type):
                     instance=instance_p, console_type=console_type),
                     topic=_compute_topic(self.topic, ctxt, None, instance))
     
    +    def validate_console_port(self, ctxt, instance, port, console_type):
    +        instance_p = jsonutils.to_primitive(instance)
    +        return self.call(ctxt, self.make_msg('validate_console_port',
    +                instance=instance_p, port=port, console_type=console_type),
    +                topic=_compute_topic(self.topic, ctxt,
    +                None, instance))
    +
         def host_maintenance_mode(self, ctxt, host_param, mode, host):
             '''Set host maintenance mode
     
    
  • nova/consoleauth/manager.py+39 2 modified
    @@ -20,6 +20,8 @@
     
     import time
     
    +from nova.compute import rpcapi as compute_rpcapi
    +from nova.db import api as db
     from nova import flags
     from nova import manager
     from nova.openstack.common import cfg
    @@ -56,22 +58,57 @@ def __init__(self, scheduler_driver=None, *args, **kwargs):
                 from nova.common import memorycache as memcache
             self.mc = memcache.Client(FLAGS.memcached_servers,
                                       debug=0)
    +        self.compute_rpcapi = compute_rpcapi.ComputeAPI()
    +
    +    def _get_tokens_for_instance(self, instance_uuid):
    +        tokens_str = self.mc.get(instance_uuid.encode('UTF-8'))
    +        if not tokens_str:
    +            tokens = []
    +        else:
    +            tokens = jsonutils.loads(tokens_str)
    +        return tokens
     
         def authorize_console(self, context, token, console_type, host, port,
    -                          internal_access_path):
    +                          internal_access_path, instance_uuid=None):
    +
             token_dict = {'token': token,
    +                      'instance_uuid': instance_uuid,
                           'console_type': console_type,
                           'host': host,
                           'port': port,
                           'internal_access_path': internal_access_path,
                           'last_activity_at': time.time()}
             data = jsonutils.dumps(token_dict)
             self.mc.set(token.encode('UTF-8'), data, FLAGS.console_token_ttl)
    +        if instance_uuid is not None:
    +            tokens = self._get_tokens_for_instance(instance_uuid)
    +            tokens.append(token)
    +            self.mc.set(instance_uuid.encode('UTF-8'),
    +                        jsonutils.dumps(tokens))
    +
             LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals())
     
    +    def _validate_token(self, context, token):
    +        instance_uuid = token['instance_uuid']
    +        if instance_uuid is None:
    +            return False
    +        instance = db.instance_get_by_uuid(context, instance_uuid)
    +        return self.compute_rpcapi.validate_console_port(context,
    +                                            instance,
    +                                            token['port'],
    +                                            token['console_type'])
    +
         def check_token(self, context, token):
             token_str = self.mc.get(token.encode('UTF-8'))
             token_valid = (token_str is not None)
             LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals())
             if token_valid:
    -            return jsonutils.loads(token_str)
    +            token = jsonutils.loads(token_str)
    +            if self._validate_token(context, token):
    +                return token
    +
    +    def delete_tokens_for_instance(self, context, instance_uuid):
    +        tokens = self._get_tokens_for_instance(instance_uuid)
    +        for token in tokens:
    +            self.mc.delete(token)
    +        self.mc.delete(instance_uuid.encode('UTF-8'))
    
  • nova/consoleauth/rpcapi.py+8 2 modified
    @@ -49,14 +49,20 @@ def __init__(self):
                     default_version=self.BASE_RPC_API_VERSION)
     
         def authorize_console(self, ctxt, token, console_type, host, port,
    -                          internal_access_path):
    +                          internal_access_path, instance_uuid=None):
             # The remote side doesn't return anything, but we want to block
             # until it completes.
             return self.call(ctxt,
                     self.make_msg('authorize_console',
                                   token=token, console_type=console_type,
                                   host=host, port=port,
    -                              internal_access_path=internal_access_path))
    +                              internal_access_path=internal_access_path,
    +                              instance_uuid=instance_uuid))
     
         def check_token(self, ctxt, token):
             return self.call(ctxt, self.make_msg('check_token', token=token))
    +
    +    def delete_tokens_for_instance(self, ctxt, instance_uuid):
    +        return self.call(ctxt,
    +                self.make_msg('delete_tokens_for_instance',
    +                              instance_uuid=instance_uuid))
    
  • nova/tests/compute/test_compute.py+51 1 modified
    @@ -1372,6 +1372,24 @@ def fake_cleanup_volumes(context, instance):
             self.compute._delete_instance(self.context,
                     instance=jsonutils.to_primitive(instance))
     
    +    def test_delete_instance_deletes_console_auth_tokens(self):
    +        instance = self._create_fake_instance()
    +        self.flags(vnc_enabled=True)
    +
    +        self.tokens_deleted = False
    +
    +        def fake_delete_tokens(*args, **kwargs):
    +            self.tokens_deleted = True
    +
    +        cauth_rpcapi = self.compute.consoleauth_rpcapi
    +        self.stubs.Set(cauth_rpcapi, 'delete_tokens_for_instance',
    +                       fake_delete_tokens)
    +
    +        self.compute._delete_instance(self.context,
    +                instance=jsonutils.to_primitive(instance))
    +
    +        self.assertTrue(self.tokens_deleted)
    +
         def test_instance_termination_exception_sets_error(self):
             """Test that we handle InstanceTerminationFailure
             which is propagated up from the underlying driver.
    @@ -4505,7 +4523,9 @@ def test_vnc_console(self):
                                  'console_type': fake_console_type,
                                  'host': 'fake_console_host',
                                  'port': 'fake_console_port',
    -                             'internal_access_path': 'fake_access_path'}
    +                             'internal_access_path': 'fake_access_path',
    +                             'instance_uuid': fake_instance["uuid"]}
    +
             fake_connect_info2 = copy.deepcopy(fake_connect_info)
             fake_connect_info2['access_url'] = 'fake_console_url'
     
    @@ -4539,6 +4559,36 @@ def test_get_vnc_console_no_host(self):
     
             db.instance_destroy(self.context, instance['uuid'])
     
    +    def test_validate_console_port(self):
    +        self.flags(vnc_enabled=True)
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_vnc_console",
    +                       fake_driver_get_console)
    +
    +        self.assertTrue(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "5900",
    +                                                            "novnc"))
    +
    +    def test_validate_console_port_wrong_port(self):
    +        self.flags(vnc_enabled=True)
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_vnc_console",
    +                       fake_driver_get_console)
    +
    +        self.assertFalse(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "wrongport",
    +                                                            "novnc"))
    +
         def test_console_output(self):
             fake_instance = {'uuid': 'fake_uuid',
                              'host': 'fake_compute_host'}
    
  • nova/tests/compute/test_rpcapi.py+5 0 modified
    @@ -168,6 +168,11 @@ def test_get_vnc_console(self):
             self._test_compute_api('get_vnc_console', 'call',
                     instance=self.fake_instance, console_type='type')
     
    +    def test_validate_console_port(self):
    +        self._test_compute_api('validate_console_port', 'call',
    +                instance=self.fake_instance, port="5900",
    +                console_type="novnc")
    +
         def test_host_maintenance_mode(self):
             self._test_compute_api('host_maintenance_mode', 'call',
                     host_param='param', mode='mode', host='host')
    
  • nova/tests/consoleauth/test_consoleauth.py+66 1 modified
    @@ -45,8 +45,73 @@ def test_tokens_expire(self):
             """Test that tokens expire correctly."""
             token = 'mytok'
             self.flags(console_token_ttl=1)
    +
    +        def fake_validate_token(*args, **kwargs):
    +            return True
    +        self.stubs.Set(self.manager,
    +                       "_validate_token",
    +                       fake_validate_token)
    +
             self.manager.authorize_console(self.context, token, 'novnc',
    -                                       '127.0.0.1', 'host', '')
    +                                       '127.0.0.1', '8080', 'host', "1234")
             self.assertTrue(self.manager.check_token(self.context, token))
             time.sleep(1.1)
             self.assertFalse(self.manager.check_token(self.context, token))
    +
    +    def test_multiple_tokens_for_instance(self):
    +        tokens = ["token" + str(i) for i in xrange(10)]
    +        instance = "12345"
    +
    +        def fake_validate_token(*args, **kwargs):
    +            return True
    +
    +        self.stubs.Set(self.manager, "_validate_token",
    +                       fake_validate_token)
    +        for token in tokens:
    +            self.manager.authorize_console(self.context, token, 'novnc',
    +                                          '127.0.0.1', '8080', 'host',
    +                                          instance)
    +
    +        for token in tokens:
    +            self.assertTrue(self.manager.check_token(self.context, token))
    +
    +    def test_delete_tokens_for_instance(self):
    +        instance = "12345"
    +        tokens = ["token" + str(i) for i in xrange(10)]
    +
    +        def fake_validate_token(*args, **kwargs):
    +            return True
    +        self.stubs.Set(self.manager, "_validate_token",
    +                       fake_validate_token)
    +
    +        for token in tokens:
    +            self.manager.authorize_console(self.context, token, 'novnc',
    +                                          '127.0.0.1', '8080', 'host',
    +                                          instance)
    +        self.manager.delete_tokens_for_instance(self.context, instance)
    +        stored_tokens = self.manager._get_tokens_for_instance(instance)
    +
    +        self.assertEqual(len(stored_tokens), 0)
    +
    +        for token in tokens:
    +            self.assertFalse(self.manager.check_token(self.context, token))
    +
    +    def test_wrong_token_has_port(self):
    +        token = 'mytok'
    +
    +        def fake_validate_token(*args, **kwargs):
    +            return False
    +
    +        self.stubs.Set(self.manager, "_validate_token",
    +                       fake_validate_token)
    +
    +        self.manager.authorize_console(self.context, token, 'novnc',
    +                                        '127.0.0.1', '8080', 'host',
    +                                        instance_uuid='instance')
    +        self.assertFalse(self.manager.check_token(self.context, token))
    +
    +    def test_console_no_instance_uuid(self):
    +        self.manager.authorize_console(self.context, "token", 'novnc',
    +                                        '127.0.0.1', '8080', 'host',
    +                                        instance_uuid=None)
    +        self.assertFalse(self.manager.check_token(self.context, "token"))
    
  • nova/tests/consoleauth/test_rpcapi.py+5 1 modified
    @@ -68,7 +68,11 @@ def _fake_call(_ctxt, _topic, _msg, _timeout):
         def test_authorize_console(self):
             self._test_consoleauth_api('authorize_console', token='token',
                     console_type='ctype', host='h', port='p',
    -                internal_access_path='iap')
    +                internal_access_path='iap', instance_uuid="1234")
     
         def test_check_token(self):
             self._test_consoleauth_api('check_token', token='t')
    +
    +    def test_delete_tokens_for_instnace(self):
    +        self._test_consoleauth_api('delete_tokens_for_instance',
    +                                   instance_uuid="instance")
    
ad94a9020219

Flush tokens on instance delete

https://github.com/openstack/novaJohn HerndonFeb 19, 2013via ghsa
11 files changed · +247 19
  • nova/common/memorycache.py+5 0 modified
    @@ -83,3 +83,8 @@ def incr(self, key, delta=1):
             new_value = int(value) + delta
             self.cache[key] = (self.cache[key][0], str(new_value))
             return new_value
    +
    +    def delete(self, key, time=0):
    +        """Deletes the value associated with a key."""
    +        if key in self.cache:
    +            del self.cache[key]
    
  • nova/compute/api.py+7 5 modified
    @@ -2189,8 +2189,9 @@ def get_vnc_console(self, context, instance, console_type):
                     instance=instance, console_type=console_type)
     
             self.consoleauth_rpcapi.authorize_console(context,
    -                connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['token'], console_type,
    +                connect_info['host'], connect_info['port'],
    +                connect_info['internal_access_path'], instance['uuid'])
     
             return {'url': connect_info['access_url']}
     
    @@ -2207,10 +2208,11 @@ def get_spice_console(self, context, instance, console_type):
             """Get a url to an instance Console."""
             connect_info = self.compute_rpcapi.get_spice_console(context,
                     instance=instance, console_type=console_type)
    -
    +        print connect_info
             self.consoleauth_rpcapi.authorize_console(context,
    -                connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['token'], console_type,
    +                connect_info['host'], connect_info['port'],
    +                connect_info['internal_access_path'], instance['uuid'])
     
             return {'url': connect_info['access_url']}
     
    
  • nova/compute/cells_api.py+4 2 modified
    @@ -465,7 +465,8 @@ def get_vnc_console(self, context, instance, console_type):
     
             self.consoleauth_rpcapi.authorize_console(context,
                     connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['port'], connect_info['internal_access_path'],
    +                instance_uuid=instance['uuid'])
             return {'url': connect_info['access_url']}
     
         @wrap_check_policy
    @@ -480,7 +481,8 @@ def get_spice_console(self, context, instance, console_type):
     
             self.consoleauth_rpcapi.authorize_console(context,
                     connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['port'], connect_info['internal_access_path'],
    +                instance_uuid=instance['uuid'])
             return {'url': connect_info['access_url']}
     
         @validate_cell
    
  • nova/compute/manager.py+18 1 modified
    @@ -50,6 +50,7 @@
     from nova.compute import utils as compute_utils
     from nova.compute import vm_states
     from nova import conductor
    +from nova import consoleauth
     import nova.context
     from nova import exception
     from nova import hooks
    @@ -317,7 +318,7 @@ def agent_build_get_by_triple(self, context, hypervisor, os, architecture):
     class ComputeManager(manager.SchedulerDependentManager):
         """Manages the running instances from creation to destruction."""
     
    -    RPC_API_VERSION = '2.25'
    +    RPC_API_VERSION = '2.26'
     
         def __init__(self, compute_driver=None, *args, **kwargs):
             """Load configuration options and connect to the hypervisor."""
    @@ -335,6 +336,8 @@ def __init__(self, compute_driver=None, *args, **kwargs):
             self.conductor_api = conductor.API()
             self.is_quantum_security_groups = (
                 openstack_driver.is_quantum_security_groups())
    +        self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI()
    +
             super(ComputeManager, self).__init__(service_name="compute",
                                                  *args, **kwargs)
     
    @@ -1223,6 +1226,10 @@ def _delete_instance(self, context, instance, bdms):
             self._notify_about_instance_usage(context, instance, "delete.end",
                     system_metadata=system_meta)
     
    +        if CONF.vnc_enabled or CONF.spice.enabled:
    +            self.consoleauth_rpcapi.delete_tokens_for_instance(context,
    +                                                       instance['uuid'])
    +
         @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
         @wrap_instance_event
         @wrap_instance_fault
    @@ -2555,6 +2562,16 @@ def get_spice_console(self, context, console_type, instance):
     
             return connect_info
     
    +    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
    +    @wrap_instance_fault
    +    def validate_console_port(self, ctxt, instance, port, console_type):
    +        if console_type == "spice-html5":
    +            console_info = self.driver.get_spice_console(instance)
    +        else:
    +            console_info = self.driver.get_vnc_console(instance)
    +
    +        return console_info['port'] == port
    +
         def _attach_volume_boot(self, context, instance, volume, mountpoint):
             """Attach a volume to an instance at boot time. So actual attach
             is done by instance creation"""
    
  • nova/compute/rpcapi.py+10 0 modified
    @@ -161,6 +161,8 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
             2.23 - Remove network_info from reboot_instance
             2.24 - Added get_spice_console method
             2.25 - Add attach_interface() and detach_interface()
    +        2.26 - Add validate_console_token to ensure the service connects to
    +               vnc on the correct port
         '''
     
         #
    @@ -321,6 +323,14 @@ def get_spice_console(self, ctxt, instance, console_type):
                     topic=_compute_topic(self.topic, ctxt, None, instance),
                              version='2.24')
     
    +    def validate_console_port(self, ctxt, instance, port, console_type):
    +        instance_p = jsonutils.to_primitive(instance)
    +        return self.call(ctxt, self.make_msg('validate_console_port',
    +                instance=instance_p, port=port, console_type=console_type),
    +                topic=_compute_topic(self.topic, ctxt,
    +                None, instance),
    +                version='2.26')
    +
         def host_maintenance_mode(self, ctxt, host_param, mode, host):
             '''Set host maintenance mode
     
    
  • nova/consoleauth/manager.py+42 3 modified
    @@ -23,6 +23,8 @@
     from oslo.config import cfg
     
     from nova.common import memorycache
    +from nova.compute import rpcapi as compute_rpcapi
    +from nova.conductor import api as conductor_api
     from nova import manager
     from nova.openstack.common import jsonutils
     from nova.openstack.common import log as logging
    @@ -46,30 +48,67 @@
     class ConsoleAuthManager(manager.Manager):
         """Manages token based authentication."""
     
    -    RPC_API_VERSION = '1.1'
    +    RPC_API_VERSION = '1.2'
     
         def __init__(self, scheduler_driver=None, *args, **kwargs):
             super(ConsoleAuthManager, self).__init__(*args, **kwargs)
             self.mc = memorycache.get_client()
    +        self.compute_rpcapi = compute_rpcapi.ComputeAPI()
    +        self.conductor_api = conductor_api.API()
    +
    +    def _get_tokens_for_instance(self, instance_uuid):
    +        tokens_str = self.mc.get(instance_uuid.encode('UTF-8'))
    +        if not tokens_str:
    +            tokens = []
    +        else:
    +            tokens = jsonutils.loads(tokens_str)
    +        return tokens
     
         def authorize_console(self, context, token, console_type, host, port,
    -                          internal_access_path):
    +                          internal_access_path, instance_uuid=None):
    +
             token_dict = {'token': token,
    +                      'instance_uuid': instance_uuid,
                           'console_type': console_type,
                           'host': host,
                           'port': port,
                           'internal_access_path': internal_access_path,
                           'last_activity_at': time.time()}
             data = jsonutils.dumps(token_dict)
             self.mc.set(token.encode('UTF-8'), data, CONF.console_token_ttl)
    +        if instance_uuid is not None:
    +            tokens = self._get_tokens_for_instance(instance_uuid)
    +            tokens.append(token)
    +            self.mc.set(instance_uuid.encode('UTF-8'),
    +                        jsonutils.dumps(tokens))
    +
             LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals())
     
    +    def _validate_token(self, context, token):
    +        instance_uuid = token['instance_uuid']
    +        if instance_uuid is None:
    +            return False
    +        instance = self.conductor_api.instance_get_by_uuid(context,
    +                                                             instance_uuid)
    +        return self.compute_rpcapi.validate_console_port(context,
    +                                            instance,
    +                                            token['port'],
    +                                            token['console_type'])
    +
         def check_token(self, context, token):
             token_str = self.mc.get(token.encode('UTF-8'))
             token_valid = (token_str is not None)
             LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals())
             if token_valid:
    -            return jsonutils.loads(token_str)
    +            token = jsonutils.loads(token_str)
    +            if self._validate_token(context, token):
    +                return token
    +
    +    def delete_tokens_for_instance(self, context, instance_uuid):
    +        tokens = self._get_tokens_for_instance(instance_uuid)
    +        for token in tokens:
    +            self.mc.delete(token)
    +        self.mc.delete(instance_uuid.encode('UTF-8'))
     
         def get_backdoor_port(self, context):
             return self.backdoor_port
    
  • nova/consoleauth/rpcapi.py+12 2 modified
    @@ -32,6 +32,8 @@ class ConsoleAuthAPI(nova.openstack.common.rpc.proxy.RpcProxy):
     
             1.0 - Initial version.
             1.1 - Added get_backdoor_port()
    +        1.2 - Added instance_uuid to authorize_console, and
    +              delete_tokens_for_instance
         '''
     
         #
    @@ -50,18 +52,26 @@ def __init__(self):
                     default_version=self.BASE_RPC_API_VERSION)
     
         def authorize_console(self, ctxt, token, console_type, host, port,
    -                          internal_access_path):
    +                          internal_access_path, instance_uuid=None):
             # The remote side doesn't return anything, but we want to block
             # until it completes.
             return self.call(ctxt,
                     self.make_msg('authorize_console',
                                   token=token, console_type=console_type,
                                   host=host, port=port,
    -                              internal_access_path=internal_access_path))
    +                              internal_access_path=internal_access_path,
    +                              instance_uuid=instance_uuid),
    +                version="1.2")
     
         def check_token(self, ctxt, token):
             return self.call(ctxt, self.make_msg('check_token', token=token))
     
    +    def delete_tokens_for_instance(self, ctxt, instance_uuid):
    +        return self.call(ctxt,
    +                self.make_msg('delete_tokens_for_instance',
    +                              instance_uuid=instance_uuid),
    +                version="1.2")
    +
         def get_backdoor_port(self, ctxt, host):
             return self.call(ctxt, self.make_msg('get_backdoor_port'),
                              version='1.1')
    
  • nova/tests/compute/test_compute.py+73 4 modified
    @@ -1420,6 +1420,54 @@ def test_novnc_vnc_console(self):
     
             self.compute.terminate_instance(self.context, instance=instance)
     
    +    def test_validate_console_port_vnc(self):
    +        self.flags(vnc_enabled=True)
    +        self.flags(enabled=True, group='spice')
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_vnc_console",
    +                       fake_driver_get_console)
    +
    +        self.assertTrue(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "5900",
    +                                                            "novnc"))
    +
    +    def test_validate_console_port_spice(self):
    +        self.flags(vnc_enabled=True)
    +        self.flags(enabled=True, group='spice')
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_spice_console",
    +                       fake_driver_get_console)
    +
    +        self.assertTrue(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "5900",
    +                                                            "spice-html5"))
    +
    +    def test_validate_console_port_wrong_port(self):
    +        self.flags(vnc_enabled=True)
    +        self.flags(enabled=True, group='spice')
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_vnc_console",
    +                       fake_driver_get_console)
    +
    +        self.assertFalse(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "wrongport",
    +                                                            "spice-html5"))
    +
         def test_xvpvnc_vnc_console(self):
             # Make sure we can a vnc console for an instance.
             self.flags(vnc_enabled=True)
    @@ -1715,6 +1763,25 @@ def fake_cleanup_volumes(context, instance):
                     instance=jsonutils.to_primitive(instance),
                     bdms={})
     
    +    def test_delete_instance_deletes_console_auth_tokens(self):
    +        instance = self._create_fake_instance()
    +        self.flags(vnc_enabled=True)
    +
    +        self.tokens_deleted = False
    +
    +        def fake_delete_tokens(*args, **kwargs):
    +            self.tokens_deleted = True
    +
    +        cauth_rpcapi = self.compute.consoleauth_rpcapi
    +        self.stubs.Set(cauth_rpcapi, 'delete_tokens_for_instance',
    +                       fake_delete_tokens)
    +
    +        self.compute._delete_instance(self.context,
    +                instance=jsonutils.to_primitive(instance),
    +                bdms={})
    +
    +        self.assertTrue(self.tokens_deleted)
    +
         def test_instance_termination_exception_sets_error(self):
             """Test that we handle InstanceTerminationFailure
             which is propagated up from the underlying driver.
    @@ -5735,7 +5802,8 @@ def test_vnc_console(self):
                                  'console_type': fake_console_type,
                                  'host': 'fake_console_host',
                                  'port': 'fake_console_port',
    -                             'internal_access_path': 'fake_access_path'}
    +                             'internal_access_path': 'fake_access_path',
    +                             'instance_uuid': fake_instance['uuid']}
             fake_connect_info2 = copy.deepcopy(fake_connect_info)
             fake_connect_info2['access_url'] = 'fake_console_url'
     
    @@ -5747,7 +5815,7 @@ def test_vnc_console(self):
                        'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION}
             rpc_msg2 = {'method': 'authorize_console',
                         'args': fake_connect_info,
    -                    'version': '1.0'}
    +                    'version': '1.2'}
     
             rpc.call(self.context, 'compute.%s' % fake_instance['host'],
                     rpc_msg1, None).AndReturn(fake_connect_info2)
    @@ -5779,7 +5847,8 @@ def test_spice_console(self):
                                  'console_type': fake_console_type,
                                  'host': 'fake_console_host',
                                  'port': 'fake_console_port',
    -                             'internal_access_path': 'fake_access_path'}
    +                             'internal_access_path': 'fake_access_path',
    +                             'instance_uuid': fake_instance['uuid']}
             fake_connect_info2 = copy.deepcopy(fake_connect_info)
             fake_connect_info2['access_url'] = 'fake_console_url'
     
    @@ -5791,7 +5860,7 @@ def test_spice_console(self):
                        'version': '2.24'}
             rpc_msg2 = {'method': 'authorize_console',
                         'args': fake_connect_info,
    -                    'version': '1.0'}
    +                    'version': '1.2'}
     
             rpc.call(self.context, 'compute.%s' % fake_instance['host'],
                     rpc_msg1, None).AndReturn(fake_connect_info2)
    
  • nova/tests/compute/test_rpcapi.py+6 0 modified
    @@ -171,6 +171,12 @@ def test_get_spice_console(self):
                     instance=self.fake_instance, console_type='type',
                     version='2.24')
     
    +    def test_validate_console_port(self):
    +        self._test_compute_api('validate_console_port', 'call',
    +                instance=self.fake_instance, port="5900",
    +                console_type="novnc",
    +                version="2.26")
    +
         def test_host_maintenance_mode(self):
             self._test_compute_api('host_maintenance_mode', 'call',
                     host_param='param', mode='mode', host='host')
    
  • nova/tests/consoleauth/test_consoleauth.py+63 1 modified
    @@ -42,12 +42,74 @@ def test_tokens_expire(self):
             self.useFixture(test.TimeOverride())
             token = 'mytok'
             self.flags(console_token_ttl=1)
    +
    +        def fake_validate_console_port(*args, **kwargs):
    +            return True
    +        self.stubs.Set(self.manager.compute_rpcapi,
    +                       "validate_console_port",
    +                       fake_validate_console_port)
    +
             self.manager.authorize_console(self.context, token, 'novnc',
    -                                       '127.0.0.1', 'host', '')
    +                                         '127.0.0.1', '8080', 'host',
    +                                         'instance')
             self.assertTrue(self.manager.check_token(self.context, token))
             timeutils.advance_time_seconds(1)
             self.assertFalse(self.manager.check_token(self.context, token))
     
    +    def test_multiple_tokens_for_instance(self):
    +        tokens = ["token" + str(i) for i in xrange(10)]
    +        instance = "12345"
    +
    +        def fake_validate_console_port(*args, **kwargs):
    +            return True
    +
    +        self.stubs.Set(self.manager.compute_rpcapi,
    +                       "validate_console_port",
    +                       fake_validate_console_port)
    +        for token in tokens:
    +            self.manager.authorize_console(self.context, token, 'novnc',
    +                                          '127.0.0.1', '8080', 'host',
    +                                          instance)
    +
    +        for token in tokens:
    +            self.assertTrue(self.manager.check_token(self.context, token))
    +
    +    def test_delete_tokens_for_instance(self):
    +        instance = "12345"
    +        tokens = ["token" + str(i) for i in xrange(10)]
    +        for token in tokens:
    +            self.manager.authorize_console(self.context, token, 'novnc',
    +                                          '127.0.0.1', '8080', 'host',
    +                                          instance)
    +        self.manager.delete_tokens_for_instance(self.context, instance)
    +        stored_tokens = self.manager._get_tokens_for_instance(instance)
    +
    +        self.assertEqual(len(stored_tokens), 0)
    +
    +        for token in tokens:
    +            self.assertFalse(self.manager.check_token(self.context, token))
    +
    +    def test_wrong_token_has_port(self):
    +        token = 'mytok'
    +
    +        def fake_validate_console_port(*args, **kwargs):
    +            return False
    +
    +        self.stubs.Set(self.manager.compute_rpcapi,
    +                       "validate_console_port",
    +                       fake_validate_console_port)
    +
    +        self.manager.authorize_console(self.context, token, 'novnc',
    +                                        '127.0.0.1', '8080', 'host',
    +                                        instance_uuid='instance')
    +        self.assertFalse(self.manager.check_token(self.context, token))
    +
    +    def test_console_no_instance_uuid(self):
    +        self.manager.authorize_console(self.context, "token", 'novnc',
    +                                        '127.0.0.1', '8080', 'host',
    +                                        instance_uuid=None)
    +        self.assertFalse(self.manager.check_token(self.context, "token"))
    +
         def test_get_backdoor_port(self):
             self.manager.backdoor_port = 59697
             port = self.manager.get_backdoor_port(self.context)
    
  • nova/tests/consoleauth/test_rpcapi.py+7 1 modified
    @@ -65,11 +65,17 @@ def _fake_call(_ctxt, _topic, _msg, _timeout):
         def test_authorize_console(self):
             self._test_consoleauth_api('authorize_console', token='token',
                     console_type='ctype', host='h', port='p',
    -                internal_access_path='iap')
    +                internal_access_path='iap', instance_uuid="instance",
    +                version="1.2")
     
         def test_check_token(self):
             self._test_consoleauth_api('check_token', token='t')
     
    +    def test_delete_tokens_for_instnace(self):
    +        self._test_consoleauth_api('delete_tokens_for_instance',
    +                                   instance_uuid="instance",
    +                                   version='1.2')
    +
         def test_get_backdoor_port(self):
             self._test_consoleauth_api('get_backdoor_port', host='fake_host',
                                        version='1.1')
    
3b0f4cf6bea3

Flush tokens on instance delete

https://github.com/openstack/novaJohn HerndonFeb 19, 2013via ghsa
11 files changed · +247 19
  • nova/common/memorycache.py+5 0 modified
    @@ -83,3 +83,8 @@ def incr(self, key, delta=1):
             new_value = int(value) + delta
             self.cache[key] = (self.cache[key][0], str(new_value))
             return new_value
    +
    +    def delete(self, key, time=0):
    +        """Deletes the value associated with a key."""
    +        if key in self.cache:
    +            del self.cache[key]
    
  • nova/compute/api.py+7 5 modified
    @@ -2189,8 +2189,9 @@ def get_vnc_console(self, context, instance, console_type):
                     instance=instance, console_type=console_type)
     
             self.consoleauth_rpcapi.authorize_console(context,
    -                connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['token'], console_type,
    +                connect_info['host'], connect_info['port'],
    +                connect_info['internal_access_path'], instance['uuid'])
     
             return {'url': connect_info['access_url']}
     
    @@ -2207,10 +2208,11 @@ def get_spice_console(self, context, instance, console_type):
             """Get a url to an instance Console."""
             connect_info = self.compute_rpcapi.get_spice_console(context,
                     instance=instance, console_type=console_type)
    -
    +        print connect_info
             self.consoleauth_rpcapi.authorize_console(context,
    -                connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['token'], console_type,
    +                connect_info['host'], connect_info['port'],
    +                connect_info['internal_access_path'], instance['uuid'])
     
             return {'url': connect_info['access_url']}
     
    
  • nova/compute/cells_api.py+4 2 modified
    @@ -465,7 +465,8 @@ def get_vnc_console(self, context, instance, console_type):
     
             self.consoleauth_rpcapi.authorize_console(context,
                     connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['port'], connect_info['internal_access_path'],
    +                instance_uuid=instance['uuid'])
             return {'url': connect_info['access_url']}
     
         @wrap_check_policy
    @@ -480,7 +481,8 @@ def get_spice_console(self, context, instance, console_type):
     
             self.consoleauth_rpcapi.authorize_console(context,
                     connect_info['token'], console_type, connect_info['host'],
    -                connect_info['port'], connect_info['internal_access_path'])
    +                connect_info['port'], connect_info['internal_access_path'],
    +                instance_uuid=instance['uuid'])
             return {'url': connect_info['access_url']}
     
         @validate_cell
    
  • nova/compute/manager.py+18 1 modified
    @@ -50,6 +50,7 @@
     from nova.compute import utils as compute_utils
     from nova.compute import vm_states
     from nova import conductor
    +from nova import consoleauth
     import nova.context
     from nova import exception
     from nova import hooks
    @@ -317,7 +318,7 @@ def agent_build_get_by_triple(self, context, hypervisor, os, architecture):
     class ComputeManager(manager.SchedulerDependentManager):
         """Manages the running instances from creation to destruction."""
     
    -    RPC_API_VERSION = '2.25'
    +    RPC_API_VERSION = '2.26'
     
         def __init__(self, compute_driver=None, *args, **kwargs):
             """Load configuration options and connect to the hypervisor."""
    @@ -335,6 +336,8 @@ def __init__(self, compute_driver=None, *args, **kwargs):
             self.conductor_api = conductor.API()
             self.is_quantum_security_groups = (
                 openstack_driver.is_quantum_security_groups())
    +        self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI()
    +
             super(ComputeManager, self).__init__(service_name="compute",
                                                  *args, **kwargs)
     
    @@ -1223,6 +1226,10 @@ def _delete_instance(self, context, instance, bdms):
             self._notify_about_instance_usage(context, instance, "delete.end",
                     system_metadata=system_meta)
     
    +        if CONF.vnc_enabled or CONF.spice.enabled:
    +            self.consoleauth_rpcapi.delete_tokens_for_instance(context,
    +                                                       instance['uuid'])
    +
         @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
         @wrap_instance_event
         @wrap_instance_fault
    @@ -2555,6 +2562,16 @@ def get_spice_console(self, context, console_type, instance):
     
             return connect_info
     
    +    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
    +    @wrap_instance_fault
    +    def validate_console_port(self, ctxt, instance, port, console_type):
    +        if console_type == "spice-html5":
    +            console_info = self.driver.get_spice_console(instance)
    +        else:
    +            console_info = self.driver.get_vnc_console(instance)
    +
    +        return console_info['port'] == port
    +
         def _attach_volume_boot(self, context, instance, volume, mountpoint):
             """Attach a volume to an instance at boot time. So actual attach
             is done by instance creation"""
    
  • nova/compute/rpcapi.py+10 0 modified
    @@ -161,6 +161,8 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
             2.23 - Remove network_info from reboot_instance
             2.24 - Added get_spice_console method
             2.25 - Add attach_interface() and detach_interface()
    +        2.26 - Add validate_console_token to ensure the service connects to
    +               vnc on the correct port
         '''
     
         #
    @@ -321,6 +323,14 @@ def get_spice_console(self, ctxt, instance, console_type):
                     topic=_compute_topic(self.topic, ctxt, None, instance),
                              version='2.24')
     
    +    def validate_console_port(self, ctxt, instance, port, console_type):
    +        instance_p = jsonutils.to_primitive(instance)
    +        return self.call(ctxt, self.make_msg('validate_console_port',
    +                instance=instance_p, port=port, console_type=console_type),
    +                topic=_compute_topic(self.topic, ctxt,
    +                None, instance),
    +                version='2.26')
    +
         def host_maintenance_mode(self, ctxt, host_param, mode, host):
             '''Set host maintenance mode
     
    
  • nova/consoleauth/manager.py+42 3 modified
    @@ -23,6 +23,8 @@
     from oslo.config import cfg
     
     from nova.common import memorycache
    +from nova.compute import rpcapi as compute_rpcapi
    +from nova.conductor import api as conductor_api
     from nova import manager
     from nova.openstack.common import jsonutils
     from nova.openstack.common import log as logging
    @@ -46,30 +48,67 @@
     class ConsoleAuthManager(manager.Manager):
         """Manages token based authentication."""
     
    -    RPC_API_VERSION = '1.1'
    +    RPC_API_VERSION = '1.2'
     
         def __init__(self, scheduler_driver=None, *args, **kwargs):
             super(ConsoleAuthManager, self).__init__(*args, **kwargs)
             self.mc = memorycache.get_client()
    +        self.compute_rpcapi = compute_rpcapi.ComputeAPI()
    +        self.conductor_api = conductor_api.API()
    +
    +    def _get_tokens_for_instance(self, instance_uuid):
    +        tokens_str = self.mc.get(instance_uuid.encode('UTF-8'))
    +        if not tokens_str:
    +            tokens = []
    +        else:
    +            tokens = jsonutils.loads(tokens_str)
    +        return tokens
     
         def authorize_console(self, context, token, console_type, host, port,
    -                          internal_access_path):
    +                          internal_access_path, instance_uuid=None):
    +
             token_dict = {'token': token,
    +                      'instance_uuid': instance_uuid,
                           'console_type': console_type,
                           'host': host,
                           'port': port,
                           'internal_access_path': internal_access_path,
                           'last_activity_at': time.time()}
             data = jsonutils.dumps(token_dict)
             self.mc.set(token.encode('UTF-8'), data, CONF.console_token_ttl)
    +        if instance_uuid is not None:
    +            tokens = self._get_tokens_for_instance(instance_uuid)
    +            tokens.append(token)
    +            self.mc.set(instance_uuid.encode('UTF-8'),
    +                        jsonutils.dumps(tokens))
    +
             LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals())
     
    +    def _validate_token(self, context, token):
    +        instance_uuid = token['instance_uuid']
    +        if instance_uuid is None:
    +            return False
    +        instance = self.conductor_api.instance_get_by_uuid(context,
    +                                                             instance_uuid)
    +        return self.compute_rpcapi.validate_console_port(context,
    +                                            instance,
    +                                            token['port'],
    +                                            token['console_type'])
    +
         def check_token(self, context, token):
             token_str = self.mc.get(token.encode('UTF-8'))
             token_valid = (token_str is not None)
             LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals())
             if token_valid:
    -            return jsonutils.loads(token_str)
    +            token = jsonutils.loads(token_str)
    +            if self._validate_token(context, token):
    +                return token
    +
    +    def delete_tokens_for_instance(self, context, instance_uuid):
    +        tokens = self._get_tokens_for_instance(instance_uuid)
    +        for token in tokens:
    +            self.mc.delete(token)
    +        self.mc.delete(instance_uuid.encode('UTF-8'))
     
         def get_backdoor_port(self, context):
             return self.backdoor_port
    
  • nova/consoleauth/rpcapi.py+12 2 modified
    @@ -32,6 +32,8 @@ class ConsoleAuthAPI(nova.openstack.common.rpc.proxy.RpcProxy):
     
             1.0 - Initial version.
             1.1 - Added get_backdoor_port()
    +        1.2 - Added instance_uuid to authorize_console, and
    +              delete_tokens_for_instance
         '''
     
         #
    @@ -50,18 +52,26 @@ def __init__(self):
                     default_version=self.BASE_RPC_API_VERSION)
     
         def authorize_console(self, ctxt, token, console_type, host, port,
    -                          internal_access_path):
    +                          internal_access_path, instance_uuid=None):
             # The remote side doesn't return anything, but we want to block
             # until it completes.
             return self.call(ctxt,
                     self.make_msg('authorize_console',
                                   token=token, console_type=console_type,
                                   host=host, port=port,
    -                              internal_access_path=internal_access_path))
    +                              internal_access_path=internal_access_path,
    +                              instance_uuid=instance_uuid),
    +                version="1.2")
     
         def check_token(self, ctxt, token):
             return self.call(ctxt, self.make_msg('check_token', token=token))
     
    +    def delete_tokens_for_instance(self, ctxt, instance_uuid):
    +        return self.call(ctxt,
    +                self.make_msg('delete_tokens_for_instance',
    +                              instance_uuid=instance_uuid),
    +                version="1.2")
    +
         def get_backdoor_port(self, ctxt, host):
             return self.call(ctxt, self.make_msg('get_backdoor_port'),
                              version='1.1')
    
  • nova/tests/compute/test_compute.py+73 4 modified
    @@ -1420,6 +1420,54 @@ def test_novnc_vnc_console(self):
     
             self.compute.terminate_instance(self.context, instance=instance)
     
    +    def test_validate_console_port_vnc(self):
    +        self.flags(vnc_enabled=True)
    +        self.flags(enabled=True, group='spice')
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_vnc_console",
    +                       fake_driver_get_console)
    +
    +        self.assertTrue(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "5900",
    +                                                            "novnc"))
    +
    +    def test_validate_console_port_spice(self):
    +        self.flags(vnc_enabled=True)
    +        self.flags(enabled=True, group='spice')
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_spice_console",
    +                       fake_driver_get_console)
    +
    +        self.assertTrue(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "5900",
    +                                                            "spice-html5"))
    +
    +    def test_validate_console_port_wrong_port(self):
    +        self.flags(vnc_enabled=True)
    +        self.flags(enabled=True, group='spice')
    +        instance = jsonutils.to_primitive(self._create_fake_instance())
    +
    +        def fake_driver_get_console(*args, **kwargs):
    +            return {'host': "fake_host", 'port': "5900",
    +                    'internal_access_path': None}
    +        self.stubs.Set(self.compute.driver, "get_vnc_console",
    +                       fake_driver_get_console)
    +
    +        self.assertFalse(self.compute.validate_console_port(self.context,
    +                                                            instance,
    +                                                            "wrongport",
    +                                                            "spice-html5"))
    +
         def test_xvpvnc_vnc_console(self):
             # Make sure we can a vnc console for an instance.
             self.flags(vnc_enabled=True)
    @@ -1715,6 +1763,25 @@ def fake_cleanup_volumes(context, instance):
                     instance=jsonutils.to_primitive(instance),
                     bdms={})
     
    +    def test_delete_instance_deletes_console_auth_tokens(self):
    +        instance = self._create_fake_instance()
    +        self.flags(vnc_enabled=True)
    +
    +        self.tokens_deleted = False
    +
    +        def fake_delete_tokens(*args, **kwargs):
    +            self.tokens_deleted = True
    +
    +        cauth_rpcapi = self.compute.consoleauth_rpcapi
    +        self.stubs.Set(cauth_rpcapi, 'delete_tokens_for_instance',
    +                       fake_delete_tokens)
    +
    +        self.compute._delete_instance(self.context,
    +                instance=jsonutils.to_primitive(instance),
    +                bdms={})
    +
    +        self.assertTrue(self.tokens_deleted)
    +
         def test_instance_termination_exception_sets_error(self):
             """Test that we handle InstanceTerminationFailure
             which is propagated up from the underlying driver.
    @@ -5735,7 +5802,8 @@ def test_vnc_console(self):
                                  'console_type': fake_console_type,
                                  'host': 'fake_console_host',
                                  'port': 'fake_console_port',
    -                             'internal_access_path': 'fake_access_path'}
    +                             'internal_access_path': 'fake_access_path',
    +                             'instance_uuid': fake_instance['uuid']}
             fake_connect_info2 = copy.deepcopy(fake_connect_info)
             fake_connect_info2['access_url'] = 'fake_console_url'
     
    @@ -5747,7 +5815,7 @@ def test_vnc_console(self):
                        'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION}
             rpc_msg2 = {'method': 'authorize_console',
                         'args': fake_connect_info,
    -                    'version': '1.0'}
    +                    'version': '1.2'}
     
             rpc.call(self.context, 'compute.%s' % fake_instance['host'],
                     rpc_msg1, None).AndReturn(fake_connect_info2)
    @@ -5779,7 +5847,8 @@ def test_spice_console(self):
                                  'console_type': fake_console_type,
                                  'host': 'fake_console_host',
                                  'port': 'fake_console_port',
    -                             'internal_access_path': 'fake_access_path'}
    +                             'internal_access_path': 'fake_access_path',
    +                             'instance_uuid': fake_instance['uuid']}
             fake_connect_info2 = copy.deepcopy(fake_connect_info)
             fake_connect_info2['access_url'] = 'fake_console_url'
     
    @@ -5791,7 +5860,7 @@ def test_spice_console(self):
                        'version': '2.24'}
             rpc_msg2 = {'method': 'authorize_console',
                         'args': fake_connect_info,
    -                    'version': '1.0'}
    +                    'version': '1.2'}
     
             rpc.call(self.context, 'compute.%s' % fake_instance['host'],
                     rpc_msg1, None).AndReturn(fake_connect_info2)
    
  • nova/tests/compute/test_rpcapi.py+6 0 modified
    @@ -171,6 +171,12 @@ def test_get_spice_console(self):
                     instance=self.fake_instance, console_type='type',
                     version='2.24')
     
    +    def test_validate_console_port(self):
    +        self._test_compute_api('validate_console_port', 'call',
    +                instance=self.fake_instance, port="5900",
    +                console_type="novnc",
    +                version="2.26")
    +
         def test_host_maintenance_mode(self):
             self._test_compute_api('host_maintenance_mode', 'call',
                     host_param='param', mode='mode', host='host')
    
  • nova/tests/consoleauth/test_consoleauth.py+63 1 modified
    @@ -42,12 +42,74 @@ def test_tokens_expire(self):
             self.useFixture(test.TimeOverride())
             token = 'mytok'
             self.flags(console_token_ttl=1)
    +
    +        def fake_validate_console_port(*args, **kwargs):
    +            return True
    +        self.stubs.Set(self.manager.compute_rpcapi,
    +                       "validate_console_port",
    +                       fake_validate_console_port)
    +
             self.manager.authorize_console(self.context, token, 'novnc',
    -                                       '127.0.0.1', 'host', '')
    +                                         '127.0.0.1', '8080', 'host',
    +                                         'instance')
             self.assertTrue(self.manager.check_token(self.context, token))
             timeutils.advance_time_seconds(1)
             self.assertFalse(self.manager.check_token(self.context, token))
     
    +    def test_multiple_tokens_for_instance(self):
    +        tokens = ["token" + str(i) for i in xrange(10)]
    +        instance = "12345"
    +
    +        def fake_validate_console_port(*args, **kwargs):
    +            return True
    +
    +        self.stubs.Set(self.manager.compute_rpcapi,
    +                       "validate_console_port",
    +                       fake_validate_console_port)
    +        for token in tokens:
    +            self.manager.authorize_console(self.context, token, 'novnc',
    +                                          '127.0.0.1', '8080', 'host',
    +                                          instance)
    +
    +        for token in tokens:
    +            self.assertTrue(self.manager.check_token(self.context, token))
    +
    +    def test_delete_tokens_for_instance(self):
    +        instance = "12345"
    +        tokens = ["token" + str(i) for i in xrange(10)]
    +        for token in tokens:
    +            self.manager.authorize_console(self.context, token, 'novnc',
    +                                          '127.0.0.1', '8080', 'host',
    +                                          instance)
    +        self.manager.delete_tokens_for_instance(self.context, instance)
    +        stored_tokens = self.manager._get_tokens_for_instance(instance)
    +
    +        self.assertEqual(len(stored_tokens), 0)
    +
    +        for token in tokens:
    +            self.assertFalse(self.manager.check_token(self.context, token))
    +
    +    def test_wrong_token_has_port(self):
    +        token = 'mytok'
    +
    +        def fake_validate_console_port(*args, **kwargs):
    +            return False
    +
    +        self.stubs.Set(self.manager.compute_rpcapi,
    +                       "validate_console_port",
    +                       fake_validate_console_port)
    +
    +        self.manager.authorize_console(self.context, token, 'novnc',
    +                                        '127.0.0.1', '8080', 'host',
    +                                        instance_uuid='instance')
    +        self.assertFalse(self.manager.check_token(self.context, token))
    +
    +    def test_console_no_instance_uuid(self):
    +        self.manager.authorize_console(self.context, "token", 'novnc',
    +                                        '127.0.0.1', '8080', 'host',
    +                                        instance_uuid=None)
    +        self.assertFalse(self.manager.check_token(self.context, "token"))
    +
         def test_get_backdoor_port(self):
             self.manager.backdoor_port = 59697
             port = self.manager.get_backdoor_port(self.context)
    
  • nova/tests/consoleauth/test_rpcapi.py+7 1 modified
    @@ -65,11 +65,17 @@ def _fake_call(_ctxt, _topic, _msg, _timeout):
         def test_authorize_console(self):
             self._test_consoleauth_api('authorize_console', token='token',
                     console_type='ctype', host='h', port='p',
    -                internal_access_path='iap')
    +                internal_access_path='iap', instance_uuid="instance",
    +                version="1.2")
     
         def test_check_token(self):
             self._test_consoleauth_api('check_token', token='t')
     
    +    def test_delete_tokens_for_instnace(self):
    +        self._test_consoleauth_api('delete_tokens_for_instance',
    +                                   instance_uuid="instance",
    +                                   version='1.2')
    +
         def test_get_backdoor_port(self):
             self._test_consoleauth_api('get_backdoor_port', host='fake_host',
                                        version='1.1')
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

20

News mentions

0

No linked articles in our index yet.