consoleauth: Store access_url on token authorization
Related-bug: 1409142 As part of the fix for the related bug - we've added protocol checking to mitigate MITM attacks, however we base protocol checking on a config option that is normally only intended for compute hosts. This is quite user hostile, as it is now important that all nodes running compute and proxy services have this option in sync. We can do better than that - we can persist the URL the client is expected to use, and once we get it back on token validation, we can make sure that the request is using the intended protocol, mitigating the MITM injected script attacks. This patch makes sure that the access_url is persisted with the token - the follow-up patch makes consoles use that info. Change-Id: I02a377f54de46536ca35413b615d3298967afc33
This commit is contained in:
parent
bf70df295b
commit
2ffcf18d00
|
@ -2858,7 +2858,8 @@ class API(base.Base):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type,
|
||||
connect_info['host'], connect_info['port'],
|
||||
connect_info['internal_access_path'], instance.uuid)
|
||||
connect_info['internal_access_path'], instance.uuid,
|
||||
access_url=connect_info['access_url'])
|
||||
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
|
@ -2878,7 +2879,8 @@ class API(base.Base):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type,
|
||||
connect_info['host'], connect_info['port'],
|
||||
connect_info['internal_access_path'], instance.uuid)
|
||||
connect_info['internal_access_path'], instance.uuid,
|
||||
access_url=connect_info['access_url'])
|
||||
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
|
@ -2898,7 +2900,8 @@ class API(base.Base):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type,
|
||||
connect_info['host'], connect_info['port'],
|
||||
connect_info['internal_access_path'], instance.uuid)
|
||||
connect_info['internal_access_path'], instance.uuid,
|
||||
access_url=connect_info['access_url'])
|
||||
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
|
@ -2919,7 +2922,8 @@ class API(base.Base):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type,
|
||||
connect_info['host'], connect_info['port'],
|
||||
connect_info['internal_access_path'], instance.uuid)
|
||||
connect_info['internal_access_path'], instance.uuid,
|
||||
access_url=connect_info['access_url'])
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
@check_instance_host
|
||||
|
|
|
@ -337,7 +337,7 @@ class ComputeCellsAPI(compute_api.API):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type, connect_info['host'],
|
||||
connect_info['port'], connect_info['internal_access_path'],
|
||||
instance.uuid)
|
||||
instance.uuid, access_url=connect_info['access_url'])
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
@wrap_check_policy
|
||||
|
@ -353,7 +353,7 @@ class ComputeCellsAPI(compute_api.API):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type, connect_info['host'],
|
||||
connect_info['port'], connect_info['internal_access_path'],
|
||||
instance.uuid)
|
||||
instance.uuid, access_url=connect_info['access_url'])
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
@wrap_check_policy
|
||||
|
@ -369,7 +369,7 @@ class ComputeCellsAPI(compute_api.API):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type, connect_info['host'],
|
||||
connect_info['port'], connect_info['internal_access_path'],
|
||||
instance.uuid)
|
||||
instance.uuid, access_url=connect_info['access_url'])
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
@wrap_check_policy
|
||||
|
@ -385,7 +385,7 @@ class ComputeCellsAPI(compute_api.API):
|
|||
self.consoleauth_rpcapi.authorize_console(context,
|
||||
connect_info['token'], console_type, connect_info['host'],
|
||||
connect_info['port'], connect_info['internal_access_path'],
|
||||
instance.uuid)
|
||||
instance.uuid, access_url=connect_info['access_url'])
|
||||
return {'url': connect_info['access_url']}
|
||||
|
||||
@check_instance_cell
|
||||
|
|
|
@ -47,7 +47,7 @@ CONF.import_opt('enable', 'nova.cells.opts', group='cells')
|
|||
class ConsoleAuthManager(manager.Manager):
|
||||
"""Manages token based authentication."""
|
||||
|
||||
target = messaging.Target(version='2.0')
|
||||
target = messaging.Target(version='2.1')
|
||||
|
||||
def __init__(self, scheduler_driver=None, *args, **kwargs):
|
||||
super(ConsoleAuthManager, self).__init__(service_name='consoleauth',
|
||||
|
@ -65,7 +65,8 @@ class ConsoleAuthManager(manager.Manager):
|
|||
return tokens
|
||||
|
||||
def authorize_console(self, context, token, console_type, host, port,
|
||||
internal_access_path, instance_uuid):
|
||||
internal_access_path, instance_uuid,
|
||||
access_url=None):
|
||||
|
||||
token_dict = {'token': token,
|
||||
'instance_uuid': instance_uuid,
|
||||
|
@ -73,6 +74,7 @@ class ConsoleAuthManager(manager.Manager):
|
|||
'host': host,
|
||||
'port': port,
|
||||
'internal_access_path': internal_access_path,
|
||||
'access_url': access_url,
|
||||
'last_activity_at': time.time()}
|
||||
data = jsonutils.dumps(token_dict)
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ class ConsoleAuthAPI(object):
|
|||
... Icehouse and Juno support message version 2.0. So, any changes to
|
||||
existing methods in 2.x after that point should be done such that they
|
||||
can handle the version_cap being set to 2.0.
|
||||
|
||||
* 2.1 - Added access_url to authorize_console
|
||||
'''
|
||||
|
||||
VERSION_ALIASES = {
|
||||
|
@ -58,22 +60,28 @@ class ConsoleAuthAPI(object):
|
|||
|
||||
def __init__(self):
|
||||
super(ConsoleAuthAPI, self).__init__()
|
||||
target = messaging.Target(topic=CONF.consoleauth_topic, version='2.0')
|
||||
target = messaging.Target(topic=CONF.consoleauth_topic, version='2.1')
|
||||
version_cap = self.VERSION_ALIASES.get(CONF.upgrade_levels.consoleauth,
|
||||
CONF.upgrade_levels.consoleauth)
|
||||
self.client = rpc.get_client(target, version_cap=version_cap)
|
||||
|
||||
def authorize_console(self, ctxt, token, console_type, host, port,
|
||||
internal_access_path, instance_uuid):
|
||||
internal_access_path, instance_uuid,
|
||||
access_url):
|
||||
# The remote side doesn't return anything, but we want to block
|
||||
# until it completes.'
|
||||
cctxt = self.client.prepare()
|
||||
return cctxt.call(ctxt,
|
||||
'authorize_console',
|
||||
token=token, console_type=console_type,
|
||||
host=host, port=port,
|
||||
internal_access_path=internal_access_path,
|
||||
instance_uuid=instance_uuid)
|
||||
msg_args = dict(token=token, console_type=console_type,
|
||||
host=host, port=port,
|
||||
internal_access_path=internal_access_path,
|
||||
instance_uuid=instance_uuid,
|
||||
access_url=access_url)
|
||||
version = '2.1'
|
||||
if not self.client.can_send_version('2.1'):
|
||||
version = '2.0'
|
||||
del msg_args['access_url']
|
||||
|
||||
cctxt = self.client.prepare(version=version)
|
||||
return cctxt.call(ctxt, 'authorize_console', **msg_args)
|
||||
|
||||
def check_token(self, ctxt, token):
|
||||
cctxt = self.client.prepare()
|
||||
|
|
|
@ -8979,7 +8979,8 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
'authorize_console')
|
||||
self.compute_api.consoleauth_rpcapi.authorize_console(
|
||||
self.context, 'fake_token', fake_console_type, 'fake_console_host',
|
||||
'fake_console_port', 'fake_access_path', 'fake_uuid')
|
||||
'fake_console_port', 'fake_access_path', 'fake_uuid',
|
||||
access_url='fake_console_url')
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -9018,7 +9019,8 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
'authorize_console')
|
||||
self.compute_api.consoleauth_rpcapi.authorize_console(
|
||||
self.context, 'fake_token', fake_console_type, 'fake_console_host',
|
||||
'fake_console_port', 'fake_access_path', 'fake_uuid')
|
||||
'fake_console_port', 'fake_access_path', 'fake_uuid',
|
||||
access_url='fake_console_url')
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -9057,7 +9059,8 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
'authorize_console')
|
||||
self.compute_api.consoleauth_rpcapi.authorize_console(
|
||||
self.context, 'fake_token', fake_console_type, 'fake_console_host',
|
||||
'fake_console_port', 'fake_access_path', 'fake_uuid')
|
||||
'fake_console_port', 'fake_access_path', 'fake_uuid',
|
||||
access_url='fake_console_url')
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -9097,7 +9100,8 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
self.compute_api.consoleauth_rpcapi.authorize_console(
|
||||
self.context, 'fake_token', fake_console_type,
|
||||
'fake_serial_host', 'fake_tcp_port',
|
||||
'fake_access_path', 'fake_uuid')
|
||||
'fake_access_path', 'fake_uuid',
|
||||
access_url='fake_access_url')
|
||||
|
||||
console = self.compute_api.get_serial_console(self.context,
|
||||
fake_instance,
|
||||
|
|
|
@ -20,6 +20,7 @@ import contextlib
|
|||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
|
||||
from nova.consoleauth import rpcapi as consoleauth_rpcapi
|
||||
from nova import context
|
||||
|
@ -29,6 +30,8 @@ CONF = cfg.CONF
|
|||
|
||||
|
||||
class ConsoleAuthRpcAPITestCase(test.NoDBTestCase):
|
||||
DROPPED_ARG = object()
|
||||
|
||||
def _test_consoleauth_api(self, method, **kwargs):
|
||||
do_cast = kwargs.pop('_do_cast', False)
|
||||
|
||||
|
@ -40,6 +43,10 @@ class ConsoleAuthRpcAPITestCase(test.NoDBTestCase):
|
|||
|
||||
orig_prepare = rpcapi.client.prepare
|
||||
|
||||
version = kwargs.pop('version', None)
|
||||
rpc_kwargs = {k: v for k, v in six.iteritems(kwargs)
|
||||
if v is not self.DROPPED_ARG}
|
||||
|
||||
with contextlib.nested(
|
||||
mock.patch.object(rpcapi.client, 'cast' if do_cast else 'call'),
|
||||
mock.patch.object(rpcapi.client, 'prepare'),
|
||||
|
@ -49,19 +56,35 @@ class ConsoleAuthRpcAPITestCase(test.NoDBTestCase):
|
|||
):
|
||||
prepare_mock.return_value = rpcapi.client
|
||||
rpc_mock.return_value = None if do_cast else 'foo'
|
||||
csv_mock.side_effect = (
|
||||
lambda v: orig_prepare().can_send_version())
|
||||
|
||||
def fake_csv(v):
|
||||
if version:
|
||||
return orig_prepare(
|
||||
version_cap=version).can_send_version(version=v)
|
||||
else:
|
||||
return orig_prepare().can_send_version()
|
||||
csv_mock.side_effect = fake_csv
|
||||
|
||||
retval = getattr(rpcapi, method)(ctxt, **kwargs)
|
||||
self.assertEqual(retval, rpc_mock.return_value)
|
||||
|
||||
prepare_mock.assert_called_once_with()
|
||||
rpc_mock.assert_called_once_with(ctxt, method, **kwargs)
|
||||
if version:
|
||||
prepare_mock.assert_called_once_with(version=version)
|
||||
else:
|
||||
prepare_mock.assert_called_once_with()
|
||||
rpc_mock.assert_called_once_with(ctxt, method, **rpc_kwargs)
|
||||
|
||||
def test_authorize_console(self):
|
||||
self._test_consoleauth_api('authorize_console', token='token',
|
||||
console_type='ctype', host='h', port='p',
|
||||
internal_access_path='iap', instance_uuid="instance")
|
||||
internal_access_path='iap', instance_uuid="instance",
|
||||
access_url=self.DROPPED_ARG, version='2.0')
|
||||
|
||||
def test_authorize_console_access_url(self):
|
||||
self._test_consoleauth_api('authorize_console', token='token',
|
||||
console_type='ctype', host='h', port='p',
|
||||
internal_access_path='iap', instance_uuid="instance",
|
||||
access_url="fake_access_url", version='2.1')
|
||||
|
||||
def test_check_token(self):
|
||||
self._test_consoleauth_api('check_token', token='t')
|
||||
|
|
Loading…
Reference in New Issue