summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Pittaro <mikeyp@LaHondaResearch.org>2012-02-10 18:42:38 -0800
committerMike Pittaro <mikeyp@LaHondaResearch.org>2012-02-17 15:10:42 -0800
commit1463839ff37c1baa7736a88cdd573dee802b29f0 (patch)
tree82e04a780a1528eb0ecda0418b44fbd7c6b3d0ae
parent55bc3d927739f98a002c4590b196aa6780fa8fbf (diff)
Add support for admin_password to LibVirt
If the config flag --libvirt_inject_password is set, Libvirt now makes an attempt to inject the admin_password to instances at startup time. Fixes bug 767202 Change-Id: I1491c84825bf0bbad43a7d53b379271caa2b76f6
-rw-r--r--nova/virt/disk/api.py119
-rw-r--r--nova/virt/libvirt/connection.py18
-rw-r--r--nova/virt/xenapi/vm_utils.py7
3 files changed, 136 insertions, 8 deletions
diff --git a/nova/virt/disk/api.py b/nova/virt/disk/api.py
index 060f64e1c..51e8f1907 100644
--- a/nova/virt/disk/api.py
+++ b/nova/virt/disk/api.py
@@ -25,8 +25,10 @@ Includes injection of SSH PGP keys into authorized_keys file.
"""
+import crypt
import json
import os
+import random
import tempfile
from nova import exception
@@ -207,7 +209,8 @@ class _DiskImage(object):
# Public module functions
-def inject_data(image, key=None, net=None, metadata=None,
+def inject_data(image,
+ key=None, net=None, metadata=None, admin_password=None,
partition=None, use_cow=False):
"""Injects a ssh key and optionally net data into a disk image.
@@ -220,7 +223,8 @@ def inject_data(image, key=None, net=None, metadata=None,
img = _DiskImage(image=image, partition=partition, use_cow=use_cow)
if img.mount():
try:
- inject_data_into_fs(img.mount_dir, key, net, metadata,
+ inject_data_into_fs(img.mount_dir,
+ key, net, metadata, admin_password,
utils.execute)
finally:
img.umount()
@@ -274,7 +278,7 @@ def destroy_container(img):
LOG.exception(_('Failed to remove container: %s'), exn)
-def inject_data_into_fs(fs, key, net, metadata, execute):
+def inject_data_into_fs(fs, key, net, metadata, admin_password, execute):
"""Injects data into a filesystem already mounted by the caller.
Virt connections can call this directly if they mount their fs
in a different way to inject_data
@@ -285,6 +289,8 @@ def inject_data_into_fs(fs, key, net, metadata, execute):
_inject_net_into_fs(net, fs, execute=execute)
if metadata:
_inject_metadata_into_fs(metadata, fs, execute=execute)
+ if admin_password:
+ _inject_admin_password_into_fs(admin_password, fs, execute=execute)
def _inject_file_into_fs(fs, path, contents):
@@ -336,3 +342,110 @@ def _inject_net_into_fs(net, fs, execute=None):
utils.execute('chmod', 755, netdir, run_as_root=True)
netfile = os.path.join(netdir, 'interfaces')
utils.execute('tee', netfile, process_input=net, run_as_root=True)
+
+
+def _inject_admin_password_into_fs(admin_passwd, fs, execute=None):
+ """Set the root password to admin_passwd
+
+ admin_password is a root password
+ fs is the path to the base of the filesystem into which to inject
+ the key.
+
+ This method modifies the instance filesystem directly,
+ and does not require a guest agent running in the instance.
+
+ """
+ # The approach used here is to copy the password and shadow
+ # files from the instance filesystem to local files, make any
+ # necessary changes, and then copy them back.
+
+ admin_user = 'root'
+
+ fd, tmp_passwd = tempfile.mkstemp()
+ os.close(fd)
+ fd, tmp_shadow = tempfile.mkstemp()
+ os.close(fd)
+
+ utils.execute('cp', os.path.join(fs, 'etc', 'passwd'), tmp_passwd,
+ run_as_root=True)
+ utils.execute('cp', os.path.join(fs, 'etc', 'shadow'), tmp_shadow,
+ run_as_root=True)
+ _set_passwd(admin_user, admin_passwd, tmp_passwd, tmp_shadow)
+ utils.execute('cp', tmp_passwd, os.path.join(fs, 'etc', 'passwd'),
+ run_as_root=True)
+ utils.execute('rm', tmp_passwd, run_as_root=True)
+ utils.execute('cp', tmp_shadow, os.path.join(fs, 'etc', 'shadow'),
+ run_as_root=True)
+ utils.execute('rm', tmp_shadow, run_as_root=True)
+
+
+def _set_passwd(username, admin_passwd, passwd_file, shadow_file):
+ """set the password for username to admin_passwd
+
+ The passwd_file is not modified. The shadow_file is updated.
+ if the username is not found in both files, an exception is raised.
+
+ :param username: the username
+ :param encrypted_passwd: the encrypted password
+ :param passwd_file: path to the passwd file
+ :param shadow_file: path to the shadow password file
+ :returns: nothing
+ :raises: exception.Error(), IOError()
+
+ """
+ salt_set = ('abcdefghijklmnopqrstuvwxyz'
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ '0123456789./')
+ # encryption algo - id pairs for crypt()
+ algos = {'SHA-512': '$6$', 'SHA-256': '$5$', 'MD5': '$1$', 'DES': '' }
+
+ salt = 16 * ' '
+ salt = ''.join([random.choice(salt_set) for c in salt])
+
+ # crypt() depends on the underlying libc, and may not support all
+ # forms of hash. We try md5 first. If we get only 13 characters back,
+ # then the underlying crypt() didn't understand the '$n$salt' magic,
+ # so we fall back to DES.
+ # md5 is the default because it's widely supported. Although the
+ # local crypt() might support stronger SHA, the target instance
+ # might not.
+ encrypted_passwd = crypt.crypt(admin_passwd, algos['MD5'] + salt)
+ if len(encrypted_passwd) == 13:
+ encrypted_passwd = crypt.crypt(admin_passwd, algos['DES'] + salt)
+
+ try:
+ p_file = open(passwd_file, 'rb')
+ s_file = open(shadow_file, 'rb')
+
+ # username MUST exist in passwd file or it's an error
+ found = False
+ for entry in p_file:
+ split_entry = entry.split(':')
+ if split_entry[0] == username:
+ found = True
+ break
+ if not found:
+ msg = _('User %(username)s not found in password file.')
+ raise exception.Error(msg % username)
+
+ # update password in the shadow file.It's an error if the
+ # the user doesn't exist.
+ new_shadow = list()
+ found = False
+ for entry in s_file:
+ split_entry = entry.split(':')
+ if split_entry[0] == username:
+ split_entry[1] = encrypted_passwd
+ found = True
+ new_entry = ':'.join(split_entry)
+ new_shadow.append(new_entry)
+ s_file.close()
+ if not found:
+ msg = _('User %(username)s not found in shadow file.')
+ raise exception.Error(msg % username)
+ s_file = open(shadow_file, 'wb')
+ for entry in new_shadow:
+ s_file.write(entry)
+ finally:
+ p_file.close()
+ s_file.close()
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index b89b67372..b1cd27c10 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -100,6 +100,10 @@ libvirt_opts = [
default='',
help='Override the default libvirt URI '
'(which is dependent on libvirt_type)'),
+ cfg.BoolOpt('libvirt_inject_password',
+ default=False,
+ help='Inject the admin password at boot time, '
+ 'without an agent.'),
cfg.BoolOpt('use_usb_tablet',
default=True,
help='Sync virtual and real mouse cursors in Windows VMs'),
@@ -1155,7 +1159,14 @@ class LibvirtConnection(driver.ComputeDriver):
'use_ipv6': FLAGS.use_ipv6}]))
metadata = instance.get('metadata')
- if any((key, net, metadata)):
+
+ if FLAGS.libvirt_inject_password:
+ admin_password = instance.get('admin_pass')
+ else:
+ admin_password = None
+
+ if any((key, net, metadata, admin_password)):
+
instance_name = instance['name']
if config_drive: # Should be True or None by now.
@@ -1165,12 +1176,13 @@ class LibvirtConnection(driver.ComputeDriver):
injection_path = basepath('disk')
img_id = instance.image_ref
- for injection in ('metadata', 'key', 'net'):
+ for injection in ('metadata', 'key', 'net', 'admin_password'):
if locals()[injection]:
LOG.info(_('Injecting %(injection)s into image %(img_id)s'
% locals()), instance=instance)
try:
- disk.inject_data(injection_path, key, net, metadata,
+ disk.inject_data(injection_path,
+ key, net, metadata, admin_password,
partition=target_partition,
use_cow=FLAGS.use_cow_images)
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index dc9340e6b..0451c82b9 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -1704,8 +1704,11 @@ def _mounted_processing(device, key, net, metadata):
if not _find_guest_agent(tmpdir, FLAGS.xenapi_agent_path):
LOG.info(_('Manipulating interface files '
'directly'))
- disk.inject_data_into_fs(tmpdir, key, net, metadata,
- utils.execute)
+ # for xenapi, we don't 'inject' admin_password here,
+ # it's handled at instance startup time
+ disk.inject_data_into_fs(tmpdir,
+ key, net, None, metadata,
+ utils.execute)
finally:
utils.execute('umount', dev_path, run_as_root=True)
else: