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): ) -@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)) -@unittest.skip("Not implemented") class Qcow2SettingRootPassword(Qcow2BuildImage): """ Ensures that the root password is set correctly in the backing file -- 2.13.5 _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list