[virt-tools-list] [virt-bootstrap] [PATCH v6 16/26] Set root password with guestfs-python

Cedric Bosdonnat cbosdonnat at suse.com
Sat Aug 19 13:02:22 UTC 2017


On Thu, 2017-08-17 at 10:39 +0100, Radostin Stoyanov wrote:
> 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.

Maybe rephrase to make it clear that it's the same layer that is added
for uid/gid mapping, not another one.

> ---
>  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

Like previous commit, I'ld rather see these tests added in this commit, rather
than skipped before.

ACK with those changes
--
Cedric




More information about the virt-tools-list mailing list