summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormelanie witt <melwittt@gmail.com>2018-02-09 19:43:30 +0000
committerMatt Riedemann <mriedem.os@gmail.com>2018-03-07 14:44:37 +0000
commit6ed7fc11459eb5a69088630e5ea4d12e441f80f2 (patch)
treeb3ec62c5f5ba318a55e2a77ec78cac65cf9168c2
parentd8bc8442b6b938d30b4a95b3731ee99c409686f0 (diff)
downloadnova-6ed7fc11459eb5a69088630e5ea4d12e441f80f2.tar.gz
Save admin password to sysmeta in libvirt driver
We have an API for setting the admin password for an already created instance and we have a metadata API for retrieving the encrypted password. In the libvirt driver, when a request to set the admin password is received, it is indeed set in the guest but the instance system metadata is never updated with the encrypted password, so attempts to retrieve the password via the metadata service API result in an empty string returned instead of the encrypted password. This has been broken in the libvirt driver since the set admin password password feature was added, as far as I can tell. The xen api driver, however, handles the same thing correctly and this adds similar logic to the libvirt driver to fix the problem. Closes-Bug: #1748544 Change-Id: Icf44c4c94529cb75232abe1f3ecc5a4d3646b0cc (cherry picked from commit 715a3cadb07fb92cc11542cfb5001844122b6f60)
-rw-r--r--nova/tests/unit/virt/libvirt/test_driver.py40
-rw-r--r--nova/virt/libvirt/driver.py18
2 files changed, 56 insertions, 2 deletions
diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 3cd945cbc8..77569bb141 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -1337,6 +1337,39 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_guest.set_user_password.assert_called_once_with("root", "123")
+ @mock.patch('nova.objects.Instance.save')
+ @mock.patch('oslo_serialization.base64.encode_as_text')
+ @mock.patch('nova.api.metadata.password.convert_password')
+ @mock.patch('nova.crypto.ssh_encrypt_text')
+ @mock.patch('nova.utils.get_image_from_system_metadata')
+ @mock.patch.object(host.Host,
+ 'has_min_version', return_value=True)
+ @mock.patch('nova.virt.libvirt.host.Host.get_guest')
+ def test_set_admin_password_saves_sysmeta(self, mock_get_guest,
+ ver, mock_image, mock_encrypt,
+ mock_convert, mock_encode,
+ mock_save):
+ self.flags(virt_type='kvm', group='libvirt')
+ instance = objects.Instance(**self.test_instance)
+ # Password will only be saved in sysmeta if the key_data is present
+ instance.key_data = 'ssh-rsa ABCFEFG'
+ mock_image.return_value = {"properties": {
+ "hw_qemu_guest_agent": "yes"}}
+ mock_guest = mock.Mock(spec=libvirt_guest.Guest)
+ mock_get_guest.return_value = mock_guest
+ mock_convert.return_value = {'password_0': 'converted-password'}
+
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+ drvr.set_admin_password(instance, "123")
+
+ mock_guest.set_user_password.assert_called_once_with("root", "123")
+ mock_encrypt.assert_called_once_with(instance.key_data, '123')
+ mock_encode.assert_called_once_with(mock_encrypt.return_value)
+ mock_convert.assert_called_once_with(None, mock_encode.return_value)
+ self.assertEqual('converted-password',
+ instance.system_metadata['password_0'])
+ mock_save.assert_called_once_with()
+
@mock.patch.object(host.Host,
'has_min_version', return_value=True)
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
@@ -1439,8 +1472,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_get_guest.return_value = mock_guest
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
- self.assertRaises(exception.NovaException,
- drvr.set_admin_password, instance, "123")
+ with mock.patch.object(
+ drvr, '_save_instance_password_if_sshkey_present') as save_p:
+ self.assertRaises(exception.NovaException,
+ drvr.set_admin_password, instance, "123")
+ save_p.assert_not_called()
@mock.patch('nova.utils.get_image_from_system_metadata')
@mock.patch.object(host.Host,
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index c2e0e6a569..e8a0c2e612 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -52,6 +52,7 @@ from os_brick import exception as brick_exception
from os_brick.initiator import connector
from oslo_concurrency import processutils
from oslo_log import log as logging
+from oslo_serialization import base64
from oslo_serialization import jsonutils
from oslo_service import loopingcall
from oslo_utils import encodeutils
@@ -66,6 +67,7 @@ import six
from six.moves import range
from nova.api.metadata import base as instance_metadata
+from nova.api.metadata import password
from nova import block_device
from nova.compute import power_state
from nova.compute import task_states
@@ -74,6 +76,7 @@ import nova.conf
from nova.console import serial as serial_console
from nova.console import type as ctype
from nova import context as nova_context
+from nova import crypto
from nova import exception
from nova.i18n import _
from nova import image
@@ -1976,6 +1979,17 @@ class LibvirtDriver(driver.ComputeDriver):
else:
raise exception.SetAdminPasswdNotSupported()
+ # TODO(melwitt): Combine this with the similar xenapi code at some point.
+ def _save_instance_password_if_sshkey_present(self, instance, new_pass):
+ sshkey = instance.key_data if 'key_data' in instance else None
+ if sshkey and sshkey.startswith("ssh-rsa"):
+ enc = crypto.ssh_encrypt_text(sshkey, new_pass)
+ # NOTE(melwitt): The convert_password method doesn't actually do
+ # anything with the context argument, so we can pass None.
+ instance.system_metadata.update(
+ password.convert_password(None, base64.encode_as_text(enc)))
+ instance.save()
+
def set_admin_password(self, instance, new_pass):
self._can_set_admin_password(instance.image_meta)
@@ -2000,6 +2014,10 @@ class LibvirtDriver(driver.ComputeDriver):
'"%(user)s": [Error Code %(error_code)s] %(ex)s')
% {'user': user, 'error_code': error_code, 'ex': err_msg})
raise exception.InternalError(msg)
+ else:
+ # Save the password in sysmeta so it may be retrieved from the
+ # metadata service.
+ self._save_instance_password_if_sshkey_present(instance, new_pass)
def _can_quiesce(self, instance, image_meta):
if CONF.libvirt.virt_type not in ('kvm', 'qemu'):