From: "T. Schaffner" <tobias.schaffner@siemens.com>
To: <isar-users@googlegroups.com>
Cc: <quirin.gylstorff@siemens.com>, <henning.schild@siemens.com>,
"Tobias Schaffner" <tobias.schaffner@siemens.com>
Subject: [PATCH v2 1/4] simplify image-account-extension
Date: Tue, 9 May 2023 09:44:09 +0200 [thread overview]
Message-ID: <20230509074412.86392-2-tobias.schaffner@siemens.com> (raw)
In-Reply-To: <20230509074412.86392-1-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..ab7b48a4 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, 2019-2023
#
# 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)
}
--
2.34.1
next prev parent reply other threads:[~2023-05-09 7:44 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-05-09 7:44 [PATCH v2 0/4] Rewrite the image-account-extension in python T. Schaffner
2023-05-09 7:44 ` T. Schaffner [this message]
2023-05-09 7:44 ` [PATCH v2 2/4] create a minimal python unittest infrastructure T. Schaffner
2023-05-09 7:44 ` [PATCH v2 3/4] add unittests for the image-account-extension T. Schaffner
2023-05-09 7:44 ` [PATCH v2 4/4] set minimal python version in user_manual to 3.5 T. Schaffner
2023-05-22 4:41 ` [PATCH v2 0/4] Rewrite the image-account-extension in python Uladzimir Bely
2023-05-22 4:57 ` Uladzimir Bely
2023-05-22 6:52 ` Schaffner, Tobias
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=20230509074412.86392-2-tobias.schaffner@siemens.com \
--to=tobias.schaffner@siemens.com \
--cc=henning.schild@siemens.com \
--cc=isar-users@googlegroups.com \
--cc=quirin.gylstorff@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