Merge "Add MigrationSshKey to generated passwords"
This commit is contained in:
commit
6a17d629f0
|
@ -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…
Reference in New Issue