summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOliver Walsh <owalsh@redhat.com>2017-03-23 17:31:51 +0000
committerOliver Walsh <owalsh@redhat.com>2017-05-19 21:41:12 +0100
commitc49755c72bf81999c6a6f9f13bf86874c705a204 (patch)
treef627d6cd0be43a2c1476b305dc405336a8f6f4b8
parent570c5acc9e7bebeaa489821b6bb21568fb005575 (diff)
Add MigrationSshKey to generated passwords
This reuses the existing password generation mistral action to generate an ssh keypair to be used for nova cold migration Paramiko is used to generate the ssh key, based on the existing approach in the nova keypair api. Also update validation ssh key generation to reuse the same method. Change-Id: I9e7a1862911312ad942233ac8fc828f4e1be1dcf (cherry picked from commit 5f136811d69432aff85a8356b14bd2153ec7f76e)
Notes
Notes (review): Code-Review+1: Alex Schultz <aschultz@redhat.com> Code-Review+2: Ben Nemec <openstack@nemebean.com> Workflow+1: Emilien Macchi <emilien@redhat.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Wed, 07 Jun 2017 18:57:14 +0000 Reviewed-on: https://review.openstack.org/466402 Project: openstack/tripleo-common Branch: refs/heads/stable/newton
-rw-r--r--releasenotes/notes/migration_ssh_key-6e772d18d4d24485.yaml4
-rw-r--r--requirements.txt1
-rw-r--r--tripleo_common/actions/validations.py23
-rw-r--r--tripleo_common/constants.py1
-rw-r--r--tripleo_common/tests/actions/test_parameters.py16
-rw-r--r--tripleo_common/tests/actions/test_validations.py25
-rw-r--r--tripleo_common/tests/utils/test_passwords.py6
-rw-r--r--tripleo_common/tests/utils/test_validations.py7
-rw-r--r--tripleo_common/utils/passwords.py19
-rw-r--r--tripleo_common/utils/validations.py9
10 files changed, 59 insertions, 52 deletions
diff --git a/releasenotes/notes/migration_ssh_key-6e772d18d4d24485.yaml b/releasenotes/notes/migration_ssh_key-6e772d18d4d24485.yaml
new file mode 100644
index 0000000..54c09ee
--- /dev/null
+++ b/releasenotes/notes/migration_ssh_key-6e772d18d4d24485.yaml
@@ -0,0 +1,4 @@
1---
2features:
3 - Add MigrationSshKey to generated passwords. This ssh key-pair is used by
4 nova cold-migration and libvirt live-migration unless TLS is enabled.
diff --git a/requirements.txt b/requirements.txt
index ae2d440..9899647 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,3 +17,4 @@ python-ironic-inspector-client>=1.5.0 # Apache-2.0
17Jinja2>=2.8 # BSD License (3 clause) 17Jinja2>=2.8 # BSD License (3 clause)
18python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 18python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
19passlib>=1.6 # BSD 19passlib>=1.6 # BSD
20paramiko>=2.0 # LGPLv2.1+
diff --git a/tripleo_common/actions/validations.py b/tripleo_common/actions/validations.py
index cb35cf8..bd97e1f 100644
--- a/tripleo_common/actions/validations.py
+++ b/tripleo_common/actions/validations.py
@@ -12,16 +12,13 @@
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15import os
16import shutil
17import tempfile
18
19from mistral.workflow import utils as mistral_workflow_utils 15from mistral.workflow import utils as mistral_workflow_utils
20from mistralclient.api import base as mistralclient_api 16from mistralclient.api import base as mistralclient_api
21from oslo_concurrency.processutils import ProcessExecutionError 17from oslo_concurrency.processutils import ProcessExecutionError
22 18
23from tripleo_common.actions import base 19from tripleo_common.actions import base
24from tripleo_common import constants 20from tripleo_common import constants
21from tripleo_common.utils import passwords as password_utils
25from tripleo_common.utils import validations as utils 22from tripleo_common.utils import validations as utils
26 23
27 24
@@ -35,25 +32,13 @@ class GetPubkeyAction(base.TripleOAction):
35 env = mc.environments.get('ssh_keys') 32 env = mc.environments.get('ssh_keys')
36 public_key = env.variables['public_key'] 33 public_key = env.variables['public_key']
37 except Exception: 34 except Exception:
38 tmp_dir = tempfile.mkdtemp() 35 ssh_key = password_utils.create_ssh_keypair()
39 private_key_path = os.path.join(tmp_dir, 'id_rsa') 36 public_key = ssh_key['public_key']
40 public_key_path = private_key_path + '.pub'
41 utils.create_ssh_keypair(private_key_path)
42
43 with open(private_key_path, 'r') as f:
44 private_key = f.read().strip()
45 with open(public_key_path, 'r') as f:
46 public_key = f.read().strip()
47
48 shutil.rmtree(tmp_dir)
49 37
50 workflow_env = { 38 workflow_env = {
51 'name': 'ssh_keys', 39 'name': 'ssh_keys',
52 'description': 'SSH keys for TripleO validations', 40 'description': 'SSH keys for TripleO validations',
53 'variables': { 41 'variables': ssh_key
54 'public_key': public_key,
55 'private_key': private_key,
56 }
57 } 42 }
58 mc.environments.create(**workflow_env) 43 mc.environments.create(**workflow_env)
59 44
diff --git a/tripleo_common/constants.py b/tripleo_common/constants.py
index 77acf2d..68341fc 100644
--- a/tripleo_common/constants.py
+++ b/tripleo_common/constants.py
@@ -81,6 +81,7 @@ PASSWORD_PARAMETER_NAMES = (
81 'NeutronMetadataProxySharedSecret', 81 'NeutronMetadataProxySharedSecret',
82 'NeutronPassword', 82 'NeutronPassword',
83 'NovaPassword', 83 'NovaPassword',
84 'MigrationSshKey',
84 'RabbitPassword', 85 'RabbitPassword',
85 'RedisPassword', 86 'RedisPassword',
86 'SaharaPassword', 87 'SaharaPassword',
diff --git a/tripleo_common/tests/actions/test_parameters.py b/tripleo_common/tests/actions/test_parameters.py
index 356ac0d..9e32eae 100644
--- a/tripleo_common/tests/actions/test_parameters.py
+++ b/tripleo_common/tests/actions/test_parameters.py
@@ -51,7 +51,11 @@ _EXISTING_PASSWORDS = {
51 'HeatPassword': 'bREnsXtMHKTHxt8XW6NXAYr48', 51 'HeatPassword': 'bREnsXtMHKTHxt8XW6NXAYr48',
52 'MysqlClustercheckPassword': 'jN4RMMWWJ4sycaRwh7UvrAtfX', 52 'MysqlClustercheckPassword': 'jN4RMMWWJ4sycaRwh7UvrAtfX',
53 'CephClientKey': b'AQCQXtlXAAAAABAAKyc+8St8i9onHyu2mPk+vg==', 53 'CephClientKey': b'AQCQXtlXAAAAABAAKyc+8St8i9onHyu2mPk+vg==',
54 'NeutronPassword': 'ZxAjdU2UXCV4GM3WyPKrzAZXD' 54 'NeutronPassword': 'ZxAjdU2UXCV4GM3WyPKrzAZXD',
55 'MigrationSshKey': {
56 'private_key': 'private_key',
57 'public_key': 'public_key'
58 },
55} 59}
56 60
57 61
@@ -248,15 +252,20 @@ class GeneratePasswordsActionTest(base.TestCase):
248 @mock.patch('tripleo_common.actions.base.TripleOAction.' 252 @mock.patch('tripleo_common.actions.base.TripleOAction.'
249 '_get_orchestration_client') 253 '_get_orchestration_client')
250 @mock.patch('tripleo_common.utils.passwords.' 254 @mock.patch('tripleo_common.utils.passwords.'
255 'create_ssh_keypair')
256 @mock.patch('tripleo_common.utils.passwords.'
251 'get_snmpd_readonly_user_password') 257 'get_snmpd_readonly_user_password')
252 @mock.patch('tripleo_common.actions.base.TripleOAction.' 258 @mock.patch('tripleo_common.actions.base.TripleOAction.'
253 '_get_workflow_client') 259 '_get_workflow_client')
254 @mock.patch('mistral.context.ctx') 260 @mock.patch('mistral.context.ctx')
255 def test_run_passwords_exist(self, mock_ctx, mock_get_workflow_client, 261 def test_run_passwords_exist(self, mock_ctx, mock_get_workflow_client,
256 mock_get_snmpd_readonly_user_password, 262 mock_get_snmpd_readonly_user_password,
263 mock_create_ssh_keypair,
257 mock_get_orchestration_client): 264 mock_get_orchestration_client):
258 265
259 mock_get_snmpd_readonly_user_password.return_value = "TestPassword" 266 mock_get_snmpd_readonly_user_password.return_value = "TestPassword"
267 mock_create_ssh_keypair.return_value = {'public_key': 'Foo',
268 'private_key': 'Bar'}
260 269
261 mock_ctx.return_value = mock.MagicMock() 270 mock_ctx.return_value = mock.MagicMock()
262 mock_mistral = mock.MagicMock() 271 mock_mistral = mock.MagicMock()
@@ -286,15 +295,20 @@ class GeneratePasswordsActionTest(base.TestCase):
286 @mock.patch('tripleo_common.actions.base.TripleOAction.' 295 @mock.patch('tripleo_common.actions.base.TripleOAction.'
287 '_get_orchestration_client') 296 '_get_orchestration_client')
288 @mock.patch('tripleo_common.utils.passwords.' 297 @mock.patch('tripleo_common.utils.passwords.'
298 'create_ssh_keypair')
299 @mock.patch('tripleo_common.utils.passwords.'
289 'get_snmpd_readonly_user_password') 300 'get_snmpd_readonly_user_password')
290 @mock.patch('tripleo_common.actions.base.TripleOAction.' 301 @mock.patch('tripleo_common.actions.base.TripleOAction.'
291 '_get_workflow_client') 302 '_get_workflow_client')
292 @mock.patch('mistral.context.ctx') 303 @mock.patch('mistral.context.ctx')
293 def test_passwords_exist_in_heat(self, mock_ctx, mock_get_workflow_client, 304 def test_passwords_exist_in_heat(self, mock_ctx, mock_get_workflow_client,
294 mock_get_snmpd_readonly_user_password, 305 mock_get_snmpd_readonly_user_password,
306 mock_create_ssh_keypair,
295 mock_get_orchestration_client): 307 mock_get_orchestration_client):
296 308
297 mock_get_snmpd_readonly_user_password.return_value = "TestPassword" 309 mock_get_snmpd_readonly_user_password.return_value = "TestPassword"
310 mock_create_ssh_keypair.return_value = {'public_key': 'Foo',
311 'private_key': 'Bar'}
298 312
299 existing_passwords = _EXISTING_PASSWORDS.copy() 313 existing_passwords = _EXISTING_PASSWORDS.copy()
300 existing_passwords.pop("AdminPassword") 314 existing_passwords.pop("AdminPassword")
diff --git a/tripleo_common/tests/actions/test_validations.py b/tripleo_common/tests/actions/test_validations.py
index 02319fc..9acfa95 100644
--- a/tripleo_common/tests/actions/test_validations.py
+++ b/tripleo_common/tests/actions/test_validations.py
@@ -40,26 +40,19 @@ class GetPubkeyActionTest(base.TestCase):
40 40
41 @mock.patch( 41 @mock.patch(
42 'tripleo_common.actions.base.TripleOAction._get_workflow_client') 42 'tripleo_common.actions.base.TripleOAction._get_workflow_client')
43 @mock.patch('tripleo_common.utils.validations.create_ssh_keypair') 43 @mock.patch('tripleo_common.utils.passwords.create_ssh_keypair')
44 @mock.patch('tempfile.mkdtemp') 44 def test_run_no_pubkey(self, mock_create_keypair,
45 @mock.patch('shutil.rmtree') 45 get_workflow_client_mock):
46 def test_run_no_pubkey(self, mock_rmtree, mock_mkdtemp,
47 mock_create_keypair, get_workflow_client_mock):
48 mistral = mock.MagicMock() 46 mistral = mock.MagicMock()
49 get_workflow_client_mock.return_value = mistral 47 get_workflow_client_mock.return_value = mistral
50 mistral.environments.get.side_effect = 'nope, sorry' 48 mistral.environments.get.side_effect = 'nope, sorry'
51 mock_mkdtemp.return_value = '/tmp_path' 49 mock_create_keypair.return_value = {
50 'public_key': 'public_key',
51 'private_key': 'private_key',
52 }
52 53
53 mock_open_context = mock.mock_open() 54 action = validations.GetPubkeyAction()
54 mock_open_context().read.side_effect = ['private_key', 'public_key'] 55 self.assertEqual('public_key', action.run())
55
56 with mock.patch('six.moves.builtins.open', mock_open_context):
57 action = validations.GetPubkeyAction()
58 self.assertEqual('public_key', action.run())
59
60 mock_mkdtemp.assert_called_once()
61 mock_create_keypair.assert_called_once_with('/tmp_path/id_rsa')
62 mock_rmtree.asser_called_once_with('/tmp_path')
63 56
64 57
65class Enabled(base.TestCase): 58class Enabled(base.TestCase):
diff --git a/tripleo_common/tests/utils/test_passwords.py b/tripleo_common/tests/utils/test_passwords.py
index 47ca417..ca67461 100644
--- a/tripleo_common/tests/utils/test_passwords.py
+++ b/tripleo_common/tests/utils/test_passwords.py
@@ -34,3 +34,9 @@ class TestPasswords(base.TestCase):
34 value = password_utils.get_snmpd_readonly_user_password(mock_mistral) 34 value = password_utils.get_snmpd_readonly_user_password(mock_mistral)
35 35
36 self.assertEqual(value, "78cbc32b858718267c355d4") 36 self.assertEqual(value, "78cbc32b858718267c355d4")
37
38 def test_create_ssh_keypair(self):
39
40 value = password_utils.create_ssh_keypair(comment="Foo")
41 self.assertEqual('ssh-rsa', value['public_key'][:7])
42 self.assertEqual('Foo', value['public_key'][-3:])
diff --git a/tripleo_common/tests/utils/test_validations.py b/tripleo_common/tests/utils/test_validations.py
index 83d775f..0c717bd 100644
--- a/tripleo_common/tests/utils/test_validations.py
+++ b/tripleo_common/tests/utils/test_validations.py
@@ -89,13 +89,6 @@ VALIDATION_GROUPS_1_2_PARSED = {
89class ValidationsKeyTest(base.TestCase): 89class ValidationsKeyTest(base.TestCase):
90 90
91 @mock.patch("oslo_concurrency.processutils.execute") 91 @mock.patch("oslo_concurrency.processutils.execute")
92 def test_create_ssh_keypair(self, mock_execute):
93 validations.create_ssh_keypair('/path/to/key')
94 mock_execute.assert_called_once_with(
95 '/usr/bin/ssh-keygen', '-t', 'rsa', '-N', '',
96 '-f', '/path/to/key', '-C', 'tripleo-validations')
97
98 @mock.patch("oslo_concurrency.processutils.execute")
99 @mock.patch('tempfile.mkstemp') 92 @mock.patch('tempfile.mkstemp')
100 def test_write_identity_file(self, mock_mkstemp, mock_execute): 93 def test_write_identity_file(self, mock_mkstemp, mock_execute):
101 mock_open_context = mock.mock_open() 94 mock_open_context = mock.mock_open()
diff --git a/tripleo_common/utils/passwords.py b/tripleo_common/utils/passwords.py
index 4f9e045..05dce62 100644
--- a/tripleo_common/utils/passwords.py
+++ b/tripleo_common/utils/passwords.py
@@ -15,6 +15,7 @@
15import base64 15import base64
16import logging 16import logging
17import os 17import os
18import paramiko
18import struct 19import struct
19import time 20import time
20import uuid 21import uuid
@@ -39,7 +40,6 @@ def generate_overcloud_passwords(mistralclient, stack_env=None):
39 passwords = {} 40 passwords = {}
40 41
41 for name in constants.PASSWORD_PARAMETER_NAMES: 42 for name in constants.PASSWORD_PARAMETER_NAMES:
42
43 # Support users upgrading from Mitaka or otherwise creating a plan for 43 # Support users upgrading from Mitaka or otherwise creating a plan for
44 # a Heat stack that already exists. 44 # a Heat stack that already exists.
45 if stack_env and name in stack_env.get('parameter_defaults', {}): 45 if stack_env and name in stack_env.get('parameter_defaults', {}):
@@ -56,6 +56,8 @@ def generate_overcloud_passwords(mistralclient, stack_env=None):
56 passwords[name] = get_snmpd_readonly_user_password(mistralclient) 56 passwords[name] = get_snmpd_readonly_user_password(mistralclient)
57 elif name in ('KeystoneCredential0', 'KeystoneCredential1'): 57 elif name in ('KeystoneCredential0', 'KeystoneCredential1'):
58 passwords[name] = create_keystone_credential() 58 passwords[name] = create_keystone_credential()
59 elif name == 'MigrationSshKey':
60 passwords[name] = create_ssh_keypair()
59 else: 61 else:
60 passwords[name] = passutils.generate_password( 62 passwords[name] = passutils.generate_password(
61 size=_MIN_PASSWORD_SIZE) 63 size=_MIN_PASSWORD_SIZE)
@@ -81,3 +83,18 @@ def get_snmpd_readonly_user_password(mistralclient):
81 83
82def create_keystone_credential(): 84def create_keystone_credential():
83 return base64.urlsafe_b64encode(os.urandom(32)) 85 return base64.urlsafe_b64encode(os.urandom(32))
86
87
88def create_ssh_keypair(comment=None, bits=2048):
89 """Generate an ssh keypair for use on the overcloud"""
90 if comment is None:
91 comment = "Generated by TripleO"
92 key = paramiko.RSAKey.generate(bits)
93 keyout = six.StringIO()
94 key.write_private_key(keyout)
95 private_key = keyout.getvalue()
96 public_key = '{} {} {}'.format(key.get_name(), key.get_base64(), comment)
97 return {
98 'private_key': private_key,
99 'public_key': public_key,
100 }
diff --git a/tripleo_common/utils/validations.py b/tripleo_common/utils/validations.py
index fa16f92..391dac7 100644
--- a/tripleo_common/utils/validations.py
+++ b/tripleo_common/utils/validations.py
@@ -92,13 +92,6 @@ def run_validation(validation, identity_file, plan):
92 ) 92 )
93 93
94 94
95def create_ssh_keypair(key_path):
96 """Create SSH keypair"""
97 LOG.debug('Creating SSH keypair at %s', key_path)
98 processutils.execute('/usr/bin/ssh-keygen', '-t', 'rsa', '-N', '',
99 '-f', key_path, '-C', 'tripleo-validations')
100
101
102def write_identity_file(key): 95def write_identity_file(key):
103 """Write the SSH private key to disk""" 96 """Write the SSH private key to disk"""
104 fd, path = tempfile.mkstemp(prefix='validations_identity_') 97 fd, path = tempfile.mkstemp(prefix='validations_identity_')
@@ -111,6 +104,6 @@ def write_identity_file(key):
111 104
112 105
113def cleanup_identity_file(path): 106def cleanup_identity_file(path):
114 """Write the SSH private key to disk""" 107 """Remove the SSH private key from disk"""
115 LOG.debug('Cleaning up identity file at %s', path) 108 LOG.debug('Cleaning up identity file at %s', path)
116 processutils.execute('/usr/bin/sudo', '/usr/bin/rm', '-f', path) 109 processutils.execute('/usr/bin/sudo', '/usr/bin/rm', '-f', path)