Skip to content

Commit 1463839

Browse files
committed
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
1 parent 55bc3d9 commit 1463839

File tree

3 files changed

+136
-8
lines changed

3 files changed

+136
-8
lines changed

nova/virt/disk/api.py

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
2626
"""
2727

28+
import crypt
2829
import json
2930
import os
31+
import random
3032
import tempfile
3133

3234
from nova import exception
@@ -207,7 +209,8 @@ def umount(self):
207209

208210
# Public module functions
209211

210-
def inject_data(image, key=None, net=None, metadata=None,
212+
def inject_data(image,
213+
key=None, net=None, metadata=None, admin_password=None,
211214
partition=None, use_cow=False):
212215
"""Injects a ssh key and optionally net data into a disk image.
213216
@@ -220,7 +223,8 @@ def inject_data(image, key=None, net=None, metadata=None,
220223
img = _DiskImage(image=image, partition=partition, use_cow=use_cow)
221224
if img.mount():
222225
try:
223-
inject_data_into_fs(img.mount_dir, key, net, metadata,
226+
inject_data_into_fs(img.mount_dir,
227+
key, net, metadata, admin_password,
224228
utils.execute)
225229
finally:
226230
img.umount()
@@ -274,7 +278,7 @@ def destroy_container(img):
274278
LOG.exception(_('Failed to remove container: %s'), exn)
275279

276280

277-
def inject_data_into_fs(fs, key, net, metadata, execute):
281+
def inject_data_into_fs(fs, key, net, metadata, admin_password, execute):
278282
"""Injects data into a filesystem already mounted by the caller.
279283
Virt connections can call this directly if they mount their fs
280284
in a different way to inject_data
@@ -285,6 +289,8 @@ def inject_data_into_fs(fs, key, net, metadata, execute):
285289
_inject_net_into_fs(net, fs, execute=execute)
286290
if metadata:
287291
_inject_metadata_into_fs(metadata, fs, execute=execute)
292+
if admin_password:
293+
_inject_admin_password_into_fs(admin_password, fs, execute=execute)
288294

289295

290296
def _inject_file_into_fs(fs, path, contents):
@@ -336,3 +342,110 @@ def _inject_net_into_fs(net, fs, execute=None):
336342
utils.execute('chmod', 755, netdir, run_as_root=True)
337343
netfile = os.path.join(netdir, 'interfaces')
338344
utils.execute('tee', netfile, process_input=net, run_as_root=True)
345+
346+
347+
def _inject_admin_password_into_fs(admin_passwd, fs, execute=None):
348+
"""Set the root password to admin_passwd
349+
350+
admin_password is a root password
351+
fs is the path to the base of the filesystem into which to inject
352+
the key.
353+
354+
This method modifies the instance filesystem directly,
355+
and does not require a guest agent running in the instance.
356+
357+
"""
358+
# The approach used here is to copy the password and shadow
359+
# files from the instance filesystem to local files, make any
360+
# necessary changes, and then copy them back.
361+
362+
admin_user = 'root'
363+
364+
fd, tmp_passwd = tempfile.mkstemp()
365+
os.close(fd)
366+
fd, tmp_shadow = tempfile.mkstemp()
367+
os.close(fd)
368+
369+
utils.execute('cp', os.path.join(fs, 'etc', 'passwd'), tmp_passwd,
370+
run_as_root=True)
371+
utils.execute('cp', os.path.join(fs, 'etc', 'shadow'), tmp_shadow,
372+
run_as_root=True)
373+
_set_passwd(admin_user, admin_passwd, tmp_passwd, tmp_shadow)
374+
utils.execute('cp', tmp_passwd, os.path.join(fs, 'etc', 'passwd'),
375+
run_as_root=True)
376+
utils.execute('rm', tmp_passwd, run_as_root=True)
377+
utils.execute('cp', tmp_shadow, os.path.join(fs, 'etc', 'shadow'),
378+
run_as_root=True)
379+
utils.execute('rm', tmp_shadow, run_as_root=True)
380+
381+
382+
def _set_passwd(username, admin_passwd, passwd_file, shadow_file):
383+
"""set the password for username to admin_passwd
384+
385+
The passwd_file is not modified. The shadow_file is updated.
386+
if the username is not found in both files, an exception is raised.
387+
388+
:param username: the username
389+
:param encrypted_passwd: the encrypted password
390+
:param passwd_file: path to the passwd file
391+
:param shadow_file: path to the shadow password file
392+
:returns: nothing
393+
:raises: exception.Error(), IOError()
394+
395+
"""
396+
salt_set = ('abcdefghijklmnopqrstuvwxyz'
397+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
398+
'0123456789./')
399+
# encryption algo - id pairs for crypt()
400+
algos = {'SHA-512': '$6$', 'SHA-256': '$5$', 'MD5': '$1$', 'DES': '' }
401+
402+
salt = 16 * ' '
403+
salt = ''.join([random.choice(salt_set) for c in salt])
404+
405+
# crypt() depends on the underlying libc, and may not support all
406+
# forms of hash. We try md5 first. If we get only 13 characters back,
407+
# then the underlying crypt() didn't understand the '$n$salt' magic,
408+
# so we fall back to DES.
409+
# md5 is the default because it's widely supported. Although the
410+
# local crypt() might support stronger SHA, the target instance
411+
# might not.
412+
encrypted_passwd = crypt.crypt(admin_passwd, algos['MD5'] + salt)
413+
if len(encrypted_passwd) == 13:
414+
encrypted_passwd = crypt.crypt(admin_passwd, algos['DES'] + salt)
415+
416+
try:
417+
p_file = open(passwd_file, 'rb')
418+
s_file = open(shadow_file, 'rb')
419+
420+
# username MUST exist in passwd file or it's an error
421+
found = False
422+
for entry in p_file:
423+
split_entry = entry.split(':')
424+
if split_entry[0] == username:
425+
found = True
426+
break
427+
if not found:
428+
msg = _('User %(username)s not found in password file.')
429+
raise exception.Error(msg % username)
430+
431+
# update password in the shadow file.It's an error if the
432+
# the user doesn't exist.
433+
new_shadow = list()
434+
found = False
435+
for entry in s_file:
436+
split_entry = entry.split(':')
437+
if split_entry[0] == username:
438+
split_entry[1] = encrypted_passwd
439+
found = True
440+
new_entry = ':'.join(split_entry)
441+
new_shadow.append(new_entry)
442+
s_file.close()
443+
if not found:
444+
msg = _('User %(username)s not found in shadow file.')
445+
raise exception.Error(msg % username)
446+
s_file = open(shadow_file, 'wb')
447+
for entry in new_shadow:
448+
s_file.write(entry)
449+
finally:
450+
p_file.close()
451+
s_file.close()

nova/virt/libvirt/connection.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@
100100
default='',
101101
help='Override the default libvirt URI '
102102
'(which is dependent on libvirt_type)'),
103+
cfg.BoolOpt('libvirt_inject_password',
104+
default=False,
105+
help='Inject the admin password at boot time, '
106+
'without an agent.'),
103107
cfg.BoolOpt('use_usb_tablet',
104108
default=True,
105109
help='Sync virtual and real mouse cursors in Windows VMs'),
@@ -1155,7 +1159,14 @@ def basepath(fname='', suffix=suffix):
11551159
'use_ipv6': FLAGS.use_ipv6}]))
11561160

11571161
metadata = instance.get('metadata')
1158-
if any((key, net, metadata)):
1162+
1163+
if FLAGS.libvirt_inject_password:
1164+
admin_password = instance.get('admin_pass')
1165+
else:
1166+
admin_password = None
1167+
1168+
if any((key, net, metadata, admin_password)):
1169+
11591170
instance_name = instance['name']
11601171

11611172
if config_drive: # Should be True or None by now.
@@ -1165,12 +1176,13 @@ def basepath(fname='', suffix=suffix):
11651176
injection_path = basepath('disk')
11661177
img_id = instance.image_ref
11671178

1168-
for injection in ('metadata', 'key', 'net'):
1179+
for injection in ('metadata', 'key', 'net', 'admin_password'):
11691180
if locals()[injection]:
11701181
LOG.info(_('Injecting %(injection)s into image %(img_id)s'
11711182
% locals()), instance=instance)
11721183
try:
1173-
disk.inject_data(injection_path, key, net, metadata,
1184+
disk.inject_data(injection_path,
1185+
key, net, metadata, admin_password,
11741186
partition=target_partition,
11751187
use_cow=FLAGS.use_cow_images)
11761188

nova/virt/xenapi/vm_utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,8 +1704,11 @@ def _mounted_processing(device, key, net, metadata):
17041704
if not _find_guest_agent(tmpdir, FLAGS.xenapi_agent_path):
17051705
LOG.info(_('Manipulating interface files '
17061706
'directly'))
1707-
disk.inject_data_into_fs(tmpdir, key, net, metadata,
1708-
utils.execute)
1707+
# for xenapi, we don't 'inject' admin_password here,
1708+
# it's handled at instance startup time
1709+
disk.inject_data_into_fs(tmpdir,
1710+
key, net, None, metadata,
1711+
utils.execute)
17091712
finally:
17101713
utils.execute('umount', dev_path, run_as_root=True)
17111714
else:

0 commit comments

Comments
 (0)