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
This commit is contained in:
parent
4a84166ca7
commit
5f136811d6
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Add MigrationSshKey to generated passwords. This ssh key-pair is used by
|
||||
nova cold-migration and libvirt live-migration unless TLS is enabled.
|
@ -18,3 +18,4 @@ Jinja2!=2.9.0,!=2.9.1,!=2.9.2,!=2.9.3,!=2.9.4,>=2.8 # BSD License (3 clause)
|
||||
python-novaclient>=7.1.0 # Apache-2.0
|
||||
passlib>=1.7.0 # BSD
|
||||
netifaces>=0.10.4 # MIT
|
||||
paramiko>=2.0 # LGPLv2.1+
|
||||
|
@ -12,16 +12,13 @@
|
||||
# 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 os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from mistral.workflow import utils as mistral_workflow_utils
|
||||
from mistralclient.api import base as mistralclient_api
|
||||
from oslo_concurrency.processutils import ProcessExecutionError
|
||||
|
||||
from tripleo_common.actions import base
|
||||
from tripleo_common import constants
|
||||
from tripleo_common.utils import passwords as password_utils
|
||||
from tripleo_common.utils import validations as utils
|
||||
|
||||
|
||||
@ -33,25 +30,13 @@ class GetPubkeyAction(base.TripleOAction):
|
||||
env = mc.environments.get('ssh_keys')
|
||||
public_key = env.variables['public_key']
|
||||
except Exception:
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
private_key_path = os.path.join(tmp_dir, 'id_rsa')
|
||||
public_key_path = private_key_path + '.pub'
|
||||
utils.create_ssh_keypair(private_key_path)
|
||||
|
||||
with open(private_key_path, 'r') as f:
|
||||
private_key = f.read().strip()
|
||||
with open(public_key_path, 'r') as f:
|
||||
public_key = f.read().strip()
|
||||
|
||||
shutil.rmtree(tmp_dir)
|
||||
ssh_key = password_utils.create_ssh_keypair()
|
||||
public_key = ssh_key['public_key']
|
||||
|
||||
workflow_env = {
|
||||
'name': 'ssh_keys',
|
||||
'description': 'SSH keys for TripleO validations',
|
||||
'variables': {
|
||||
'public_key': public_key,
|
||||
'private_key': private_key,
|
||||
}
|
||||
'variables': ssh_key
|
||||
}
|
||||
mc.environments.create(**workflow_env)
|
||||
|
||||
|
@ -87,6 +87,7 @@ PASSWORD_PARAMETER_NAMES = (
|
||||
'NeutronMetadataProxySharedSecret',
|
||||
'NeutronPassword',
|
||||
'NovaPassword',
|
||||
'MigrationSshKey',
|
||||
'OctaviaHeartbeatKey',
|
||||
'OctaviaPassword',
|
||||
'PacemakerRemoteAuthkey',
|
||||
|
@ -128,6 +128,10 @@ _EXISTING_PASSWORDS = {
|
||||
'QttkuxyeQTgHupKNaZF6y7rDyf7mbNR9DaPXpBQuZ7un6KDj2Dfh7yvfhPk8cHG7n9pb'
|
||||
'KEKD3sgbbKnQ8d9MsGhUtCQVed7dtjpYKsmGJmbYMvZjpGpqsfsHQfFRdCgJHnW3FdQ6'
|
||||
'sGhUtCQVed7dtj12',
|
||||
'MigrationSshKey': {
|
||||
'private_key': 'private_key',
|
||||
'public_key': 'public_key'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -323,6 +327,8 @@ class GeneratePasswordsActionTest(base.TestCase):
|
||||
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.'
|
||||
'get_orchestration_client')
|
||||
@mock.patch('tripleo_common.utils.passwords.'
|
||||
'create_ssh_keypair')
|
||||
@mock.patch('tripleo_common.utils.passwords.'
|
||||
'get_snmpd_readonly_user_password')
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.'
|
||||
@ -330,9 +336,12 @@ class GeneratePasswordsActionTest(base.TestCase):
|
||||
@mock.patch('mistral.context.ctx')
|
||||
def test_run_passwords_exist(self, mock_ctx, mock_get_workflow_client,
|
||||
mock_get_snmpd_readonly_user_password,
|
||||
mock_create_ssh_keypair,
|
||||
mock_get_orchestration_client):
|
||||
|
||||
mock_get_snmpd_readonly_user_password.return_value = "TestPassword"
|
||||
mock_create_ssh_keypair.return_value = {'public_key': 'Foo',
|
||||
'private_key': 'Bar'}
|
||||
|
||||
mock_ctx.return_value = mock.MagicMock()
|
||||
mock_mistral = mock.MagicMock()
|
||||
@ -361,6 +370,8 @@ class GeneratePasswordsActionTest(base.TestCase):
|
||||
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.'
|
||||
'get_orchestration_client')
|
||||
@mock.patch('tripleo_common.utils.passwords.'
|
||||
'create_ssh_keypair')
|
||||
@mock.patch('tripleo_common.utils.passwords.'
|
||||
'get_snmpd_readonly_user_password')
|
||||
@mock.patch('tripleo_common.actions.base.TripleOAction.'
|
||||
@ -368,9 +379,12 @@ class GeneratePasswordsActionTest(base.TestCase):
|
||||
@mock.patch('mistral.context.ctx')
|
||||
def test_passwords_exist_in_heat(self, mock_ctx, mock_get_workflow_client,
|
||||
mock_get_snmpd_readonly_user_password,
|
||||
mock_create_ssh_keypair,
|
||||
mock_get_orchestration_client):
|
||||
|
||||
mock_get_snmpd_readonly_user_password.return_value = "TestPassword"
|
||||
mock_create_ssh_keypair.return_value = {'public_key': 'Foo',
|
||||
'private_key': 'Bar'}
|
||||
|
||||
existing_passwords = _EXISTING_PASSWORDS.copy()
|
||||
existing_passwords.pop("AdminPassword")
|
||||
|
@ -40,26 +40,19 @@ class GetPubkeyActionTest(base.TestCase):
|
||||
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.base.TripleOAction.get_workflow_client')
|
||||
@mock.patch('tripleo_common.utils.validations.create_ssh_keypair')
|
||||
@mock.patch('tempfile.mkdtemp')
|
||||
@mock.patch('shutil.rmtree')
|
||||
def test_run_no_pubkey(self, mock_rmtree, mock_mkdtemp,
|
||||
mock_create_keypair, get_workflow_client_mock):
|
||||
@mock.patch('tripleo_common.utils.passwords.create_ssh_keypair')
|
||||
def test_run_no_pubkey(self, mock_create_keypair,
|
||||
get_workflow_client_mock):
|
||||
mistral = mock.MagicMock()
|
||||
get_workflow_client_mock.return_value = mistral
|
||||
mistral.environments.get.side_effect = 'nope, sorry'
|
||||
mock_mkdtemp.return_value = '/tmp_path'
|
||||
mock_create_keypair.return_value = {
|
||||
'public_key': 'public_key',
|
||||
'private_key': 'private_key',
|
||||
}
|
||||
|
||||
mock_open_context = mock.mock_open()
|
||||
mock_open_context().read.side_effect = ['private_key', 'public_key']
|
||||
|
||||
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||
action = validations.GetPubkeyAction()
|
||||
self.assertEqual('public_key', action.run())
|
||||
|
||||
mock_mkdtemp.assert_called_once()
|
||||
mock_create_keypair.assert_called_once_with('/tmp_path/id_rsa')
|
||||
mock_rmtree.asser_called_once_with('/tmp_path')
|
||||
action = validations.GetPubkeyAction()
|
||||
self.assertEqual('public_key', action.run())
|
||||
|
||||
|
||||
class Enabled(base.TestCase):
|
||||
|
@ -69,3 +69,9 @@ class TestPasswords(base.TestCase):
|
||||
|
||||
self.assertNotEqual(value['KeystoneCredential0'],
|
||||
value['KeystoneCredential1'])
|
||||
|
||||
def test_create_ssh_keypair(self):
|
||||
|
||||
value = password_utils.create_ssh_keypair(comment="Foo")
|
||||
self.assertEqual('ssh-rsa', value['public_key'][:7])
|
||||
self.assertEqual('Foo', value['public_key'][-3:])
|
||||
|
@ -89,13 +89,6 @@ VALIDATION_GROUPS_1_2_PARSED = {
|
||||
|
||||
class ValidationsKeyTest(base.TestCase):
|
||||
|
||||
@mock.patch("oslo_concurrency.processutils.execute")
|
||||
def test_create_ssh_keypair(self, mock_execute):
|
||||
validations.create_ssh_keypair('/path/to/key')
|
||||
mock_execute.assert_called_once_with(
|
||||
'/usr/bin/ssh-keygen', '-t', 'rsa', '-N', '',
|
||||
'-f', '/path/to/key', '-C', 'tripleo-validations')
|
||||
|
||||
@mock.patch("oslo_concurrency.processutils.execute")
|
||||
@mock.patch('tempfile.mkstemp')
|
||||
def test_write_identity_file(self, mock_mkstemp, mock_execute):
|
||||
|
@ -15,6 +15,7 @@
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
import paramiko
|
||||
import struct
|
||||
import time
|
||||
import uuid
|
||||
@ -42,7 +43,6 @@ def generate_passwords(mistralclient=None, stack_env=None):
|
||||
passwords = {}
|
||||
|
||||
for name in constants.PASSWORD_PARAMETER_NAMES:
|
||||
|
||||
# Support users upgrading from Mitaka or otherwise creating a plan for
|
||||
# a Heat stack that already exists.
|
||||
if stack_env and name in stack_env.get('parameter_defaults', {}):
|
||||
@ -68,6 +68,8 @@ def generate_passwords(mistralclient=None, stack_env=None):
|
||||
elif name in ('KeystoneCredential0', 'KeystoneCredential1',
|
||||
'KeystoneFernetKey0', 'KeystoneFernetKey1'):
|
||||
passwords[name] = create_keystone_credential()
|
||||
elif name == 'MigrationSshKey':
|
||||
passwords[name] = create_ssh_keypair()
|
||||
else:
|
||||
passwords[name] = passutils.generate_password(
|
||||
size=_MIN_PASSWORD_SIZE)
|
||||
@ -93,3 +95,18 @@ def get_snmpd_readonly_user_password(mistralclient):
|
||||
|
||||
def create_keystone_credential():
|
||||
return base64.urlsafe_b64encode(os.urandom(32))
|
||||
|
||||
|
||||
def create_ssh_keypair(comment=None, bits=2048):
|
||||
"""Generate an ssh keypair for use on the overcloud"""
|
||||
if comment is None:
|
||||
comment = "Generated by TripleO"
|
||||
key = paramiko.RSAKey.generate(bits)
|
||||
keyout = six.StringIO()
|
||||
key.write_private_key(keyout)
|
||||
private_key = keyout.getvalue()
|
||||
public_key = '{} {} {}'.format(key.get_name(), key.get_base64(), comment)
|
||||
return {
|
||||
'private_key': private_key,
|
||||
'public_key': public_key,
|
||||
}
|
||||
|
@ -93,13 +93,6 @@ def run_validation(validation, identity_file, plan):
|
||||
)
|
||||
|
||||
|
||||
def create_ssh_keypair(key_path):
|
||||
"""Create SSH keypair"""
|
||||
LOG.debug('Creating SSH keypair at %s', key_path)
|
||||
processutils.execute('/usr/bin/ssh-keygen', '-t', 'rsa', '-N', '',
|
||||
'-f', key_path, '-C', 'tripleo-validations')
|
||||
|
||||
|
||||
def write_identity_file(key):
|
||||
"""Write the SSH private key to disk"""
|
||||
fd, path = tempfile.mkstemp(prefix='validations_identity_')
|
||||
@ -112,7 +105,7 @@ def write_identity_file(key):
|
||||
|
||||
|
||||
def cleanup_identity_file(path):
|
||||
"""Write the SSH private key to disk"""
|
||||
"""Remove the SSH private key from disk"""
|
||||
LOG.debug('Cleaning up identity file at %s', path)
|
||||
processutils.execute('/usr/bin/sudo', '/usr/bin/rm', '-f', path)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user