Skip to content

Commit

Permalink
Add support for admin_password to LibVirt
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lhrc-mikeyp committed Feb 17, 2012
1 parent 55bc3d9 commit 1463839
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 8 deletions.
119 changes: 116 additions & 3 deletions nova/virt/disk/api.py
Expand Up @@ -25,8 +25,10 @@
"""

import crypt
import json
import os
import random
import tempfile

from nova import exception
Expand Down Expand Up @@ -207,7 +209,8 @@ def umount(self):

# 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.
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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()
18 changes: 15 additions & 3 deletions nova/virt/libvirt/connection.py
Expand Up @@ -100,6 +100,10 @@
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'),
Expand Down Expand Up @@ -1155,7 +1159,14 @@ def basepath(fname='', suffix=suffix):
'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.
Expand All @@ -1165,12 +1176,13 @@ def basepath(fname='', suffix=suffix):
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)

Expand Down
7 changes: 5 additions & 2 deletions nova/virt/xenapi/vm_utils.py
Expand Up @@ -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:
Expand Down

0 comments on commit 1463839

Please sign in to comment.