Add consoleauth manager

Change-Id: Ifddab90b5e61407a8a12b560738b39fcb5c9780e
Partially-Implements: bp console-support
This commit is contained in:
liusheng 2017-03-13 10:59:33 +08:00
parent d043e72ac5
commit 915312e25c
7 changed files with 288 additions and 0 deletions

@ -16,6 +16,7 @@
from oslo_config import cfg from oslo_config import cfg
from mogan.conf import api from mogan.conf import api
from mogan.conf import cache
from mogan.conf import database from mogan.conf import database
from mogan.conf import default from mogan.conf import default
from mogan.conf import engine from mogan.conf import engine
@ -40,3 +41,4 @@ neutron.register_opts(CONF)
quota.register_opts(CONF) quota.register_opts(CONF)
scheduler.register_opts(CONF) scheduler.register_opts(CONF)
shellinabox.register_opts(CONF) shellinabox.register_opts(CONF)
cache.register_opts(CONF)

24
mogan/conf/cache.py Normal file

@ -0,0 +1,24 @@
# Copyright 2017 Huawei Technologies Co.,LTD.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_cache import core
def register_opts(conf):
core.configure(conf)
def list_opts():
# The oslo_cache library returns a list of tuples
return dict(core._opts.list_opts())

@ -0,0 +1,155 @@
# Copyright (c) 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Auth Components for Consoles."""
import time
from eventlet import greenpool
import oslo_cache
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_serialization import jsonutils
import mogan.conf
from mogan.engine import rpcapi
LOG = logging.getLogger(__name__)
CONF = mogan.conf.CONF
class ConsoleAuthManager(object):
"""Manages token based authentication."""
target = messaging.Target(version='1.0')
def __init__(self, host, topic):
super(ConsoleAuthManager, self).__init__()
self.host = host
self.topic = topic
self._started = False
self._cache = None
self._cache_instance = None
self.engine_rpcapi = rpcapi.EngineAPI()
def init_host(self):
self._worker_pool = greenpool.GreenPool(
size=CONF.engine.workers_pool_size)
self._started = True
def del_host(self):
self._worker_pool.waitall()
self._started = False
def periodic_tasks(self, context, raise_on_error=False):
pass
@property
def cache(self):
"""The recommended config example:
[cache]
backend_argument = url:127.0.0.1:11211
expiration_time = 600
backend = dogpile.cache.memcached
enabled = True
"""
# TODO(liusheng)may need to define config options [consoleauth]
# section and then override to the [cache] section
if self._cache is None:
cache_region = oslo_cache.create_region()
self._cache = oslo_cache.configure_cache_region(CONF, cache_region)
return self._cache
@property
def cache_instance(self):
"""Init a permanent cache region for instance token storage."""
if self._cache_instance is None:
cache_ttl = CONF.cache.expiration_time
try:
CONF.set_override('expiration_time', None, 'cache')
cache_region = oslo_cache.create_region()
self._cache_instance = oslo_cache.configure_cache_region(
CONF, cache_region)
finally:
CONF.set_override('expiration_time', cache_ttl, 'cache')
return self._cache_instance
def reset(self):
LOG.info('Reloading Mogan engine RPC API')
self.engine_rpcapi = rpcapi.EngineAPI()
def _get_tokens_for_instance(self, instance_uuid):
tokens_str = self.cache_instance.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, instance_uuid,
access_url=None):
token_dict = {'token': token,
'instance_uuid': instance_uuid,
'console_type': console_type,
'host': host,
'port': port,
'internal_access_path': internal_access_path,
'access_url': access_url,
'last_activity_at': time.time()}
data = jsonutils.dumps(token_dict)
self.cache.set(token.encode('UTF-8'), data)
tokens = self._get_tokens_for_instance(instance_uuid)
# Remove the expired tokens from cache.
token_values = self.cache.get_multi(
[tok.encode('UTF-8') for tok in tokens])
tokens = [name for name, value in zip(tokens, token_values)
if value]
tokens.append(token)
self.cache_instance.set(instance_uuid.encode('UTF-8'),
jsonutils.dumps(tokens))
LOG.info("Received Token: %(token)s, %(token_dict)s",
{'token': token, 'token_dict': token_dict})
def _validate_token(self, context, token):
instance_uuid = token['instance_uuid']
if instance_uuid is None:
return False
return True
# TODO(need to validate the console port)
# return self.compute_rpcapi.validate_console_port(
# context, instance, token['port'], token['console_type'])
def check_token(self, context, token):
token_str = self.cache.get(token.encode('UTF-8'))
token_valid = bool(token_str)
LOG.info("Checking Token: %(token)s, %(token_valid)s",
{'token': token, 'token_valid': token_valid})
if token_valid:
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)
self.cache.delete_multi(
[tok.encode('UTF-8') for tok in tokens])
self.cache_instance.delete(instance_uuid.encode('UTF-8'))

