public inbox for isar-users@googlegroups.com
 help / color / mirror / Atom feed
From: Henning Schild <henning.schild@siemens.com>
To: "T. Schaffner" <tobias.schaffner@siemens.com>
Cc: <isar-users@googlegroups.com>, <quirin.gylstorff@siemens.com>
Subject: Re: [PATCH 1/4] simplify image-account-extension
Date: Thu, 30 Mar 2023 21:55:01 +0200	[thread overview]
Message-ID: <20230330215501.6f7cafb7@md1za8fc.ad001.siemens.net> (raw)
In-Reply-To: <20230330110804.1016614-2-tobias.schaffner@siemens.com>

Am Thu, 30 Mar 2023 13:08:01 +0200
schrieb "T. Schaffner" <tobias.schaffner@siemens.com>:

> From: Tobias Schaffner <tobias.schaffner@siemens.com>
> 
> Do the complete user and group creation in python. This allows us to
> drop the encoding and parsing code that was used to make the user and
> group lists available in the shell function.
> 
> Signed-off-by: Tobias Schaffner <tobias.schaffner@siemens.com>
> ---
>  meta/classes/image-account-extension.bbclass | 368
> +++++++------------ 1 file changed, 124 insertions(+), 244
> deletions(-)
> 
> diff --git a/meta/classes/image-account-extension.bbclass
> b/meta/classes/image-account-extension.bbclass index
> 1a1f704d..d1133bb4 100644 ---
> a/meta/classes/image-account-extension.bbclass +++
> b/meta/classes/image-account-extension.bbclass @@ -1,5 +1,5 @@
>  # This software is a part of ISAR.
> -# Copyright (C) Siemens AG, 2019
> +# Copyright (C) Siemens AG, 2023

2019-2023

did not look at the rest, this feels like it should be tried out ...
functional review, which i would try to do

please make sure that would pass flake8 ... might be hard since it is
"embedded code"

a long time ago we had contributions from Harald Seiler, who managed to
somehow lint python code inside bb code.

Henning

