Add console auth tokens db api methods

Console auth tokens will be saved in the database
instead of in memory in a console auth server.

Adding the db api methods to create token records,
get them and delete all tokens for an instance
in this patch.

The following patch in the series will add the console
auth token object.

Change-Id: I881faa62f3be4986b38d11c4ac059672ae45c11f
Co-Authored-By: Eli Qiao <qiaoliyong@gmail.com>
partially-implements: blueprint convert-consoles-to-objects
This commit is contained in:
Paul Murray 2016-05-23 15:23:18 +01:00
parent c880706ddd
commit 0c5ff5057e
5 changed files with 252 additions and 0 deletions

View File

@ -2045,3 +2045,39 @@ def instance_tag_delete_all(context, instance_uuid):
def instance_tag_exists(context, instance_uuid, tag):
"""Check if specified tag exist on the instance."""
return IMPL.instance_tag_exists(context, instance_uuid, tag)
####################
def console_auth_token_create(context, values):
"""Create a console authorization."""
return IMPL.console_auth_token_create(context, values)
def console_auth_token_get_valid(context, token_hash, instance_uuid):
"""Get a valid console authorization by token_hash and instance_uuid.
The console authorizations expire at the time specified by their
'expires' column. An expired console auth token will not be returned
to the caller - it is treated as if it does not exist.
"""
return IMPL.console_auth_token_get_valid(context,
token_hash,
instance_uuid)
def console_auth_token_destroy_all_by_instance(context, instance_uuid):
"""Delete all console authorizations belonging to the instance."""
return IMPL.console_auth_token_destroy_all_by_instance(context,
instance_uuid)
def console_auth_token_destroy_expired_by_host(context, host):
"""Delete expired console authorizations belonging to the host.
The console authorizations expire at the time specified by their
'expires' column. This function is used to garbage collect expired
tokens associated with the given host.
"""
return IMPL.console_auth_token_destroy_expired_by_host(context, host)

View File

@ -6803,3 +6803,40 @@ def instance_tag_exists(context, instance_uuid, tag):
q = context.session.query(models.Tag).filter_by(
resource_id=instance_uuid, tag=tag)
return context.session.query(q.exists()).scalar()
####################
@pick_context_manager_writer
def console_auth_token_create(context, values):
instance_uuid = values.get('instance_uuid')
_check_instance_exists_in_project(context, instance_uuid)
token_ref = models.ConsoleAuthToken()
token_ref.update(values)
context.session.add(token_ref)
return token_ref
@pick_context_manager_reader
def console_auth_token_get_valid(context, token_hash, instance_uuid):
_check_instance_exists_in_project(context, instance_uuid)
return context.session.query(models.ConsoleAuthToken).\
filter_by(token_hash=token_hash).\
filter_by(instance_uuid=instance_uuid).\
filter(models.ConsoleAuthToken.expires > timeutils.utcnow_ts()).\
first()
@pick_context_manager_writer
def console_auth_token_destroy_all_by_instance(context, instance_uuid):
context.session.query(models.ConsoleAuthToken).\
filter_by(instance_uuid=instance_uuid).delete()
@pick_context_manager_writer
def console_auth_token_destroy_expired_by_host(context, host):
context.session.query(models.ConsoleAuthToken).\
filter_by(host=host).\
filter(models.ConsoleAuthToken.expires <= timeutils.utcnow_ts()).\
delete()

View File

