On Sat, 2017-08-26 at 21:42 +0100, Radostin Stoyanov wrote: > Use the python bindings of libguestfs to create additional qcow2 image > which has as backing file the last layer (layer-0.qcow2 for FileSource) > and insert hashed value of given root password in the /etc/shadow file. > > Note: This additional qcow2 image is also used to apply UID/GID map. > --- > 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 | 18 +++++++ > tests/file_source.py | 9 ++++ > 6 files changed, 103 insertions(+), 41 deletions(-) > > diff --git a/src/virtBootstrap/sources/docker_source.py b/src/virtBootstrap/sources/docker_source.py > index a2fc8b9..3500bf1 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) > @@ -287,10 +289,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 a1b648e..d5da5c1 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): > """ > @@ -432,29 +471,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 > @@ -569,7 +585,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. > > @@ -582,14 +598,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 f0abac4..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 eeea379..9090988 100644 > --- a/tests/docker_source.py > +++ b/tests/docker_source.py > @@ -286,6 +286,24 @@ class TestQcow2DockerSource(Qcow2ImageAccessor): > g.umount('/') > g.shutdown() > > + def test_qcow2_setting_root_password(self): > + """ > + Ensures that the root password is set in the last qcow2 image. > + """ > + self.root_password = "My secret password" > + layers_rootfs = self.call_bootstrap() > + > + g = guestfs.GuestFS(python_return_dict=True) > + g.add_drive_opts( > + self.get_image_path(len(layers_rootfs)), > + readonly=True > + ) > + g.launch() > + g.mount('/dev/sda', '/') > + self.validate_shadow_file_in_image(g) > + g.umount('/') > + g.shutdown() > + > > class TestDockerSource(unittest.TestCase): > """ > diff --git a/tests/file_source.py b/tests/file_source.py > index 9ffd4e3..4554ecd 100644 > --- a/tests/file_source.py > +++ b/tests/file_source.py > @@ -115,3 +115,12 @@ class TestQcow2FileSource(Qcow2ImageAccessor): > self.call_bootstrap() > self.apply_mapping() > self.check_qcow2_images(self.get_image_path(1)) > + > + def test_qcow2_setting_root_password(self): > + """ > + Ensures that the root password is set in the last qcow2 image. > + """ > + self.root_password = "My secret password" > + self.call_bootstrap() > + self.check_image = self.validate_shadow_file_in_image > + self.check_qcow2_images(self.get_image_path(1)) ACK -- Cedric _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list