Merge "Hash credentials on user, project/tenant and pwd"

This commit is contained in:
Jenkins 2016-05-26 14:29:47 +00:00 committed by Gerrit Code Review
commit cbf0a5d4f5
5 changed files with 172 additions and 84 deletions

View File

@ -186,11 +186,6 @@ def get_configured_credentials(credential_type, fill_in=True,
params[attr] = getattr(_section, attr)
else:
params[attr] = getattr(_section, prefix + "_" + attr)
# NOTE(andreaf) v2 API still uses tenants, so we must translate project
# to tenant before building the Credentials object
if identity_version == 'v2':
params['tenant_name'] = params.get('project_name')
params.pop('project_name', None)
# Build and validate credentials. We are reading configured credentials,
# so validate them even if fill_in is False
credentials = get_credentials(fill_in=fill_in,

View File

@ -43,6 +43,11 @@ def read_accounts_yaml(path):
class PreProvisionedCredentialProvider(cred_provider.CredentialProvider):
# Exclude from the hash fields specific to v2 or v3 identity API
# i.e. only include user*, project*, tenant* and password
HASH_CRED_FIELDS = (set(auth.KeystoneV2Credentials.ATTRIBUTES) &
set(auth.KeystoneV3Credentials.ATTRIBUTES))
def __init__(self, identity_version, test_accounts_file,
accounts_lock_dir, name=None, credentials_domain=None,
admin_role=None, object_storage_operator_role=None,
@ -104,6 +109,7 @@ class PreProvisionedCredentialProvider(cred_provider.CredentialProvider):
object_storage_operator_role=None,
object_storage_reseller_admin_role=None):
hash_dict = {'roles': {}, 'creds': {}, 'networks': {}}
# Loop over the accounts read from the yaml file
for account in accounts:
roles = []
@ -116,7 +122,9 @@ class PreProvisionedCredentialProvider(cred_provider.CredentialProvider):
if 'resources' in account:
resources = account.pop('resources')
temp_hash = hashlib.md5()
temp_hash.update(six.text_type(account).encode('utf-8'))
account_for_hash = dict((k, v) for (k, v) in six.iteritems(account)
if k in cls.HASH_CRED_FIELDS)
temp_hash.update(six.text_type(account_for_hash).encode('utf-8'))
temp_hash_key = temp_hash.hexdigest()
hash_dict['creds'][temp_hash_key] = account
for role in roles:
@ -262,13 +270,13 @@ class PreProvisionedCredentialProvider(cred_provider.CredentialProvider):
for _hash in self.hash_dict['creds']:
# Comparing on the attributes that are expected in the YAML
init_attributes = creds.get_init_attributes()
# Only use the attributes initially used to calculate the hash
init_attributes = [x for x in init_attributes if
x in self.HASH_CRED_FIELDS]
hash_attributes = self.hash_dict['creds'][_hash].copy()
if ('user_domain_name' in init_attributes and 'user_domain_name'
not in hash_attributes):
# Allow for the case of domain_name populated from config
domain_name = self.credentials_domain
hash_attributes['user_domain_name'] = domain_name
if all([getattr(creds, k) == hash_attributes[k] for
# NOTE(andreaf) Not all fields may be available on all credentials
# so defaulting to None for that case.
if all([getattr(creds, k, None) == hash_attributes.get(k, None) for
k in init_attributes]):
return _hash
raise AttributeError('Invalid credentials %s' % creds)
@ -351,23 +359,20 @@ class PreProvisionedCredentialProvider(cred_provider.CredentialProvider):
return net_creds
def _extend_credentials(self, creds_dict):
# In case of v3, adds a user_domain_name field to the creds
# dict if not defined
# Add or remove credential domain fields to fit the identity version
domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
if 'domain' in x)
msg = 'Assuming they are valid in the default domain.'
if self.identity_version == 'v3':
user_domain_fields = set(['user_domain_name', 'user_domain_id'])
if not user_domain_fields.intersection(set(creds_dict.keys())):
creds_dict['user_domain_name'] = self.credentials_domain
# NOTE(andreaf) In case of v2, replace project with tenant if project
# is provided and tenant is not
if not domain_fields.intersection(set(creds_dict.keys())):
msg = 'Using credentials %s for v3 API calls. ' + msg
LOG.warning(msg, self._sanitize_creds(creds_dict))
creds_dict['domain_name'] = self.credentials_domain
if self.identity_version == 'v2':
if ('project_name' in creds_dict and
'tenant_name' in creds_dict and
creds_dict['project_name'] != creds_dict['tenant_name']):
clean_creds = self._sanitize_creds(creds_dict)
msg = 'Cannot specify project and tenant at the same time %s'
raise exceptions.InvalidCredentials(msg % clean_creds)
if ('project_name' in creds_dict and
'tenant_name' not in creds_dict):
creds_dict['tenant_name'] = creds_dict['project_name']
creds_dict.pop('project_name')
if domain_fields.intersection(set(creds_dict.keys())):
msg = 'Using credentials %s for v2 API calls. ' + msg
LOG.warning(msg, self._sanitize_creds(creds_dict))
# Remove all valid domain attributes
for attr in domain_fields.intersection(set(creds_dict.keys())):
creds_dict.pop(attr)
return creds_dict

View File

@ -605,6 +605,7 @@ class Credentials(object):
"""
ATTRIBUTES = []
COLLISIONS = []
def __init__(self, **kwargs):
"""Enforce the available attributes at init time (only).
@ -616,6 +617,13 @@ class Credentials(object):
self._apply_credentials(kwargs)
def _apply_credentials(self, attr):
for (key1, key2) in self.COLLISIONS:
val1 = attr.get(key1)
val2 = attr.get(key2)
if val1 and val2 and val1 != val2:
msg = ('Cannot have conflicting values for %s and %s' %
(key1, key2))
raise exceptions.InvalidCredentials(msg)
for key in attr.keys():
if key in self.ATTRIBUTES:
setattr(self, key, attr[key])
@ -673,7 +681,33 @@ class Credentials(object):
class KeystoneV2Credentials(Credentials):
ATTRIBUTES = ['username', 'password', 'tenant_name', 'user_id',
'tenant_id']
'tenant_id', 'project_id', 'project_name']
COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
def __str__(self):
"""Represent only attributes included in self.ATTRIBUTES"""
attrs = [attr for attr in self.ATTRIBUTES if attr is not 'password']
_repr = dict((k, getattr(self, k)) for k in attrs)
return str(_repr)
def __setattr__(self, key, value):
# NOTE(andreaf) In order to ease the migration towards 'project' we
# support v2 credentials configured with 'project' and translate it
# to tenant on the fly. The original kwargs are stored for clients
# that may rely on them. We also set project when tenant is defined
# so clients can rely on project being part of credentials.
parent = super(KeystoneV2Credentials, self)
# for project_* set tenant only
if key == 'project_id':
parent.__setattr__('tenant_id', value)
elif key == 'project_name':
parent.__setattr__('tenant_name', value)
if key == 'tenant_id':
parent.__setattr__('project_id', value)
elif key == 'tenant_name':
parent.__setattr__('project_name', value)
# trigger default behaviour for all attributes
parent.__setattr__(key, value)
def is_valid(self):
"""Check of credentials (no API call)
@ -684,9 +718,6 @@ class KeystoneV2Credentials(Credentials):
return None not in (self.username, self.password)
COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
class KeystoneV3Credentials(Credentials):
"""Credentials suitable for the Keystone Identity V3 API"""
@ -694,16 +725,7 @@ class KeystoneV3Credentials(Credentials):
'project_domain_id', 'project_domain_name', 'project_id',
'project_name', 'tenant_id', 'tenant_name', 'user_domain_id',
'user_domain_name', 'user_id']
def _apply_credentials(self, attr):
for (key1, key2) in COLLISIONS:
val1 = attr.get(key1)
val2 = attr.get(key2)
if val1 and val2 and val1 != val2:
msg = ('Cannot have conflicting values for %s and %s' %
(key1, key2))
raise exceptions.InvalidCredentials(msg)
super(KeystoneV3Credentials, self)._apply_credentials(attr)
COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
def __setattr__(self, key, value):
parent = super(KeystoneV3Credentials, self)

View File

@ -27,7 +27,6 @@ from tempest.common import preprov_creds
from tempest import config
from tempest.lib import auth
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.identity.v2 import token_client
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests.lib import fake_identity
@ -43,40 +42,46 @@ class TestPreProvisionedCredentials(base.TestCase):
'object_storage_operator_role': 'operator',
'object_storage_reseller_admin_role': 'reseller'}
identity_response = fake_identity._fake_v2_response
token_client = ('tempest.lib.services.identity.v2.token_client'
'.TokenClient.raw_request')
@classmethod
def _fake_accounts(cls, admin_role):
return [
{'username': 'test_user1', 'tenant_name': 'test_tenant1',
'password': 'p'},
{'username': 'test_user2', 'project_name': 'test_tenant2',
'password': 'p'},
{'username': 'test_user3', 'tenant_name': 'test_tenant3',
'password': 'p'},
{'username': 'test_user4', 'project_name': 'test_tenant4',
'password': 'p'},
{'username': 'test_user5', 'tenant_name': 'test_tenant5',
'password': 'p'},
{'username': 'test_user6', 'project_name': 'test_tenant6',
'password': 'p', 'roles': ['role1', 'role2']},
{'username': 'test_user7', 'tenant_name': 'test_tenant7',
'password': 'p', 'roles': ['role2', 'role3']},
{'username': 'test_user8', 'project_name': 'test_tenant8',
'password': 'p', 'roles': ['role4', 'role1']},
{'username': 'test_user9', 'tenant_name': 'test_tenant9',
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_user10', 'project_name': 'test_tenant10',
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_user11', 'tenant_name': 'test_tenant11',
'password': 'p', 'roles': [admin_role]},
{'username': 'test_user12', 'project_name': 'test_tenant12',
'password': 'p', 'roles': [admin_role]}]
def setUp(self):
super(TestPreProvisionedCredentials, self).setUp()
self.useFixture(fake_config.ConfigFixture())
self.patchobject(config, 'TempestConfigPrivate',
fake_config.FakePrivate)
self.patchobject(token_client.TokenClient, 'raw_request',
fake_identity._fake_v2_response)
self.patch(self.token_client, side_effect=self.identity_response)
self.useFixture(lockutils_fixtures.ExternalLockFixture())
self.test_accounts = [
{'username': 'test_user1', 'tenant_name': 'test_tenant1',
'password': 'p'},
{'username': 'test_user2', 'tenant_name': 'test_tenant2',
'password': 'p'},
{'username': 'test_user3', 'tenant_name': 'test_tenant3',
'password': 'p'},
{'username': 'test_user4', 'tenant_name': 'test_tenant4',
'password': 'p'},
{'username': 'test_user5', 'tenant_name': 'test_tenant5',
'password': 'p'},
{'username': 'test_user6', 'tenant_name': 'test_tenant6',
'password': 'p', 'roles': ['role1', 'role2']},
{'username': 'test_user7', 'tenant_name': 'test_tenant7',
'password': 'p', 'roles': ['role2', 'role3']},
{'username': 'test_user8', 'tenant_name': 'test_tenant8',
'password': 'p', 'roles': ['role4', 'role1']},
{'username': 'test_user9', 'tenant_name': 'test_tenant9',
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_user10', 'tenant_name': 'test_tenant10',
'password': 'p', 'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_user11', 'tenant_name': 'test_tenant11',
'password': 'p', 'roles': [cfg.CONF.identity.admin_role]},
{'username': 'test_user12', 'tenant_name': 'test_tenant12',
'password': 'p', 'roles': [cfg.CONF.identity.admin_role]},
]
self.test_accounts = self._fake_accounts(cfg.CONF.identity.admin_role)
self.accounts_mock = self.useFixture(mockpatch.Patch(
'tempest.common.preprov_creds.read_accounts_yaml',
return_value=self.test_accounts))
@ -89,24 +94,33 @@ class TestPreProvisionedCredentials(base.TestCase):
def _get_hash_list(self, accounts_list):
hash_list = []
hash_fields = (
preprov_creds.PreProvisionedCredentialProvider.HASH_CRED_FIELDS)
for account in accounts_list:
hash = hashlib.md5()
hash.update(six.text_type(account).encode('utf-8'))
account_for_hash = dict((k, v) for (k, v) in six.iteritems(account)
if k in hash_fields)
hash.update(six.text_type(account_for_hash).encode('utf-8'))
temp_hash = hash.hexdigest()
hash_list.append(temp_hash)
return hash_list
def test_get_hash(self):
self.patchobject(token_client.TokenClient, 'raw_request',
fake_identity._fake_v2_response)
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params)
hash_list = self._get_hash_list(self.test_accounts)
test_cred_dict = self.test_accounts[3]
test_creds = auth.get_credentials(fake_identity.FAKE_AUTH_URL,
**test_cred_dict)
results = test_account_class.get_hash(test_creds)
self.assertEqual(hash_list[3], results)
# Test with all accounts to make sure we try all combinations
# and hide no race conditions
hash_index = 0
for test_cred_dict in self.test_accounts:
test_account_class = (
preprov_creds.PreProvisionedCredentialProvider(
**self.fixed_params))
hash_list = self._get_hash_list(self.test_accounts)
test_creds = auth.get_credentials(
fake_identity.FAKE_AUTH_URL,
identity_version=self.fixed_params['identity_version'],
**test_cred_dict)
results = test_account_class.get_hash(test_creds)
self.assertEqual(hash_list[hash_index], results)
hash_index += 1
def test_get_hash_dict(self):
test_account_class = preprov_creds.PreProvisionedCredentialProvider(
@ -331,3 +345,53 @@ class TestPreProvisionedCredentials(base.TestCase):
self.assertIn('id', network)
self.assertEqual('fake-id', network['id'])
self.assertEqual('network-2', network['name'])
class TestPreProvisionedCredentialsV3(TestPreProvisionedCredentials):
fixed_params = {'name': 'test class',
'identity_version': 'v3',
'test_accounts_file': 'fake_accounts_file',
'accounts_lock_dir': 'fake_locks_dir',
'admin_role': 'admin',
'object_storage_operator_role': 'operator',
'object_storage_reseller_admin_role': 'reseller'}
identity_response = fake_identity._fake_v3_response
token_client = ('tempest.lib.services.identity.v3.token_client'
'.V3TokenClient.raw_request')
@classmethod
def _fake_accounts(cls, admin_role):
return [
{'username': 'test_user1', 'project_name': 'test_project1',
'domain_name': 'domain', 'password': 'p'},
{'username': 'test_user2', 'project_name': 'test_project2',
'domain_name': 'domain', 'password': 'p'},
{'username': 'test_user3', 'project_name': 'test_project3',
'domain_name': 'domain', 'password': 'p'},
{'username': 'test_user4', 'project_name': 'test_project4',
'domain_name': 'domain', 'password': 'p'},
{'username': 'test_user5', 'project_name': 'test_project5',
'domain_name': 'domain', 'password': 'p'},
{'username': 'test_user6', 'project_name': 'test_project6',
'domain_name': 'domain', 'password': 'p',
'roles': ['role1', 'role2']},
{'username': 'test_user7', 'project_name': 'test_project7',
'domain_name': 'domain', 'password': 'p',
'roles': ['role2', 'role3']},
{'username': 'test_user8', 'project_name': 'test_project8',
'domain_name': 'domain', 'password': 'p',
'roles': ['role4', 'role1']},
{'username': 'test_user9', 'project_name': 'test_project9',
'domain_name': 'domain', 'password': 'p',
'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_user10', 'project_name': 'test_project10',
'domain_name': 'domain', 'password': 'p',
'roles': ['role1', 'role2', 'role3', 'role4']},
{'username': 'test_user11', 'project_name': 'test_project11',
'domain_name': 'domain', 'password': 'p',
'roles': [admin_role]},
{'username': 'test_user12', 'project_name': 'test_project12',
'domain_name': 'domain', 'password': 'p',
'roles': [admin_role]}]

View File

@ -36,8 +36,10 @@ class CredentialsTests(base.TestCase):
# Check the right version of credentials has been returned
self.assertIsInstance(credentials, credentials_class)
# Check the id attributes are filled in
# NOTE(andreaf) project_* attributes are accepted as input but
# never set on the credentials object
attributes = [x for x in credentials.ATTRIBUTES if (
'_id' in x and x != 'domain_id')]
'_id' in x and x != 'domain_id' and x != 'project_id')]
for attr in attributes:
if filled:
self.assertIsNotNone(getattr(credentials, attr))