25
25
26
26
"""
27
27
28
+ import crypt
28
29
import json
29
30
import os
31
+ import random
30
32
import tempfile
31
33
32
34
from nova import exception
@@ -207,7 +209,8 @@ def umount(self):
207
209
208
210
# Public module functions
209
211
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 ,
211
214
partition = None , use_cow = False ):
212
215
"""Injects a ssh key and optionally net data into a disk image.
213
216
@@ -220,7 +223,8 @@ def inject_data(image, key=None, net=None, metadata=None,
220
223
img = _DiskImage (image = image , partition = partition , use_cow = use_cow )
221
224
if img .mount ():
222
225
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 ,
224
228
utils .execute )
225
229
finally :
226
230
img .umount ()
@@ -274,7 +278,7 @@ def destroy_container(img):
274
278
LOG .exception (_ ('Failed to remove container: %s' ), exn )
275
279
276
280
277
- def inject_data_into_fs (fs , key , net , metadata , execute ):
281
+ def inject_data_into_fs (fs , key , net , metadata , admin_password , execute ):
278
282
"""Injects data into a filesystem already mounted by the caller.
279
283
Virt connections can call this directly if they mount their fs
280
284
in a different way to inject_data
@@ -285,6 +289,8 @@ def inject_data_into_fs(fs, key, net, metadata, execute):
285
289
_inject_net_into_fs (net , fs , execute = execute )
286
290
if metadata :
287
291
_inject_metadata_into_fs (metadata , fs , execute = execute )
292
+ if admin_password :
293
+ _inject_admin_password_into_fs (admin_password , fs , execute = execute )
288
294
289
295
290
296
def _inject_file_into_fs (fs , path , contents ):
@@ -336,3 +342,110 @@ def _inject_net_into_fs(net, fs, execute=None):
336
342
utils .execute ('chmod' , 755 , netdir , run_as_root = True )
337
343
netfile = os .path .join (netdir , 'interfaces' )
338
344
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 ()
0 commit comments