Generate user passwords with special characters

When we create user accounts in order to allow signalling back to Heat,
generate the passwords using a mix of upper- and lower-case letters, digits
and special characters. This should pass most password_regex filters that
users are likely to configure in Keystone, which was not true of the
previous method (using only hex characters from a UUID).

Change-Id: I7702d6ab550e4f1f53c4cea9f67ed0402afbd66d
Closes-Bug: #1666129
Closes-Bug: #1444429
(cherry picked from commit 61412548ae)
This commit is contained in:
Zane Bitter 2018-03-20 20:48:15 -04:00
parent 8437ec3fce
commit e80a6ec752
6 changed files with 29 additions and 20 deletions

View File

@ -107,3 +107,11 @@ def generate_password(length, char_classes):
selected_chars = char_buffer.getvalue() selected_chars = char_buffer.getvalue()
char_buffer.close() char_buffer.close()
return ''.join(random.sample(selected_chars, length)) return ''.join(random.sample(selected_chars, length))
def generate_openstack_password():
"""Generate a random password suitable for a Keystone User."""
return generate_password(32, [named_char_class(LOWERCASE, 1),
named_char_class(UPPERCASE, 1),
named_char_class(DIGITS, 1),
special_char_class('!@#%^&*', 1)])

View File

@ -29,6 +29,7 @@ from oslo_utils import importutils
from heat.common import context from heat.common import context
from heat.common import exception from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.common import password_gen
LOG = logging.getLogger('heat.engine.clients.keystoneclient') LOG = logging.getLogger('heat.engine.clients.keystoneclient')
@ -477,7 +478,7 @@ class KsClientWrapper(object):
user_id = user_id or self.context.get_access(self.session).user_id user_id = user_id or self.context.get_access(self.session).user_id
project_id = self.context.tenant_id project_id = self.context.tenant_id
data_blob = {'access': uuid.uuid4().hex, data_blob = {'access': uuid.uuid4().hex,
'secret': uuid.uuid4().hex} 'secret': password_gen.generate_openstack_password()}
ec2_creds = self.client.credentials.create( ec2_creds = self.client.credentials.create(
user=user_id, type='ec2', blob=jsonutils.dumps(data_blob), user=user_id, type='ec2', blob=jsonutils.dumps(data_blob),
project=project_id) project=project_id)
@ -496,7 +497,7 @@ class KsClientWrapper(object):
# files which lack domain configuration # files which lack domain configuration
return self.create_ec2_keypair(user_id) return self.create_ec2_keypair(user_id)
data_blob = {'access': uuid.uuid4().hex, data_blob = {'access': uuid.uuid4().hex,
'secret': uuid.uuid4().hex} 'secret': password_gen.generate_openstack_password()}
creds = self.domain_admin_client.credentials.create( creds = self.domain_admin_client.credentials.create(
user=user_id, type='ec2', blob=jsonutils.dumps(data_blob), user=user_id, type='ec2', blob=jsonutils.dumps(data_blob),
project=project_id) project=project_id)

View File

@ -11,11 +11,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import uuid
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from heat.common.i18n import _ from heat.common.i18n import _
from heat.common import password_gen
from heat.engine import attributes from heat.engine import attributes
from heat.engine import constraints from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
@ -135,7 +134,7 @@ class HeatWaitConditionHandle(wc_base.BaseWaitConditionHandle):
self.SIGNAL_TRANSPORT) == self.TOKEN_SIGNAL self.SIGNAL_TRANSPORT) == self.TOKEN_SIGNAL
def handle_create(self): def handle_create(self):
self.password = uuid.uuid4().hex self.password = password_gen.generate_openstack_password()
super(HeatWaitConditionHandle, self).handle_create() super(HeatWaitConditionHandle, self).handle_create()
if self._signal_transport_token(): if self._signal_transport_token():
# FIXME(shardy): The assumption here is that token expiry > timeout # FIXME(shardy): The assumption here is that token expiry > timeout

View File

@ -18,6 +18,7 @@ from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from heat.common import exception from heat.common import exception
from heat.common import password_gen
from heat.engine.clients import progress from heat.engine.clients import progress
from heat.engine.resources import stack_user from heat.engine.resources import stack_user
@ -151,7 +152,7 @@ class BaseServer(stack_user.StackUser):
elif (self.transport_poll_server_heat(props) or elif (self.transport_poll_server_heat(props) or
self.transport_zaqar_message(props)): self.transport_zaqar_message(props)):
if self.password is None: if self.password is None:
self.password = uuid.uuid4().hex self.password = password_gen.generate_openstack_password()
self._create_user() self._create_user()
self._register_access_key() self._register_access_key()

View File

