def test_fernet_keys_and_credentials(self, mock_create_creds): keys = [uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False)] snmpd_password = uuidutils.generate_uuid(dashed=False) mock_mistral = mock.Mock() mock_mistral.environments.get.return_value = mock.Mock(variables={ "undercloud_ceilometer_snmpd_password": snmpd_password }) # generate_passwords will be called multiple times # but the order is based on how the strings are hashed, and thus # not really predictable. So, make sure it is a unique one of the # generated values mock_create_creds.side_effect = keys value = password_utils.generate_passwords(mock_mistral) self.assertIn(value['KeystoneCredential0'], keys) self.assertIn(value['KeystoneCredential1'], keys) self.assertIn(value['KeystoneFernetKey0'], keys) self.assertIn(value['KeystoneFernetKey1'], keys) self.assertIn(value['BarbicanSimpleCryptoKek'], keys) self.assertNotEqual(value['KeystoneFernetKey0'], value['KeystoneFernetKey1']) self.assertNotEqual(value['KeystoneCredential0'], value['KeystoneCredential1'])
def test_fernet_keys_and_credentials(self, mock_create_creds): keys = [uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False)] snmpd_password = uuidutils.generate_uuid(dashed=False) mock_mistral = mock.Mock() mock_mistral.environments.get.return_value = mock.Mock(variables={ "undercloud_ceilometer_snmpd_password": snmpd_password }) # generate_passwords will be called multiple times # but the order is based on how the strings are hashed, and thus # not really predictable. So, make sure it is a unique one of the # generated values mock_create_creds.side_effect = keys value = password_utils.generate_passwords(mock_mistral) self.assertIn(value['KeystoneCredential0'], keys) self.assertIn(value['KeystoneCredential1'], keys) self.assertIn(value['KeystoneFernetKey0'], keys) self.assertIn(value['KeystoneFernetKey1'], keys) self.assertNotEqual(value['KeystoneFernetKey0'], value['KeystoneFernetKey1']) self.assertNotEqual(value['KeystoneCredential0'], value['KeystoneCredential1'])
def run(self, context): heat = self.get_orchestration_client(context) swift = self.get_object_client(context) mistral = self.get_workflow_client(context) try: env = plan_utils.get_env(swift, self.container) except swiftexceptions.ClientException as err: err_msg = ("Error retrieving environment for plan %s: %s" % ( self.container, err)) LOG.exception(err_msg) return actions.Result(error=err_msg) try: stack_env = heat.stacks.environment( stack_id=self.container) # legacy heat resource names from overcloud.yaml # We don't modify these to avoid changing defaults for pw_res in constants.LEGACY_HEAT_PASSWORD_RESOURCE_NAMES: try: res = heat.resources.get(self.container, pw_res) param_defaults = stack_env.get('parameter_defaults', {}) param_defaults[pw_res] = res.attributes['value'] except heat_exc.HTTPNotFound: LOG.debug('Heat resouce not found: %s' % pw_res) pass except heat_exc.HTTPNotFound: stack_env = None passwords = password_utils.generate_passwords(mistral, stack_env) # if passwords don't yet exist in plan environment if 'passwords' not in env: env['passwords'] = {} # ensure all generated passwords are present in plan env, # but respect any values previously generated and stored for name, password in passwords.items(): if name not in env['passwords']: env['passwords'][name] = password try: plan_utils.put_env(swift, env) except swiftexceptions.ClientException as err: err_msg = "Error uploading to container: %s" % err LOG.exception(err_msg) return actions.Result(error=err_msg) self.cache_delete(context, self.container, "tripleo.parameters.get") return env['passwords']
def run(self, context): heat = self.get_orchestration_client(context) swift = self.get_object_client(context) mistral = self.get_workflow_client(context) try: env = plan_utils.get_env(swift, self.container) except swiftexceptions.ClientException as err: err_msg = ("Error retrieving environment for plan %s: %s" % ( self.container, err)) LOG.exception(err_msg) return actions.Result(error=err_msg) try: stack_env = heat.stacks.environment( stack_id=self.container) except heat_exc.HTTPNotFound: stack_env = None passwords = password_utils.generate_passwords(mistral, stack_env) # if passwords don't yet exist in plan environment if 'passwords' not in env: env['passwords'] = {} # ensure all generated passwords are present in plan env, # but respect any values previously generated and stored for name, password in passwords.items(): if name not in env['passwords']: env['passwords'][name] = password try: plan_utils.put_env(swift, env) except swiftexceptions.ClientException as err: err_msg = "Error uploading to container: %s" % err LOG.exception(err_msg) return actions.Result(error=err_msg) self.cache_delete(context, self.container, "tripleo.parameters.get") return env['passwords']
def _update_passwords_env(self, passwords=None): pw_file = os.path.join(os.environ.get('HOME', ''), 'tripleo-undercloud-passwords.yaml') stack_env = {'parameter_defaults': {}} if os.path.exists(pw_file): with open(pw_file) as pf: stack_env = yaml.safe_load(pf.read()) pw = password_utils.generate_passwords(stack_env=stack_env) stack_env['parameter_defaults'].update(pw) if passwords: # These passwords are the DefaultPasswords so we only # update if they don't already exist in stack_env for p, v in passwords.items(): if p not in stack_env['parameter_defaults']: stack_env['parameter_defaults'][p] = v with open(pw_file, 'w') as pf: yaml.safe_dump(stack_env, pf, default_flow_style=False) return pw_file
def run(self, context): heat = self.get_orchestration_client(context) swift = self.get_object_client(context) mistral = self.get_workflow_client(context) try: env = plan_utils.get_env(swift, self.container) except swiftexceptions.ClientException as err: err_msg = ("Error retrieving environment for plan %s: %s" % (self.container, err)) LOG.exception(err_msg) return actions.Result(error=err_msg) try: stack_env = heat.stacks.environment(stack_id=self.container) except heat_exc.HTTPNotFound: stack_env = None passwords = password_utils.generate_passwords(mistral, stack_env) # if passwords don't yet exist in plan environment if 'passwords' not in env: env['passwords'] = {} # ensure all generated passwords are present in plan env, # but respect any values previously generated and stored for name, password in passwords.items(): if name not in env['passwords']: env['passwords'][name] = password try: plan_utils.put_env(swift, env) except swiftexceptions.ClientException as err: err_msg = "Error uploading to container: %s" % err LOG.exception(err_msg) return actions.Result(error=err_msg) self.cache_delete(context, self.container, "tripleo.parameters.get") return env['passwords']
def run(self): orchestration = self.get_orchestration_client() wc = self.get_workflow_client() try: wf_env = wc.environments.get(self.container) except Exception: msg = "Error retrieving mistral environment: %s" % self.container LOG.exception(msg) return mistral_workflow_utils.Result(error=msg) try: stack_env = orchestration.stacks.environment( stack_id=self.container) except heat_exc.HTTPNotFound: stack_env = None passwords = password_utils.generate_passwords(wc, stack_env) # if passwords don't yet exist in mistral environment if 'passwords' not in wf_env.variables: wf_env.variables['passwords'] = {} # ensure all generated passwords are present in mistral env, # but respect any values previously generated and stored for name, password in passwords.items(): if name not in wf_env.variables['passwords']: wf_env.variables['passwords'][name] = password env_kwargs = { 'name': wf_env.name, 'variables': wf_env.variables, } wc.environments.update(**env_kwargs) self.cache_delete(self.container, "tripleo.parameters.get") return wf_env.variables['passwords']
def test_fernet_keys_and_credentials(self, mock_create_creds): keys = [uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False), uuidutils.generate_uuid(dashed=False)] # generate_passwords will be called multiple times # but the order is based on how the strings are hashed, and thus # not really predictable. So, make sure it is a unique one of the # generated values mock_create_creds.side_effect = keys with mock.patch(self.open_builtins, mock.mock_open(read_data="data")): with mock.patch('yaml.load') as mock_yaml: mock_yaml.return_value = { 'parameter_defaults': { 'SnmpdReadonlyUserPassword': self.snmp_test_pw } } value = password_utils.generate_passwords() self.assertIn(value['KeystoneCredential0'], keys) self.assertIn(value['KeystoneCredential1'], keys) self.assertIn(value['KeystoneFernetKey0'], keys) self.assertIn(value['KeystoneFernetKey1'], keys) self.assertIn(value['BarbicanSimpleCryptoKek'], keys) self.assertNotEqual(value['KeystoneFernetKey0'], value['KeystoneFernetKey1']) self.assertNotEqual(value['KeystoneCredential0'], value['KeystoneCredential1']) self.assertEqual(len(value['OctaviaServerCertsKeyPassphrase']), 32)
def _update_passwords_env(self, output_dir, passwords=None): pw_file = os.path.join(output_dir, 'tripleo-undercloud-passwords.yaml') undercloud_pw_file = os.path.join(output_dir, 'undercloud-passwords.conf') stack_env = {'parameter_defaults': {}} # Getting passwords that were managed by instack-undercloud so # we can upgrade to a containerized undercloud and keep old passwords. legacy_env = {} if os.path.exists(undercloud_pw_file): config = configparser.ConfigParser() config.read(undercloud_pw_file) for k, v in config.items('auth'): # Manage exceptions if k == 'undercloud_db_password': k = 'MysqlRootPassword' elif k == 'undercloud_rabbit_username': k = 'RabbitUserName' elif k == 'undercloud_heat_encryption_key': k = 'HeatAuthEncryptionKey' else: k = ''.join(i.capitalize() for i in k.split('_')[1:]) legacy_env[k] = v if os.path.exists(pw_file): with open(pw_file) as pf: stack_env = yaml.safe_load(pf.read()) pw = password_utils.generate_passwords(stack_env=stack_env) stack_env['parameter_defaults'].update(pw) # Override what has been generated by tripleo-common with old passwords # if any. stack_env['parameter_defaults'].update(legacy_env) if passwords: # These passwords are the DefaultPasswords so we only # update if they don't already exist in stack_env for p, v in passwords.items(): if p not in stack_env['parameter_defaults']: stack_env['parameter_defaults'][p] = v # Write out the password file in yaml for heat. # This contains sensitive data so ensure it's not world-readable with open(pw_file, 'w') as pf: yaml.safe_dump(stack_env, pf, default_flow_style=False) # Using chmod here instead of permissions on the open above so we don't # have to fight with umask. os.chmod(pw_file, 0o600) # Write out an instack undercloud compatible version. # This contains sensitive data so ensure it's not world-readable with open(undercloud_pw_file, 'w') as pf: pf.write('[auth]\n') for p, v in stack_env['parameter_defaults'].items(): if 'Password' in p or 'Token' in p: # Convert camelcase from heat templates into the underscore # format used by instack undercloud. s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', p) pw_key = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() pf.write('undercloud_%s: %s\n' % (pw_key, v)) os.chmod(undercloud_pw_file, 0o600) return pw_file
def generate_passwords(swift=None, heat=None, container=constants.DEFAULT_CONTAINER_NAME, rotate_passwords=False, rotate_pw_list=None, passwords_env=None): """Generates passwords needed for Overcloud deployment This method generates passwords. By default, this method respects previously generated passwords and in stack environment. If rotate_passwords is set to True, then passwords will be replaced as follows: - if password names are specified in the rotate_pw_list, then only those passwords will be replaced. - otherwise, all passwords not in the DO_NOT_ROTATE list (as they require special handling, like KEKs and Fernet keys) will be replaced. """ if rotate_pw_list is None: rotate_pw_list = [] if passwords_env: stack_env = passwords_env placement_extracted = True elif heat is None: stack_env = None placement_extracted = True else: try: stack_env = heat.stacks.environment(stack_id=container) except heat_exc.HTTPNotFound: stack_env = None placement_extracted = False try: # We can't rely on the existence of PlacementPassword to # determine if placement extraction has occured as it was added # in stein while the service extraction was delayed to train. # Inspect the endpoint map instead. endpoint_res = heat.resources.get(container, 'EndpointMap') endpoints = endpoint_res.attributes.get('endpoint_map', None) placement_extracted = endpoints and 'PlacementPublic' in endpoints except heat_exc.HTTPNotFound: pass passwords = password_utils.generate_passwords( stack_env=stack_env, rotate_passwords=rotate_passwords, rotate_pw_list=rotate_pw_list) # NOTE(ansmith): if rabbit password previously generated and # stored, facilitate upgrade and use for oslo messaging in plan env if 'RabbitPassword' in passwords: for i in ('RpcPassword', 'NotifyPassword'): if i not in passwords: passwords[i] = passwords['RabbitPassword'] # NOTE(owalsh): placement previously used NovaPassword # Default to the same password for PlacementPassword if it is an # upgrade (i.e NovaPassword is set) so we do not need to update the # password in keystone if not placement_extracted and 'NovaPassword' in passwords: LOG.debug('Setting PlacementPassword to NovaPassword') passwords['PlacementPassword'] = passwords['NovaPassword'] return passwords