@ -0,0 +1,106 @@
# Copyright 2017 Huawei Technologies Co.,LTD.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests For ConsoleAuth manager
"""
import time
import mock
from mogan.consoleauth import manager
from mogan.tests import base as test
class ConsoleAuthManagerTestCase(test.TestCase):
"""Test case for ConsoleAuthManager class."""
def setUp(self):
super(ConsoleAuthManagerTestCase, self).setUp()
self.manager = manager.ConsoleAuthManager('test-host',
'test-consoleauth-topic')
self.instance_uuid = 'e7481762-3215-4489-bde5-0068a6bf79d1'
self.config(backend='oslo_cache.dict', enabled=True,
group='cache')
self.addCleanup(
self.manager.delete_tokens_for_instance, self.context,
self.instance_uuid)
def test_reset(self):
with mock.patch('mogan.engine.rpcapi.EngineAPI') as mock_rpc:
old_rpcapi = self.manager.engine_rpcapi
self.manager.reset()
mock_rpc.assert_called_once_with()
self.assertNotEqual(old_rpcapi,
self.manager.engine_rpcapi)
def test_tokens_expire(self):
# Test that tokens expire correctly.
token = u'mytok'
self.config(expiration_time=1, group='cache')
self.manager.authorize_console(
self.context, token, 'shellinabox', '127.0.0.1', 4321,
None, self.instance_uuid, None)
self.assertIsNotNone(self.manager.check_token(self.context, token))
time.sleep(1)
self.assertIsNone(self.manager.check_token(self.context, token))
def test_multiple_tokens_for_instance(self):
tokens = [u"token" + str(i) for i in range(10)]
for token in tokens:
self.manager.authorize_console(
self.context, token, 'shellinabox', '127.0.0.1', 4321,
None, self.instance_uuid, None)
for token in tokens:
self.assertIsNotNone(
self.manager.check_token(self.context, token))
def test_delete_tokens_for_instance(self):
tokens = [u"token" + str(i) for i in range(10)]
for token in tokens:
self.manager.authorize_console(
self.context, token, 'shellinabox', '127.0.0.1', 4321,
None, self.instance_uuid, None)
self.manager.delete_tokens_for_instance(self.context,
self.instance_uuid)
stored_tokens = self.manager._get_tokens_for_instance(
self.instance_uuid)
self.assertEqual(len(stored_tokens), 0)
for token in tokens:
self.assertIsNone(
self.manager.check_token(self.context, token))
def test_delete_expired_tokens(self):
token = u'mytok'
self.config(expiration_time=1, group='cache')
self.manager.authorize_console(
self.context, token, 'shellinabox', '127.0.0.1', 4321,
None, self.instance_uuid, None)
time.sleep(1)
self.assertIsNone(self.manager.check_token(self.context, token))
token1 = u'mytok2'
self.manager.authorize_console(
self.context, token1, 'shellinabox', '127.0.0.1', 4321,
None, self.instance_uuid, None)
stored_tokens = self.manager._get_tokens_for_instance(
self.instance_uuid)
# when trying to store token1, expired token is removed fist.
self.assertEqual(len(stored_tokens), 1)
self.assertEqual(stored_tokens[0], token1)

@ -10,6 +10,7 @@ WebOb>=1.6.0 # MIT
python-ironicclient>=1.11.0 # Apache-2.0 python-ironicclient>=1.11.0 # Apache-2.0
python-neutronclient>=5.1.0 # Apache-2.0 python-neutronclient>=5.1.0 # Apache-2.0
python-glanceclient>=2.5.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0
oslo.cache>=1.5.0 # Apache-2.0
oslo.concurrency>=3.8.0 # Apache-2.0 oslo.concurrency>=3.8.0 # Apache-2.0
oslo.config>=3.22.0 # Apache-2.0 oslo.config>=3.22.0 # Apache-2.0
oslo.context>=2.12.0 # Apache-2.0 oslo.context>=2.12.0 # Apache-2.0