@ -65,6 +65,7 @@ from nova import objects
from nova.objects import fields
from nova import quota
from nova import test
from nova.tests.unit import fake_console_auth_token
from nova.tests.unit import matchers
from nova.tests import uuidsentinel
from nova import utils
@ -9981,3 +9982,135 @@ class TestInstanceTagsFiltering(test.TestCase):
'not-tags': [u't5', u't6'],
'not-tags-any': [u't7', u't8']})
self._assertEqualInstanceUUIDs([uuids[3], uuids[5], uuids[6]], result)
class ConsoleAuthTokenTestCase(test.TestCase):
def _create_instances(self, uuids):
for uuid in uuids:
db.instance_create(self.context,
{'uuid': uuid,
'project_id': self.context.project_id})
def _create(self, token_hash, instance_uuid, expire_offset, host=None):
t = copy.deepcopy(fake_console_auth_token.fake_token_dict)
del t['id']
t['token_hash'] = token_hash
t['instance_uuid'] = instance_uuid
t['expires'] = timeutils.utcnow_ts() + expire_offset
if host:
t['host'] = host
db.console_auth_token_create(self.context, t)
def setUp(self):
super(ConsoleAuthTokenTestCase, self).setUp()
self.context = context.RequestContext('fake', 'fake')
def test_console_auth_token_create_no_instance(self):
t = copy.deepcopy(fake_console_auth_token.fake_token_dict)
del t['id']
self.assertRaises(exception.InstanceNotFound,
db.console_auth_token_create,
self.context, t)
def test_console_auth_token_get_valid_deleted_instance(self):
uuid1 = uuidsentinel.uuid1
hash1 = utils.get_sha256_str(uuidsentinel.token1)
self._create_instances([uuid1])
self._create(hash1, uuid1, 100)
db_obj1 = db.console_auth_token_get_valid(self.context, hash1, uuid1)
self.assertIsNotNone(db_obj1, "a valid token should be in database")
db.instance_destroy(self.context, uuid1)
self.assertRaises(exception.InstanceNotFound,
db.console_auth_token_get_valid,
self.context, hash1, uuid1)
def test_console_auth_token_destroy_all_by_instance(self):
uuid1 = uuidsentinel.uuid1
uuid2 = uuidsentinel.uuid2
hash1 = utils.get_sha256_str(uuidsentinel.token1)
hash2 = utils.get_sha256_str(uuidsentinel.token2)
hash3 = utils.get_sha256_str(uuidsentinel.token3)
self._create_instances([uuid1, uuid2])
self._create(hash1, uuid1, 100)
self._create(hash2, uuid1, 100)
self._create(hash3, uuid2, 100)
db_obj1 = db.console_auth_token_get_valid(self.context, hash1, uuid1)
db_obj2 = db.console_auth_token_get_valid(self.context, hash2, uuid1)
db_obj3 = db.console_auth_token_get_valid(self.context, hash3, uuid2)
self.assertIsNotNone(db_obj1, "a valid token should be in database")
self.assertIsNotNone(db_obj2, "a valid token should be in database")
self.assertIsNotNone(db_obj3, "a valid token should be in database")
db.console_auth_token_destroy_all_by_instance(self.context, uuid1)
db_obj4 = db.console_auth_token_get_valid(self.context, hash1, uuid1)
db_obj5 = db.console_auth_token_get_valid(self.context, hash2, uuid1)
db_obj6 = db.console_auth_token_get_valid(self.context, hash3, uuid2)
self.assertIsNone(db_obj4, "no valid token should be in database")
self.assertIsNone(db_obj5, "no valid token should be in database")
self.assertIsNotNone(db_obj6, "a valid token should be in database")
def test_console_auth_token_get_valid_by_expiry(self):
uuid1 = uuidsentinel.uuid1
uuid2 = uuidsentinel.uuid2
hash1 = utils.get_sha256_str(uuidsentinel.token1)
hash2 = utils.get_sha256_str(uuidsentinel.token2)
self.addCleanup(timeutils.clear_time_override)
timeutils.set_time_override(timeutils.utcnow())
self._create_instances([uuid1, uuid2])
self._create(hash1, uuid1, 10)
timeutils.advance_time_seconds(100)
self._create(hash2, uuid2, 10)
db_obj1 = db.console_auth_token_get_valid(self.context, hash1, uuid1)
db_obj2 = db.console_auth_token_get_valid(self.context, hash2, uuid2)
self.assertIsNone(db_obj1, "the token should have expired")
self.assertIsNotNone(db_obj2, "a valid token should be found here")
def test_console_auth_token_get_valid_by_uuid(self):
uuid1 = uuidsentinel.uuid1
uuid2 = uuidsentinel.uuid2
hash1 = utils.get_sha256_str(uuidsentinel.token1)
self._create_instances([uuid1, uuid2])
self._create(hash1, uuid1, 10)
db_obj1 = db.console_auth_token_get_valid(self.context, hash1, uuid1)
db_obj2 = db.console_auth_token_get_valid(self.context, hash1, uuid2)
self.assertIsNotNone(db_obj1, "a valid token should be found here")
self.assertIsNone(db_obj2, "the token uuid should not match")
def test_console_auth_token_destroy_expired_by_host(self):
uuid1 = uuidsentinel.uuid1
uuid2 = uuidsentinel.uuid2
uuid3 = uuidsentinel.uuid3
hash1 = utils.get_sha256_str(uuidsentinel.token1)
hash2 = utils.get_sha256_str(uuidsentinel.token2)
hash3 = utils.get_sha256_str(uuidsentinel.token3)
self.addCleanup(timeutils.clear_time_override)
timeutils.set_time_override(timeutils.utcnow())
self._create_instances([uuid1, uuid2, uuid3])
self._create(hash1, uuid1, 10)
self._create(hash2, uuid2, 10, host='other-host')
timeutils.advance_time_seconds(100)
self._create(hash3, uuid3, 10)
db.console_auth_token_destroy_expired_by_host(
self.context, 'fake-host')
# the api only supports getting unexpired tokens
# but by rolling back time we can see if a token that
# should be deleted is still there
timeutils.advance_time_seconds(-100)
db_obj1 = db.console_auth_token_get_valid(self.context, hash1, uuid1)
db_obj2 = db.console_auth_token_get_valid(self.context, hash2, uuid2)
db_obj3 = db.console_auth_token_get_valid(self.context, hash3, uuid3)
self.assertIsNone(db_obj1, "the token should have been deleted")
self.assertIsNotNone(db_obj2, "a valid token should be found here")
self.assertIsNotNone(db_obj3, "a valid token should be found here")

View File

@ -0,0 +1,33 @@
# Copyright 2016 Intel Corp.
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# 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 nova.tests import uuidsentinel
from nova import utils
fake_token = uuidsentinel.token
fake_token_hash = utils.get_sha256_str(fake_token)
fake_instance_uuid = uuidsentinel.instance
fake_token_dict = {
'created_at': None,
'updated_at': None,
'id': 123,
'token_hash': fake_token_hash,
'console_type': 'fake-type',
'host': 'fake-host',
'port': 1000,
'internal_access_path': 'fake-path',
'instance_uuid': fake_instance_uuid,
'expires': 100,
}

View File

@ -1211,6 +1211,19 @@ def get_hash_str(base_str):
return hashlib.md5(base_str).hexdigest()
def get_sha256_str(base_str):
"""Returns string that represents sha256 hash of base_str (in hex format).
sha1 and md5 are known to be breakable, so sha256 is a better option
when the hash is being used for security purposes. If hashing passwords
or anything else that needs to be retained for a long period a salted
hash is better.
"""
if isinstance(base_str, six.text_type):
base_str = base_str.encode('utf-8')
return hashlib.sha256(base_str).hexdigest()
def filter_and_format_resource_metadata(resource_type, resource_list,
search_filts, metadata_type=None):
"""Get all metadata for a list of resources after filtering.