[virt-tools-list] [virt-bootstrap] [PATCH v6 16/26] Set root password with guestfs-python
Radostin Stoyanov
rstoyanov1 at gmail.com
Thu Aug 17 09:39:54 UTC 2017
Use python bindings for libguestfs to create additional qcow2 image
using the last layer (layer-0.qcow2 for FileSource) as backing file
and modify the shadow file by inserting hash value of the given root
password.
---
src/virtBootstrap/sources/docker_source.py | 9 +++-
src/virtBootstrap/sources/file_source.py | 11 +++-
src/virtBootstrap/utils.py | 83 ++++++++++++++++++------------
src/virtBootstrap/virt_bootstrap.py | 14 ++---
tests/docker_source.py | 1 -
tests/file_source.py | 1 -
6 files changed, 76 insertions(+), 43 deletions(-)
diff --git a/src/virtBootstrap/sources/docker_source.py b/src/virtBootstrap/sources/docker_source.py
index ba70a44..502345d 100644
--- a/src/virtBootstrap/sources/docker_source.py
+++ b/src/virtBootstrap/sources/docker_source.py
@@ -51,6 +51,7 @@ class DockerSource(object):
@param password: Password to access source registry
@param uid_map: Mappings for UID of files in rootfs
@param gid_map: Mappings for GID of files in rootfs
+ @param root_password: Root password to set in rootfs
@param fmt: Format used to store image [dir, qcow2]
@param not_secure: Do not require HTTPS and certificate verification
@param no_cache: Whether to store downloaded images or not
@@ -65,6 +66,7 @@ class DockerSource(object):
self.password = kwargs.get('password', None)
self.uid_map = kwargs.get('uid_map', [])
self.gid_map = kwargs.get('gid_map', [])
+ self.root_password = kwargs.get('root_password', None)
self.output_format = kwargs.get('fmt', utils.DEFAULT_OUTPUT_FORMAT)
self.insecure = kwargs.get('not_secure', False)
self.no_cache = kwargs.get('no_cache', False)
@@ -290,10 +292,15 @@ class DockerSource(object):
)
img.create_base_layer()
img.create_backing_chains()
+ img.set_root_password(self.root_password)
if self.uid_map or self.gid_map:
logger.info("Mapping UID/GID")
utils.map_id_in_image(
- len(self.layers), dest, self.uid_map, self.gid_map
+ len(self.layers), # Number of layers
+ dest,
+ self.uid_map,
+ self.gid_map,
+ (self.root_password is None) # Create new disk?
)
else:
diff --git a/src/virtBootstrap/sources/file_source.py b/src/virtBootstrap/sources/file_source.py
index b4b29ce..63dd4d2 100644
--- a/src/virtBootstrap/sources/file_source.py
+++ b/src/virtBootstrap/sources/file_source.py
@@ -43,12 +43,14 @@ class FileSource(object):
@param fmt: Format used to store image [dir, qcow2]
@param uid_map: Mappings for UID of files in rootfs
@param gid_map: Mappings for GID of files in rootfs
+ @param root_password: Root password to set in rootfs
@param progress: Instance of the progress module
"""
self.path = kwargs['uri'].path
self.output_format = kwargs.get('fmt', utils.DEFAULT_OUTPUT_FORMAT)
self.uid_map = kwargs.get('uid_map', [])
self.gid_map = kwargs.get('gid_map', [])
+ self.root_password = kwargs.get('root_password', None)
self.progress = kwargs['progress'].update_progress
def unpack(self, dest):
@@ -77,9 +79,16 @@ class FileSource(object):
progress=self.progress
)
img.create_base_layer()
+ img.set_root_password(self.root_password)
if self.uid_map or self.gid_map:
logger.info("Mapping UID/GID")
- utils.map_id_in_image(1, dest, self.uid_map, self.gid_map)
+ utils.map_id_in_image(
+ 1, # Number of layers
+ dest,
+ self.uid_map,
+ self.gid_map,
+ (self.root_password is None) # Create new disk?
+ )
else:
raise Exception("Unknown format:" + self.output_format)
diff --git a/src/virtBootstrap/utils.py b/src/virtBootstrap/utils.py
index 2e0bbf8..4f8ae48 100644
--- a/src/virtBootstrap/utils.py
+++ b/src/virtBootstrap/utils.py
@@ -31,7 +31,6 @@ import subprocess
import sys
import tempfile
import logging
-import re
import guestfs
import passlib.hosts
@@ -138,6 +137,46 @@ class BuildImage(object):
xattrs=True, selinux=True, acls=True)
self.g.umount('/')
+ def set_root_password(self, root_password):
+ """
+ Set root password within new layer
+ """
+ if not root_password:
+ return
+
+ self.progress("Setting root password", logger=logger)
+ img_file = os.path.join(self.dest, 'layer-%s.qcow2' % self.nlayers)
+ self.g.disk_create(
+ filename=img_file,
+ format='qcow2',
+ size=-1,
+ backingfile=self.qcow2_files[-1],
+ backingformat='qcow2'
+ )
+ self.g.add_drive(img_file, format='qcow2')
+ self.g.launch()
+ self.g.mount('/dev/sda', '/')
+ success = False
+ if self.g.is_file('/etc/shadow'):
+ shadow_content = self.g.read_file('/etc/shadow').decode('utf-8')
+ shadow_content = shadow_content.split('\n')
+ if shadow_content:
+ # Note: 'shadow_content' is a list, pass-by-reference is used
+ set_password_in_shadow_content(shadow_content, root_password)
+ self.g.write('/etc/shadow', '\n'.join(shadow_content))
+ success = True
+ else:
+ logger.error('shadow file is empty')
+ else:
+ logger.error('shadow file was not found')
+
+ self.g.umount('/')
+ self.g.shutdown()
+
+ if not success:
+ self.progress("Removing root password layer", logger=logger)
+ os.remove(img_file)
+
def get_compression_type(tar_file):
"""
@@ -429,29 +468,6 @@ def set_root_password_in_rootfs(rootfs, password):
os.chmod(shadow_file, shadow_file_permissions)
-def set_root_password_in_image(image, password):
- """
- Set password on the root user within image
- """
- password_hash = passlib.hosts.linux_context.hash(password)
- execute(['virt-edit',
- '-a', image, '/etc/shadow',
- '-e', 's,^root:.*?:,root:%s:,' % re.escape(password_hash)])
-
-
-def set_root_password(fmt, dest, root_password):
- """
- Set root password
- """
- if fmt == "dir":
- set_root_password_in_rootfs(dest, root_password)
- elif fmt == "qcow2":
- layers = [layer for layer in os.listdir(dest)
- if layer.startswith('layer-')]
- set_root_password_in_image(os.path.join(dest, max(layers)),
- root_password)
-
-
def write_progress(prog):
"""
Write progress output to console
@@ -566,7 +582,7 @@ def apply_mapping_in_image(uid, gid, rootfs_tree, g):
g.lchown(new_uid, new_gid, os.path.join('/', member))
-def map_id_in_image(nlayers, dest, map_uid, map_gid):
+def map_id_in_image(nlayers, dest, map_uid, map_gid, new_disk=True):
"""
Create additional layer in which UID/GID mipping is applied.
@@ -579,14 +595,15 @@ def map_id_in_image(nlayers, dest, map_uid, map_gid):
additional_layer = os.path.join(dest, "layer-%d.qcow2" % nlayers)
# Add the last layer as readonly
g.add_drive_opts(last_layer, format='qcow2', readonly=True)
- # Create the additional layer
- g.disk_create(
- filename=additional_layer,
- format='qcow2',
- size=-1,
- backingfile=last_layer,
- backingformat='qcow2'
- )
+ if new_disk:
+ # Create the additional layer
+ g.disk_create(
+ filename=additional_layer,
+ format='qcow2',
+ size=-1,
+ backingfile=last_layer,
+ backingformat='qcow2'
+ )
g.add_drive(additional_layer, format='qcow2')
g.launch()
g.mount('/dev/sda', '/')
diff --git a/src/virtBootstrap/virt_bootstrap.py b/src/virtBootstrap/virt_bootstrap.py
index 2d81998..e387842 100755
--- a/src/virtBootstrap/virt_bootstrap.py
+++ b/src/virtBootstrap/virt_bootstrap.py
@@ -132,17 +132,19 @@ def bootstrap(uri, dest,
password=password,
uid_map=uid_map,
gid_map=gid_map,
+ root_password=root_password,
not_secure=not_secure,
no_cache=no_cache,
progress=prog).unpack(dest)
- if root_password is not None:
- logger.info("Setting password of the root account")
- utils.set_root_password(fmt, dest, root_password)
+ if fmt == "dir":
+ if root_password is not None:
+ logger.info("Setting password of the root account")
+ utils.set_root_password_in_rootfs(dest, root_password)
- if fmt == "dir" and uid_map or gid_map:
- logger.info("Mapping UID/GID")
- utils.mapping_uid_gid(dest, uid_map, gid_map)
+ if uid_map or gid_map:
+ logger.info("Mapping UID/GID")
+ utils.mapping_uid_gid(dest, uid_map, gid_map)
def set_logging_conf(loglevel=None):
diff --git a/tests/docker_source.py b/tests/docker_source.py
index 93835de..93acc50 100644
--- a/tests/docker_source.py
+++ b/tests/docker_source.py
@@ -371,7 +371,6 @@ class Qcow2OwnershipMapping(Qcow2BuildImage):
)
- at unittest.skip("Need fix for this test to pass")
class Qcow2SettingRootPassword(Qcow2BuildImage):
"""
Ensures that the root password is set correctly in the last backing chain
diff --git a/tests/file_source.py b/tests/file_source.py
index a18b6f4..8851c3f 100644
--- a/tests/file_source.py
+++ b/tests/file_source.py
@@ -94,7 +94,6 @@ class Qcow2OwnershipMapping(Qcow2BuildImage):
self.check_qcow2_images(self.get_image_path(1))
- at unittest.skip("Not implemented")
class Qcow2SettingRootPassword(Qcow2BuildImage):
"""
Ensures that the root password is set correctly in the backing file
--
2.13.5
More information about the virt-tools-list
mailing list