Port password generation from tripleoclient to tripleo-common

At the moment the CLI generates various passwords, this logic is
then not available to the GUI. We should move this to further
standardise the process.

This patch ports the necessary utility methods and adds an action
to generate and store passwords in a mistral environment or retrieve
previously generated passwords from the mistral environment.  The action
will generate and replace any missing passwords.  The passwords are
stored in parameter_defaults and are used in any subsequent calls to heat
(e.g. Parameters and Stack Creation).

The order of the merge of parameter_defaults and passwords allows
a user to override a password value.

The action is added to workflows in the plan_management workbook.

Closes-Bug: #1621097
Change-Id: Ic476a09f7981d4e6ee12e05b333a18cda5b4626b
This commit is contained in:
Ryan Brady
2016-09-27 14:25:54 +01:00
committed by Dougal Matthews
parent 7d8c9acde4
commit 49a7b8e80e
10 changed files with 397 additions and 7 deletions
+1
View File
@@ -16,3 +16,4 @@ mistral!=2015.1.0,>=2.0.0 # Apache-2.0
python-ironic-inspector-client>=1.5.0 # Apache-2.0
Jinja2>=2.8 # BSD License (3 clause)
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
passlib>=1.6 # BSD
+1
View File
@@ -71,6 +71,7 @@ mistral.actions =
tripleo.parameters.reset = tripleo_common.actions.parameters:ResetParametersAction
tripleo.parameters.update = tripleo_common.actions.parameters:UpdateParametersAction
tripleo.parameters.update_role = tripleo_common.actions.parameters:UpdateRoleParametersAction
tripleo.parameters.generate_passwords = tripleo_common.actions.parameters:GeneratePasswordsAction
tripleo.plan.create = tripleo_common.actions.plan:CreatePlanAction
tripleo.plan.update = tripleo_common.actions.plan:UpdatePlanAction
tripleo.plan.create_container = tripleo_common.actions.plan:CreateContainerAction
+43
View File
@@ -34,6 +34,7 @@ from tripleo_common.actions import base
from tripleo_common.actions import templates
from tripleo_common import constants
from tripleo_common.utils import parameters
from tripleo_common.utils import passwords as password_utils
LOG = logging.getLogger(__name__)
@@ -128,3 +129,45 @@ class UpdateRoleParametersAction(UpdateParametersAction):
self.parameters = parameters.set_count_and_flavor_params(
self.role, baremetal_client, compute_client)
return super(UpdateRoleParametersAction, self).run()
class GeneratePasswordsAction(base.TripleOAction):
"""Generates passwords needed for Overcloud deployment
This method generates passwords and ensures they are stored in the
mistral environment associated with a plan. This method respects
previously generated passwords and adds new passwords as necessary.
"""
def __init__(self, container=constants.DEFAULT_CONTAINER_NAME):
self.container = container
def run(self):
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("", msg)
passwords = password_utils.generate_overcloud_passwords()
# 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)
return wf_env.variables['passwords']
+13 -4
View File
@@ -160,11 +160,20 @@ class ProcessTemplatesAction(base.TripleOAction):
temp_files.append(env_temp_file)
env_paths.append(env_temp_file)
# handle user set parameter values
params = mistral_environment.variables.get('parameter_defaults')
if params:
# create a dict to hold all user set params and merge
# them in the appropriate order
merged_params = {}
# merge generated passwords into params first
passwords = mistral_environment.variables.get('passwords', {})
merged_params.update(passwords)
# handle user set parameter values next in case a user has set
# a new value for a password parameter
params = mistral_environment.variables.get(
'parameter_defaults', {})
merged_params.update(params)
if merged_params:
env_temp_file = _create_temp_file(
{'parameter_defaults': params})
{'parameter_defaults': merged_params})
temp_files.append(env_temp_file)
env_paths.append(env_temp_file)
+35
View File
@@ -50,3 +50,38 @@ TRIPLEO_META_USAGE_KEY = 'x-container-meta-usage-tripleo'
# OBJECT_META_KEY_PREFIX is used to prefix Swift metadata keys per object
# in SwiftPlanStorageBackend
OBJECT_META_KEY_PREFIX = 'x-object-meta-'
#: List of names of parameters that contain passwords
PASSWORD_PARAMETER_NAMES = (
'AdminPassword',
'AdminToken',
'AodhPassword',
'BarbicanPassword',
'CeilometerPassword',
'CeilometerMeteringSecret',
'CinderPassword',
'GlancePassword',
'GnocchiPassword',
'HAProxyStatsPassword',
'HeatPassword',
'HeatStackDomainAdminPassword',
'IronicPassword',
'MistralPassword',
'MysqlClustercheckPassword',
'NeutronPassword',
'NovaPassword',
'RabbitPassword',
'RedisPassword',
'SaharaPassword',
'SwiftHashSuffix',
'SwiftPassword',
'SnmpdReadonlyUserPassword',
'TrovePassword',
'ZaqarPassword',
'ManilaPassword',
'NeutronMetadataProxySharedSecret',
'CephMonKey',
'CephAdminKey',
'CephClientKey',
'CephRgwKey',
)
@@ -20,6 +20,40 @@ from tripleo_common.actions import parameters
from tripleo_common import constants
from tripleo_common.tests import base
_EXISTING_PASSWORDS = {
'MistralPassword': 'VFJeqBKbatYhQm9jja67hufft',
'BarbicanPassword': 'MGGQBtgKT7FnywvkcdMwE9nhx',
'AdminPassword': 'jFmY8FTpvtF2e4d4ReXvmUP8k',
'CeilometerMeteringSecret': 'CbHTGK4md4Cc8P8ZyzTns6wry',
'ZaqarPassword': 'bbFgCTFbAH8vf9n3xvZCP8aMR',
'NovaPassword': '7dZATgVPwD7Ergs9kTTDMCr7F',
'IronicPassword': '4hFDgn9ANeVfuqk84pHpD4ksa',
'RedisPassword': 'xjj3QZDcUQmU6Q7NzWBHRUhGd',
'SaharaPassword': 'spFvYGezdFwnTk7NPxgYTbUPh',
'AdminToken': 'jq6G6HyZtj7dcZEvuyhAfjutM',
'CinderPassword': 'dcxC3xyUcrmvzfrrxpAd3REcm',
'GlancePassword': 'VqJYNEdKKsGZtgnHct77XBtrV',
'RabbitPassword': 'ahuHRXdPMx9rzCdjD9CJJNCgA',
'CephAdminKey': b'AQCQXtlXAAAAABAAT4Gk+U8EqqStL+JFa9bp1Q==',
'HAProxyStatsPassword': 'P8tbdK6n4YUkTaUyy8XgEVTe6',
'TrovePassword': 'V7A7zegkMdRFnYuN23gdc4KQC',
'CeilometerPassword': 'RRdpwK6qf2pbKz2UtzxqauAdk',
'GnocchiPassword': 'cRYHcUkMuJeK3vyU9pCaznUZc',
'HeatStackDomainAdminPassword': 'GgTRyWzKYsxK4mReTJ4CM6sMc',
'CephRgwKey': b'AQCQXtlXAAAAABAAUKcqUMu6oMjAXMjoUV4/3A==',
'AodhPassword': '8VZXehsKc2HbmFFMKYuqxTJHn',
'ManilaPassword': 'NYJN86Fua3X8AVFWmMhQa2zTH',
'NeutronMetadataProxySharedSecret': 'Q2YgUCwmBkYdqsdhhCF4hbghu',
'CephMonKey': b'AQCQXtlXAAAAABAA9l+59N3yH+C49Y0JiKeGFg==',
'SwiftHashSuffix': 'td8mV6k7TYEGKCDvjVBwckpn9',
'SnmpdReadonlyUserPassword': 'TestPassword',
'SwiftPassword': 'z6EWAVfW7CuxvKdzjWTdrXCeg',
'HeatPassword': 'bREnsXtMHKTHxt8XW6NXAYr48',
'MysqlClustercheckPassword': 'jN4RMMWWJ4sycaRwh7UvrAtfX',
'CephClientKey': b'AQCQXtlXAAAAABAAKyc+8St8i9onHyu2mPk+vg==',
'NeutronPassword': 'ZxAjdU2UXCV4GM3WyPKrzAZXD'
}
class GetParametersActionTest(base.TestCase):
@@ -126,6 +160,7 @@ class UpdateParametersActionTest(base.TestCase):
test_parameters = {'SomeTestParameter': 42}
action = parameters.UpdateParametersAction(test_parameters)
action.run()
mock_mistral.environments.update.assert_called_once_with(
name=constants.DEFAULT_CONTAINER_NAME,
variables={
@@ -134,3 +169,64 @@ class UpdateParametersActionTest(base.TestCase):
'environments': [{u'path': u'environments/test.yaml'}],
'parameter_defaults': {'SomeTestParameter': 42}}
)
class GeneratePasswordsActionTest(base.TestCase):
@mock.patch('tripleo_common.utils.passwords.'
'get_hiera_key')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_workflow_client')
@mock.patch('mistral.context.ctx')
def test_run(self, mock_ctx, mock_get_workflow_client,
mock_hiera_key):
mock_hiera_key.return_value = "TestPassword"
mock_ctx.return_value = mock.MagicMock()
mock_mistral = mock.MagicMock()
mock_env = mock.MagicMock()
mock_env.name = constants.DEFAULT_CONTAINER_NAME
mock_env.variables = {
'temp_environment': 'temp_environment',
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}],
}
mock_mistral.environments.get.return_value = mock_env
mock_get_workflow_client.return_value = mock_mistral
action = parameters.GeneratePasswordsAction()
result = action.run()
for password_param_name in constants.PASSWORD_PARAMETER_NAMES:
self.assertTrue(password_param_name in result,
"%s is not in %s" % (password_param_name, result))
@mock.patch('tripleo_common.utils.passwords.'
'get_hiera_key')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_workflow_client')
@mock.patch('mistral.context.ctx')
def test_run_passwords_exist(self, mock_ctx, mock_get_workflow_client,
mock_hiera_key):
mock_hiera_key.return_value = "TestPassword"
mock_ctx.return_value = mock.MagicMock()
mock_mistral = mock.MagicMock()
mock_env = mock.MagicMock()
mock_env.name = constants.DEFAULT_CONTAINER_NAME
mock_env.variables = {
'temp_environment': 'temp_environment',
'template': 'template',
'environments': [{u'path': u'environments/test.yaml'}],
'passwords': _EXISTING_PASSWORDS
}
mock_mistral.environments.get.return_value = mock_env
mock_get_workflow_client.return_value = mock_mistral
action = parameters.GeneratePasswordsAction()
result = action.run()
# ensure old passwords used and no new generation
self.assertEqual(_EXISTING_PASSWORDS, result)
@@ -0,0 +1,36 @@
# Copyright 2016 Red Hat, Inc.
# 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.
import mock
from tripleo_common.tests import base
from tripleo_common.utils import passwords as password_utils
class TestPasswords(base.TestCase):
def test_create_cephx_key(self):
key = password_utils.create_cephx_key()
self.assertEqual(len(key), 40)
@mock.patch("subprocess.Popen")
def test_get_hiera_key(self, mock_popen):
process_mock = mock.Mock()
process_mock.communicate.return_value = ["pa$$word", ""]
mock_popen.return_value = process_mock
value = password_utils.get_hiera_key('password_name')
self.assertEqual(value, "pa$$word")
+75
View File
@@ -0,0 +1,75 @@
# Copyright 2016 Red Hat, Inc.
# 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.
import base64
import logging
import os
import struct
import subprocess
import time
import passlib.utils as passutils
from tripleo_common import constants
_MIN_PASSWORD_SIZE = 25
LOG = logging.getLogger(__name__)
def generate_overcloud_passwords():
"""Create the passwords needed for the overcloud
This will create the set of passwords required by the overcloud, store
them in the output file path and return a dictionary of passwords. If the
file already exists the existing passwords will be returned instead,
"""
passwords = {}
for name in constants.PASSWORD_PARAMETER_NAMES:
# CephX keys aren't random strings
if name.startswith("Ceph"):
passwords[name] = create_cephx_key()
elif name == 'SnmpdReadonlyUserPassword':
snmp_password = get_hiera_key(
'snmpd_readonly_user_password')
passwords[name] = snmp_password
if not snmp_password:
LOG.warning("Undercloud ceilometer SNMPd password "
"missing!")
else:
passwords[name] = passutils.generate_password(
size=_MIN_PASSWORD_SIZE)
return passwords
def create_cephx_key():
# NOTE(gfidente): Taken from
# https://github.com/ceph/ceph-deploy/blob/master/ceph_deploy/new.py#L21
key = os.urandom(16)
header = struct.pack("<hiih", 1, int(time.time()), 0, len(key))
return base64.b64encode(header + key)
def get_hiera_key(key_name):
"""Retrieve a key from the hiera store
:param password_name: Name of the key to retrieve
:type password_name: type
"""
command = ["hiera", key_name]
p = subprocess.Popen(command, stdout=subprocess.PIPE)
out, err = p.communicate()
return out
+1
View File
@@ -104,6 +104,7 @@ workflows:
- queue_name: tripleo
tasks:
deploy:
action: tripleo.deployment.deploy timeout=<% $.timeout %> container=<% $.container %>
on-success: test_validations_enabled
+96 -3
View File
@@ -12,9 +12,14 @@ workflows:
tasks:
create_plan:
action: tripleo.plan.create container=<% $.container %>
on-success: process_templates
on-success: ensure_passwords_exist
on-error: create_plan_set_status_failed
ensure_passwords_exist:
action: tripleo.parameters.generate_passwords container=<% $.container %>
on-success: process_templates
on-error: ensure_passwords_exist_set_status_failed
process_templates:
action: tripleo.templates.process container=<% $.container %>
on-success: set_status_success
@@ -32,6 +37,12 @@ workflows:
status: FAILED
message: <% task(create_plan).result %>
ensure_passwords_exist_set_status_failed:
on-success: notify_zaqar
publish:
status: FAILED
message: <% task(ensure_passwords_exist).result %>
process_templates_set_status_failed:
on-success: notify_zaqar
publish:
@@ -57,7 +68,7 @@ workflows:
tasks:
update_plan:
action: tripleo.plan.update container=<% $.container %>
on-success: process_templates
on-success: ensure_passwords_exist
on-error: update_plan_set_status_failed
process_templates:
@@ -65,6 +76,11 @@ workflows:
on-success: set_status_success
on-error: process_templates_set_status_failed
ensure_passwords_exist:
action: tripleo.parameters.generate_passwords container=<% $.container %>
on-success: process_templates
on-error: ensure_passwords_exist_set_status_failed
set_status_success:
on-success: notify_zaqar
publish:
@@ -77,12 +93,19 @@ workflows:
status: FAILED
message: <% task(update_plan).result %>
process_templates_set_status_failed:
on-success: notify_zaqar
publish:
status: FAILED
message: <% task(process_templates).result %>
ensure_passwords_exist_set_status_failed:
on-success: notify_zaqar
publish:
status: FAILED
message: <% task(ensure_passwords_exist).result %>
notify_zaqar:
action: zaqar.queue_post
input:
@@ -129,9 +152,14 @@ workflows:
create_plan:
action: tripleo.plan.create container=<% $.container %>
on-success: plan_process_templates
on-success: ensure_passwords_exist
on-error: plan_set_status_failed
ensure_passwords_exist:
action: tripleo.parameters.generate_passwords container=<% $.container %>
on-success: plan_process_templates
on-error: ensure_passwords_exist_set_status_failed
plan_process_templates:
action: tripleo.templates.process container=<% $.container %>
on-success: plan_set_status_success
@@ -149,6 +177,12 @@ workflows:
status: FAILED
message: <% task(create_plan).result %>
ensure_passwords_exist_set_status_failed:
on-success: notify_zaqar
publish:
status: FAILED
message: <% task(ensure_passwords_exist).result %>
process_templates_set_status_failed:
on-success: notify_zaqar
publish:
@@ -178,3 +212,62 @@ workflows:
status: <% $.status %>
message: <% $.message or '' %>
execution: <% execution() %>
get_passwords:
description: Retrieves passwords for a given plan
input:
- container
- queue_name: tripleo
tasks:
verify_container_exists:
action: swift.head_container container=<% $.container %>
on-success: verify_environment_exists
on-error: verify_container_set_status_failed
verify_environment_exists:
action: mistral.environments_get name=<% $.container %>
on-success: get_or_generate_passwords
on-error: verify_environment_set_status_failed
get_or_generate_passwords:
action: tripleo.parameters.generate_passwords container=<% $.container %>
on-success: get_passwords_set_status_success
on-error: get_passwords_set_status_failed
get_passwords_set_status_success:
on-success: notify_zaqar
publish:
status: SUCCESS
message: <% task(get_or_generate_passwords).result %>
get_passwords_set_status_failed:
on-success: notify_zaqar
publish:
status: FAILED
message: <% task(get_or_generate_passwords).result %>
verify_container_set_status_failed:
on-success: notify_zaqar
publish:
status: FAILED
message: <% task(verify_container_exists).result %>
verify_environment_set_status_failed:
on-success: notify_zaqar
publish:
status: FAILED
message: <% task(verify_environment_exists).result %>
notify_zaqar:
action: zaqar.queue_post
input:
queue_name: <% $.queue_name %>
messages:
body:
type: tripleo.plan_management.v1.get_passwords
payload:
status: <% $.status %>
message: <% $.message or '' %>
execution: <% execution() %>