@ -11,8 +11,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import uuid
from keystoneclient.contrib.ec2 import utils as ec2_utils from keystoneclient.contrib.ec2 import utils as ec2_utils
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
@ -21,6 +19,7 @@ from six.moves.urllib import parse as urlparse
from heat.common import exception from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.common import password_gen
from heat.engine.clients.os import swift from heat.engine.clients.os import swift
from heat.engine.resources import stack_user from heat.engine.resources import stack_user
@ -102,7 +101,7 @@ class SignalResponder(stack_user.StackUser):
""" """
if self._get_user_id() is None: if self._get_user_id() is None:
if self.password is None: if self.password is None:
self.password = uuid.uuid4().hex self.password = password_gen.generate_openstack_password()
self._create_user() self._create_user()
return {'auth_url': self.keystone().v3_endpoint, return {'auth_url': self.keystone().v3_endpoint,
'username': self.physical_resource_name(), 'username': self.physical_resource_name(),
@ -283,7 +282,7 @@ class SignalResponder(stack_user.StackUser):
if self._get_user_id() is None: if self._get_user_id() is None:
if self.password is None: if self.password is None:
self.password = uuid.uuid4().hex self.password = password_gen.generate_openstack_password()
self._create_user() self._create_user()
queue_id = self.physical_resource_name() queue_id = self.physical_resource_name()

View File

@ -12,6 +12,7 @@
# under the License. # under the License.
import json import json
import mock
import uuid import uuid
from keystoneauth1 import access as ks_access from keystoneauth1 import access as ks_access
@ -29,6 +30,7 @@ import six
from heat.common import config from heat.common import config
from heat.common import exception from heat.common import exception
from heat.common import password_gen
from heat.engine.clients.os.keystone import heat_keystoneclient from heat.engine.clients.os.keystone import heat_keystoneclient
from heat.tests import common from heat.tests import common
from heat.tests import utils from heat.tests import utils
@ -983,14 +985,13 @@ class KeystoneClientTest(common.HeatTestCase):
user_id='duser123', project_id='aproject', user_id='duser123', project_id='aproject',
credential_id='acredentialid') credential_id='acredentialid')
def _stub_uuid(self, values=None): def _stub_gen_creds(self, access, secret):
# stub UUID.hex to return the values specified # stub UUID.hex to return the values specified
values = values or [] mock_access_uuid = mock.Mock()
self.m.StubOutWithMock(uuid, 'uuid4') mock_access_uuid.hex = access
for v in values: self.patchobject(uuid, 'uuid4', return_value=mock_access_uuid)
mock_uuid = self.m.CreateMockAnything() self.patchobject(password_gen, 'generate_openstack_password',
mock_uuid.hex = v return_value=secret)
uuid.uuid4().AndReturn(mock_uuid)
def test_create_ec2_keypair(self): def test_create_ec2_keypair(self):
@ -1006,7 +1007,7 @@ class KeystoneClientTest(common.HeatTestCase):
ex_data_json = json.dumps(ex_data) ex_data_json = json.dumps(ex_data)
# stub UUID.hex to match ex_data # stub UUID.hex to match ex_data
self._stub_uuid(['dummy_access', 'dummy_secret']) self._stub_gen_creds('dummy_access', 'dummy_secret')
# mock keystone client credentials functions # mock keystone client credentials functions
self.mock_ks_v3_client.credentials = self.m.CreateMockAnything() self.mock_ks_v3_client.credentials = self.m.CreateMockAnything()
@ -1042,7 +1043,7 @@ class KeystoneClientTest(common.HeatTestCase):
ex_data_json = json.dumps(ex_data) ex_data_json = json.dumps(ex_data)
# stub UUID.hex to match ex_data # stub UUID.hex to match ex_data
self._stub_uuid(['dummy_access2', 'dummy_secret2']) self._stub_gen_creds('dummy_access2', 'dummy_secret2')
# mock keystone client credentials functions # mock keystone client credentials functions
self.mock_admin_client.credentials = self.m.CreateMockAnything() self.mock_admin_client.credentials = self.m.CreateMockAnything()
@ -1079,7 +1080,7 @@ class KeystoneClientTest(common.HeatTestCase):
ex_data_json = json.dumps(ex_data) ex_data_json = json.dumps(ex_data)
# stub UUID.hex to match ex_data # stub UUID.hex to match ex_data
self._stub_uuid(['dummy_access2', 'dummy_secret2']) self._stub_gen_creds('dummy_access2', 'dummy_secret2')
# mock keystone client credentials functions # mock keystone client credentials functions
self.mock_ks_v3_client.credentials = self.m.CreateMockAnything() self.mock_ks_v3_client.credentials = self.m.CreateMockAnything()