>  #
>  # SPDX-License-Identifier: MIT
>  #
> @@ -25,251 +25,131 @@ GROUPS ??= ""
>  #GROUP_root[gid] = ""
>  #GROUP_root[flags] = "system"
>  
> -def gen_accounts_array(d, listname, entryname, flags,
> verb_flags=None):
> -    from itertools import chain
> -
> -    entries = (d.getVar(listname) or "").split()
> -    return " ".join(
> -        ":".join(
> -            chain(
> -                (entry,),
> -                (
> -                    (",".join(
> -                        (
> -                            d.getVarFlag(entryname + "_" + entry,
> flag, True) or ""
> -                        ).split()
> -                    ) if flag not in (verb_flags or []) else (
> -                        d.getVarFlag(entryname + "_" + entry, flag,
> True) or ""
> -                    )).replace(":","=")
> -                    for flag in flags
> -                ),
> -            )
> -        )
> -        for entry in entries
> -    )
> -
> -# List of space separated entries, where each entry has the format:
> -#
> username:encryptedpassword:expiredate:inactivenumber:userid:groupid:comment:homedir:shell:group1,group2:flag1,flag2
> -IMAGE_ACCOUNTS_USERS =+ "${@gen_accounts_array(d, 'USERS', 'USER',
> ['password',  'expire', 'inactive', 'uid', 'gid', 'comment', 'home',
> 'shell', 'groups', 'flags'], ['password', 'comment', 'home',
> 'shell'])}" - -# List of space separated entries, where each entry
> has the format: -# groupname:groupid:flag1,flag2
> -IMAGE_ACCOUNTS_GROUPS =+ "${@gen_accounts_array(d, 'GROUPS',
> 'GROUP', ['gid', 'flags'])}" - -do_rootfs_install[vardeps] +=
> "${IMAGE_ACCOUNTS_GROUPS} ${IMAGE_ACCOUNTS_USERS}"
> -ROOTFS_POSTPROCESS_COMMAND += "image_postprocess_accounts"
> -image_postprocess_accounts() {
> -    # Create groups
> -    # Add space to the end of the list:
> -    list='${@" ".join(d.getVar('IMAGE_ACCOUNTS_GROUPS').split())} '
> -    while true; do
> -        # Pop first group entry:
> -        list_rest="${list#*:*:* }"
> -        entry="${list%%${list_rest}}"
> -        list="${list_rest}"
> -
> -        if [ -z "${entry}" ]; then
> -            break
> -        fi
> -
> -        # Add colon to the end of the entry and remove trailing
> space:
> -        entry="${entry% }:"
> -
> -        # Decode entries:
> -        name="${entry%%:*}"
> -        entry="${entry#${name}:}"
> -
> -        gid="${entry%%:*}"
> -        entry="${entry#${gid}:}"
> -
> -        flags="${entry%%:*}"
> -        entry="${entry#${flags}:}"
> -
> -        flags=",${flags}," # Needed for searching for substrings
> -
> -        # Check if user already exists:
> -        if grep -q "^${name}:" '${ROOTFSDIR}/etc/group'; then
> -            exists="y"
> -        else
> -            exists="n"
> -        fi
> -
> -        # Create arguments:
> -        set -- # clear arguments
> -
> -        if [ -n "$gid" ]; then
> -            set -- "$@" --gid "$gid"
> -        fi
> -
> -        if [ "n" = "$exists" ]; then
> -            if [ "${flags}" != "${flags%*,system,*}" ]; then
> -                set -- "$@" --system
> -            fi
> -        fi
> -
> -        # Create or modify groups:
> -        if [ "y" = "$exists" ]; then
> -            if [ -z "$@" ]; then
> -                echo "Do not execute groupmod (no changes)."
> -            else
> -                echo "Execute groupmod with \"$@\" for \"$name\""
> -                sudo -E chroot '${ROOTFSDIR}' \
> -                    /usr/sbin/groupmod "$@" "$name"
> -            fi
> -        else
> -            echo "Execute groupadd with \"$@\" for \"$name\""
> -            sudo -E chroot '${ROOTFSDIR}' \
> -                /usr/sbin/groupadd "$@" "$name"
> -        fi
> -    done
> -
> -    # Create users
> -    list='${@" ".join(d.getVar('IMAGE_ACCOUNTS_USERS').split())} '
> -    while true; do
> -        # Pop first user entry:
> -        list_rest="${list#*:*:*:*:*:*:*:*:*:*:* }"
> -        entry="${list%%${list_rest}}"
> -        list="${list_rest}"
> -
> -        if [ -z "${entry}" ]; then
> -            break
> -        fi
> -
> -        # Add colon to the end of the entry and remove trailing
> space:
> -        entry="${entry% }:"
> -
> -        # Decode entries:
> -        name="${entry%%:*}"
> -        entry="${entry#${name}:}"
> -
> -        password="${entry%%:*}"
> -        entry="${entry#${password}:}"
> -
> -        expire="${entry%%:*}"
> -        entry="${entry#${expire}:}"
> -
> -        inactive="${entry%%:*}"
> -        entry="${entry#${inactive}:}"
> -
> -        uid="${entry%%:*}"
> -        entry="${entry#${uid}:}"
> -
> -        gid="${entry%%:*}"
> -        entry="${entry#${gid}:}"
> -
> -        comment="${entry%%:*}"
> -        entry="${entry#${comment}:}"
> -
> -        home="${entry%%:*}"
> -        entry="${entry#${home}:}"
> -
> -        shell="${entry%%:*}"
> -        entry="${entry#${shell}:}"
> -
> -        groups="${entry%%:*}"
> -        entry="${entry#${groups}:}"
> -
> -        flags="${entry%%:*}"
> -        entry="${entry#${flags}:}"
> -
> -        flags=",${flags}," # Needed for searching for substrings
> -
> -        # Check if user already exists:
> -        if grep -q "^${name}:" '${ROOTFSDIR}/etc/passwd'; then
> -            exists="y"
> -        else
> -            exists="n"
> -        fi
> -
> -        # Create arguments:
> -        set -- # clear arguments
> -
> -        if [ -n "$expire" ]; then
> -            set -- "$@" --expiredate "$expire"
> -        fi
> -
> -        if [ -n "$inactive" ]; then
> -            set -- "$@" --inactive "$inactive"
> -        fi
> -
> -        if [ -n "$uid" ]; then
> -            set -- "$@" --uid "$uid"
> -        fi
> -
> -        if [ -n "$gid" ]; then
> -            set -- "$@" --gid "$gid"
> -        fi
> -
> -        if [ -n "$comment" ]; then
> -            set -- "$@" --comment "$comment"
> -        fi
> -
> -        if [ -n "$home" ]; then
> -            if [ "y" = "$exists" ]; then
> -                set -- "$@" --home "$home" --move-home
> -            else
> -                set -- "$@" --home-dir "$home"
> -            fi
> -        fi
> -
> -        if [ -n "$shell" ]; then
> -            set -- "$@" --shell "$shell"
> -        fi
> -
> -        if [ -n "$groups" ]; then
> -            set -- "$@" --groups "$groups"
> -        fi
> -
> -        if [ "n" = "$exists" ]; then
> -            if [ "${flags}" != "${flags%*,system,*}" ]; then
> -                set -- "$@" --system
> -            fi
> -            if [ "${flags}" != "${flags%*,no-create-home,*}" ]; then
> -                set -- "$@" --no-create-home
> -            else
> -                if [ "${flags}" != "${flags%*,create-home,*}" ]; then
> -                    set -- "$@" --create-home
> -                fi
> -            fi
> -        fi
> -
> -        # Create or modify users:
> -        if [ "y" = "$exists" ]; then
> -            if [ -z "$@" ]; then
> -                echo "Do not execute usermod (no changes)."
> -            else
> -                echo "Execute usermod with \"$@\" for \"$name\""
> -                sudo -E chroot '${ROOTFSDIR}' \
> -                    /usr/sbin/usermod "$@" "$name"
> -            fi
> -        else
> -            echo "Execute useradd with \"$@\" for \"$name\""
> -            sudo -E chroot '${ROOTFSDIR}' \
> -                /usr/sbin/useradd "$@" "$name"
> -        fi
> -
> -        # Set password:
> -        if [ -n "$password" -o "${flags}" !=
> "${flags%*,allow-empty-password,*}" ]; then
> -            chpasswd_args="-e"
> -            if [ "${flags}" != "${flags%*,clear-text-password,*}" ];
> then +def image_create_groups(d: "DataSmart") -> None:
> +    """Creates the groups defined in the ``GROUPS`` bitbake variable.
> +
> +    Args:
> +        d (DataSmart): The bitbake datastore.
> +
> +    Returns:
> +        None
> +    """
> +    entries = (d.getVar("GROUPS") or "").split()
> +    rootfsdir = d.getVar("ROOTFSDIR")
> +    chroot = ["sudo", "-E", "chroot", rootfsdir]
> +
> +    for entry in entries:
> +        args = []
> +        group_entry = "GROUP_{}".format(entry)
> +
> +        with open("{}/etc/group".format(rootfsdir), "r") as
> group_file:
> +            exists = any(line.startswith("{}:".format(entry)) for
> line in group_file) +
> +        gid = d.getVarFlag(group_entry, "gid") or ""
> +        if gid:
> +            args.append("--gid")
> +            args.append(gid)
> +
> +        flags = (d.getVarFlag(group_entry, "flags") or "").split()
> +        if "system" in flags:
> +            args.append("--system")
> +
> +        if exists:
> +            if args:
> +                bb.process.run([*chroot, "/usr/sbin/groupmod",
> *args, entry])
> +        else:
> +            bb.process.run([*chroot, "/usr/sbin/groupadd", *args,
> entry]) +
> +
> +def image_create_users(d: "DataSmart") -> None:
> +    """Creates the users defined in the ``USERS`` bitbake variable.
> +
> +    Args:
> +        d (DataSmart): The bitbake datastore.
> +
> +    Returns:
> +        None
> +    """
> +    import hashlib
> +    import crypt
> +
> +    entries = (d.getVar("USERS") or "").split()
> +    rootfsdir = d.getVar("ROOTFSDIR")
> +    chroot = ["sudo", "-E", "chroot", rootfsdir]
> +
> +    for entry in entries:
> +        args = []
> +        user_entry = "USER_{}".format(entry)
> +
> +        with open("{}/etc/passwd".format(rootfsdir), "r") as
> passwd_file:
> +            exists = any(line.startswith("{}:".format(entry)) for
> line in passwd_file) +
> +        def add_user_option(option_name, flag_name):
> +            flag_value = d.getVarFlag(user_entry, flag_name) or ""
> +            if flag_value:
> +                args.append(option_name)
> +                args.append(flag_value)
> +
> +        add_user_option("--expire", "expiredate")
> +        add_user_option("--inactive", "inactive")
> +        add_user_option("--uid", "uid")
> +        add_user_option("--gid", "gid")
> +        add_user_option("--comment", "comment")
> +        add_user_option("--shell", "shell")
> +
> +        groups = d.getVarFlag(user_entry, "groups") or ""
> +        if groups:
> +            args.append("--groups")
> +            args.append(groups.replace(' ', ','))
> +
> +        flags = (d.getVarFlag(user_entry, "flags") or "").split()
> +
> +        if exists:
> +            add_user_option("--home", "home")
> +            if d.getVarFlag(user_entry, "home") or "":
> +                args.append("--move-home")
> +        else:
> +            add_user_option("--home-dir", "home")
> +
> +            if "system" in flags:
> +                args.append("--system")
> +            if "no-create-home" in flags:
> +                args.append("--no-create-home")
> +            if "create-home" in flags:
> +                args.append("--create-home")
> +
> +        if exists:
> +            if args:
> +                bb.process.run([*chroot, "/usr/sbin/usermod", *args,
> entry])
> +        else:
> +            bb.process.run([*chroot, "/usr/sbin/useradd", *args,
> entry]) +
> +        command = [*chroot, "/usr/sbin/chpasswd"]
> +        password = d.getVarFlag(user_entry, "password") or ""
> +        if password or "allow-empty-password" in flags:
> +            if "clear-text-password" in flags:
> +
>                  # chpasswd adds a random salt when running against a
> clear-text password. # For reproducible images, we manually generate
> the password and use the # SOURCE_DATE_EPOCH to generate the salt in
> a deterministic way.
> -                if [ -z "${SOURCE_DATE_EPOCH}" ]; then
> -                    chpasswd_args=""
> -                else
> -                    salt="$(echo "${SOURCE_DATE_EPOCH}" | sha256sum
> -z | cut -c 1-15)"
> -                    password="$(openssl passwd -6 -salt $salt
> "$password")"
> -                fi
> -            fi
> -            printf '%s:%s' "$name" "$password" | sudo chroot
> '${ROOTFSDIR}' \
> -                /usr/sbin/chpasswd $chpasswd_args
> -        fi
> -        if [ "${flags}" != "${flags%*,force-passwd-change,*}" ]; then
> -            echo "Execute passwd to force password change on first
> boot for \"$name\""
> -            sudo -E chroot '${ROOTFSDIR}' \
> -                /usr/bin/passwd --expire "$name"
> -        fi
> -    done
> +                source_date_epoch = d.getVar("SOURCE_DATE_EPOCH") or
> ""
> +                if source_date_epoch:
> +                    command.append("-e")
> +                    salt =
> hashlib.sha256("{}\n".format(source_date_epoch).encode()).hexdigest()[0:15]
> +                    password = crypt.crypt(password,
> "$6${}".format(salt)) +
> +            else:
> +                command.append("-e")
> +
> +            bb.process.run(command, "{}:{}".format(entry,
> password).encode()) +
> +        if "force-passwd-change" in flags:
> +            bb.process.run([*chroot, "/usr/sbin/passwd", "--expire",
> entry]) +
> +
> +ROOTFS_POSTPROCESS_COMMAND += "image_postprocess_accounts"
> +python image_postprocess_accounts() {
> +    image_create_groups(d)
> +    image_create_users(d)
>  }


  reply	other threads:[~2023-03-30 19:55 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-03-30 11:08 [PATCH 0/4] Rewrite the image-account-extension in python T. Schaffner
2023-03-30 11:08 ` [PATCH 1/4] simplify image-account-extension T. Schaffner
2023-03-30 19:55   ` Henning Schild [this message]
2023-03-30 21:39     ` Schaffner, Tobias
2023-03-30 11:08 ` [PATCH 2/4] create a minimal python unittest infrastructure T. Schaffner
2023-03-30 11:08 ` [PATCH 3/4] add unittests for the image-account-extension T. Schaffner
2023-03-30 11:08 ` [PATCH 4/4] set minimal python version in user_manual to 3.5 T. Schaffner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230330215501.6f7cafb7@md1za8fc.ad001.siemens.net \
    --to=henning.schild@siemens.com \
    --cc=isar-users@googlegroups.com \
    --cc=quirin.gylstorff@siemens.com \
    --cc=tobias.schaffner@siemens.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox