From 082e9ca10bfb31b02c99876f425871ab58e423c5 Mon Sep 17 00:00:00 2001 From: saypaul Date: Wed, 27 Nov 2024 19:35:26 +0530 Subject: [PATCH 1/3] tier1/fix:re-sync file owner post upgrade Resync file owner if there are any discrepancies after the system is upgraded from a different build system. The script is to iterate all the files except /sysroot in parallel to check for a change in the uid/gid post-upgrade. If there is any it will trigger chown/chgrp to sync them according to the new uid/gid. The script caches the unchanged uid/gid to skip files with unchanged pairs and optimize the execution time. The scripts run via a systemd unit which in turn requires ostree-finalized-stage.service to ensure that the sync script runs only when a system is upgraded. --- tier-1/bootc-file-ownership | 83 +++++++++++++++++++++++++++++ tier-1/bootc-file-ownership.service | 15 ++++++ tier-1/manifest.yaml | 1 + tier-1/ownership-sync.yaml | 12 +++++ 4 files changed, 111 insertions(+) create mode 100755 tier-1/bootc-file-ownership create mode 100644 tier-1/bootc-file-ownership.service create mode 100644 tier-1/ownership-sync.yaml diff --git a/tier-1/bootc-file-ownership b/tier-1/bootc-file-ownership new file mode 100755 index 0000000..ef36701 --- /dev/null +++ b/tier-1/bootc-file-ownership @@ -0,0 +1,83 @@ +#!/bin/bash + +# Log file for tracking changes +LOGFILE="/var/log/fix-ownership.log" +echo "Starting ownership change process on $(date)" > "$LOGFILE" + +# Cache to track unchanged users and groups +UNCHANGED_USERS=() +UNCHANGED_GROUPS=() +USER_EXISTS=false +GROUP_EXISTS=false + +# function to check if a user is unchanged +is_user_unchanged() { + local user="$1" + [[ " ${UNCHANGED_USERS[@]} " =~ " ${user} " ]] +} + +# function to check if a group is unchanged +is_group_unchanged() { + local group="$1" + [[ " ${UNCHANGED_GROUPS[@]} " =~ " ${group} " ]] +} + +# function to process a single file +process_file() { + local filepath="$1" + + # gather username, UID, group name, and GID of the file/folder + FILE_UID=$(stat -c "%u" "$filepath") + FILE_GID=$(stat -c "%g" "$filepath") + FILE_USER=$(stat -c "%U" "$filepath") + FILE_GROUP=$(stat -c "%G" "$filepath") + + # skip files owned by unchanged users and groups + if is_user_unchanged "$FILE_USER" && is_group_unchanged "$FILE_GROUP"; then + return + fi + + # check if the user exists in /lib/passwd + if grep -q "^${FILE_USER}:" /lib/passwd; then + EXPECTED_UID=$(getent passwd "$FILE_USER" | cut -d: -f3) + USER_EXISTS=true + fi + + # check if the group exists in /lib/group + if grep -q "^${FILE_GROUP}:" /lib/group; then + EXPECTED_GID=$(getent group "$FILE_GROUP" | cut -d: -f3) + GROUP_EXISTS=true + fi + + # compare UID + if $USER_EXISTS then + if [[ "$FILE_UID" != "$EXPECTED_UID" ]]; then + echo "Fixing UID for $filepath: $FILE_UID -> $EXPECTED_UID" >> "$LOGFILE" + chown "$EXPECTED_UID" "$filepath" + else + # mark user as unchanged + UNCHANGED_USERS+=("$FILE_USER") + fi + fi + + # compare GID + if $GROUP_EXISTS then + if [[ "$FILE_GID" != "$EXPECTED_GID" ]]; then + echo "Fixing GID for $filepath: $FILE_GID -> $EXPECTED_GID" >> "$LOGFILE" + chgrp "$EXPECTED_GID" "$filepath" + else + # mark group as unchanged + UNCHANGED_GROUPS+=("$FILE_GROUP") + fi + fi + +} + +export -f process_file +export LOGFILE +export UNCHANGED_USERS +export UNCHANGED_GROUPS + +# Parallelized processing with find and xargs +find / -path /sysroot -prune -o -print 2>/dev/null | xargs -P "$(nproc)" -I {} bash -c 'process_file "$@"' _ {} +echo "ownership chnage process completed on $(date)" >> "$LOGFILE" diff --git a/tier-1/bootc-file-ownership.service b/tier-1/bootc-file-ownership.service new file mode 100644 index 0000000..138db7b --- /dev/null +++ b/tier-1/bootc-file-ownership.service @@ -0,0 +1,15 @@ +[Unit] +Description=Bootc system migration file ownership sync +# This helps verify that we're running in a bootc/ostree based target. +ConditionPathIsMountPoint=/sysroot +RequiresMountsFor=/boot +After=network.target + +[Service] +ExecStart=/usr/libexec/bootc-file-ownership +# So we can temporarily remount the sysroot writable +MountFlags=slave +# Just to auto-cleanup our temporary files +PrivateTmp=yes + + diff --git a/tier-1/manifest.yaml b/tier-1/manifest.yaml index 2242b17..934605c 100644 --- a/tier-1/manifest.yaml +++ b/tier-1/manifest.yaml @@ -13,6 +13,7 @@ include: - persistent-journal.yaml - initramfs-full.yaml - generic-growfs.yaml + - ownership-sync.yaml packages: # Include and set the default editor diff --git a/tier-1/ownership-sync.yaml b/tier-1/ownership-sync.yaml new file mode 100644 index 0000000..e6aeebb --- /dev/null +++ b/tier-1/ownership-sync.yaml @@ -0,0 +1,12 @@ +add-files: + - - bootc-file-ownership + - /usr/libexec/bootc-file-ownership + - - bootc-file-ownership.service + - /usr/lib/systemd/system/bootc-file-ownership.service + +postprocess: + - | + #!/bin/bash + set -euo pipefail + mkdir -p /usr/lib/systemd/system/ostree-finalized-stage.service.requires + ln -s ../bootc-file-ownership.service /usr/lib/systemd/system/ostree-finalized-stage.service.requires/bootc-file-ownership.service \ No newline at end of file From 4fe3e8e9a4387af1fbba4d7c3e5ca590ce6f2f72 Mon Sep 17 00:00:00 2001 From: saypaul Date: Wed, 27 Nov 2024 20:13:02 +0530 Subject: [PATCH 2/3] removed unsued params --- tier-1/bootc-file-ownership.service | 2 -- 1 file changed, 2 deletions(-) diff --git a/tier-1/bootc-file-ownership.service b/tier-1/bootc-file-ownership.service index 138db7b..37534f7 100644 --- a/tier-1/bootc-file-ownership.service +++ b/tier-1/bootc-file-ownership.service @@ -9,7 +9,5 @@ After=network.target ExecStart=/usr/libexec/bootc-file-ownership # So we can temporarily remount the sysroot writable MountFlags=slave -# Just to auto-cleanup our temporary files -PrivateTmp=yes From 8fa4201ef6a419262d466f55fa91d679dbdefe61 Mon Sep 17 00:00:00 2001 From: saypaul Date: Mon, 2 Dec 2024 20:42:42 +0530 Subject: [PATCH 3/3] remodified approach with potential usecase attached --- tier-1/bootc-file-ownership | 342 ++++++++++++++++++++++------ tier-1/bootc-file-ownership.service | 6 +- tier-1/ownership-sync.yaml | 4 +- 3 files changed, 280 insertions(+), 72 deletions(-) diff --git a/tier-1/bootc-file-ownership b/tier-1/bootc-file-ownership index ef36701..94e6f19 100755 --- a/tier-1/bootc-file-ownership +++ b/tier-1/bootc-file-ownership @@ -1,83 +1,293 @@ #!/bin/bash -# Log file for tracking changes -LOGFILE="/var/log/fix-ownership.log" -echo "Starting ownership change process on $(date)" > "$LOGFILE" +status_output=$(bootc status) -# Cache to track unchanged users and groups -UNCHANGED_USERS=() -UNCHANGED_GROUPS=() -USER_EXISTS=false -GROUP_EXISTS=false +# extract the rollback checksum +rollback_checksum=$(echo "$status_output" | awk '/rollback:/{flag=1} /checksum:/{if(flag){print $2; exit}}') -# function to check if a user is unchanged -is_user_unchanged() { - local user="$1" - [[ " ${UNCHANGED_USERS[@]} " =~ " ${user} " ]] +# retrun if rollback_checksum is empty +if [ -z "$rollback_checksum" ]; then + echo "Rollback deployment not found no need to sync " + exit 0 +fi + +# source /etc/os-release to get the ID +source /etc/os-release + +# extract the ID and remove quotes +os_id="${ID//\"}" + +prefix="/ostree/deploy/" +# Adjust the regex as needed to match specific patterns or wildcards +prefix=$(find "$prefix" -type d -regex ".*${os_id}.*" -print -quit) +prefix="${prefix}/deploy" + +# Regular expression to find folders matching the checksums +rollback_folder=$(find "$prefix" -type d -regex ".*${rollback_checksum}.*" -print -quit) + +# Define the suffixes +suffix_group="usr/lib/group" +suffix_passwd="usr/lib/passwd" + +# Construct the paths for process_* function +BOOTED_GROUP_PATH="/${suffix_group}" +BOOTED_PASSWD_PATH="/${suffix_passwd}" +ROLLBACK_GROUP_PATH="${rollback_folder}/${suffix_group}" +ROLLBACK_PASSWD_PATH="${rollback_folder}/${suffix_passwd}" + +# Array to store booted username and UID +declare -A passwd_booted=() +# Array to store rollback username and UID +declare -A passwd_rollback=() +declare -A passwd_gid_rollback=() +# Create a key-value list for UID mismatches +declare -A uid_mismatch=() +declare -A uid_missing=() + + +# Array to store rollback groupname and gid +declare -A group_rollback=() +# Array to map groups and subgroups if present in rollback_group +declare -A subgroup_mapping=() +declare -A group_booted=() +# Create a key-value list for gid mismatches +declare -A gid_mismatch=() +declare -A missing_groups=() + + + +get_username_by_uid() { + local uid=$1 + local mode=$2 # "booted" or "rollback" + local username="" + + if [[ $mode == "booted" ]]; then + for user in "${!passwd_booted[@]}"; do + if [[ "${passwd_booted[$user]}" == "$uid" ]]; then + username="$user" + break + fi + done + elif [[ $mode == "rollback" ]]; then + for user in "${!passwd_rollback[@]}"; do + if [[ "${passwd_rollback[$user]}" == "$uid" ]]; then + username="$user" + break + fi + done + fi + + echo "$username" } -# function to check if a group is unchanged -is_group_unchanged() { - local group="$1" - [[ " ${UNCHANGED_GROUPS[@]} " =~ " ${group} " ]] +get_groupname_by_gid() { + local gid=$1 + local mode=$2 # "booted" or "rollback" + local groupname="" + + if [[ $mode == "booted" ]]; then + for group in "${!group_booted[@]}"; do + if [[ "${group_booted[$group]}" == "$gid" ]]; then + groupname="$group" + break + fi + done + elif [[ $mode == "rollback" ]]; then + for group in "${!group_rollback[@]}"; do + if [[ "${group_rollback[$group]}" == "$gid" ]]; then + groupname="$group" + break + fi + done + fi + + echo "$groupname" } -# function to process a single file -process_file() { - local filepath="$1" +# Read booted_passwd file and store username and UID in an associative array +while IFS=: read -r username password uid gid userinfo homedir command; do + passwd_booted["$username"]="$uid" +done < $BOOTED_PASSWD_PATH - # gather username, UID, group name, and GID of the file/folder - FILE_UID=$(stat -c "%u" "$filepath") - FILE_GID=$(stat -c "%g" "$filepath") - FILE_USER=$(stat -c "%U" "$filepath") - FILE_GROUP=$(stat -c "%G" "$filepath") - # skip files owned by unchanged users and groups - if is_user_unchanged "$FILE_USER" && is_group_unchanged "$FILE_GROUP"; then - return - fi +# Read rollback_passwd file and store username and UID in an associative array +while IFS=: read -r username password uid gid userinfo homedir command; do + passwd_rollback["$username"]="$uid" + passwd_gid_rollback["$username"]="$gid" +done < $ROLLBACK_PASSWD_PATH - # check if the user exists in /lib/passwd - if grep -q "^${FILE_USER}:" /lib/passwd; then - EXPECTED_UID=$(getent passwd "$FILE_USER" | cut -d: -f3) - USER_EXISTS=true - fi - # check if the group exists in /lib/group - if grep -q "^${FILE_GROUP}:" /lib/group; then - EXPECTED_GID=$(getent group "$FILE_GROUP" | cut -d: -f3) - GROUP_EXISTS=true - fi +# Check for users in rollback_passwd but not in booted_passwd +for username in "${!passwd_rollback[@]}"; do + if [[ -z "${passwd_booted[$username]}" ]]; then + # Add user with details from rollback_passwd + uid_missing["${passwd_rollback[$username]}"]="$username" + elif [[ "${passwd_rollback[$username]}" != "${passwd_booted[$username]}" ]]; then + # UID mismatch - store in key-value list + uid_mismatch["${passwd_rollback[$username]}"]="${passwd_booted[$username]}" + fi +done - # compare UID - if $USER_EXISTS then - if [[ "$FILE_UID" != "$EXPECTED_UID" ]]; then - echo "Fixing UID for $filepath: $FILE_UID -> $EXPECTED_UID" >> "$LOGFILE" - chown "$EXPECTED_UID" "$filepath" - else - # mark user as unchanged - UNCHANGED_USERS+=("$FILE_USER") - fi - fi - # compare GID - if $GROUP_EXISTS then - if [[ "$FILE_GID" != "$EXPECTED_GID" ]]; then - echo "Fixing GID for $filepath: $FILE_GID -> $EXPECTED_GID" >> "$LOGFILE" - chgrp "$EXPECTED_GID" "$filepath" - else - # mark group as unchanged - UNCHANGED_GROUPS+=("$FILE_GROUP") - fi - fi -} +# Display missing users +if [[ ${#uid_missing[@]} -gt 0 ]]; then + echo "Missing Users" + for uid in "${!uid_missing[@]}"; do + echo "${uid_missing[$uid]} (UID: $uid) (GID: ${passwd_gid_rollback[${uid_missing[$uid]}]})" + done +else + echo "No missing user found" +fi -export -f process_file -export LOGFILE -export UNCHANGED_USERS -export UNCHANGED_GROUPS +if [[ ${#uid_mismatch[@]} -gt 0 ]]; then + echo "UID mismatches found:" + for rollback_uid in "${!uid_mismatch[@]}"; do + booted_uid="${uid_mismatch[$rollback_uid]}" + booted_user=$(get_username_by_uid "$booted_uid" "booted") + echo "$booted_user : (Rollback UID: $rollback_uid) (Booted UID: $booted_uid)" + done +else + echo "No UID mismatches found." +fi -# Parallelized processing with find and xargs -find / -path /sysroot -prune -o -print 2>/dev/null | xargs -P "$(nproc)" -I {} bash -c 'process_file "$@"' _ {} -echo "ownership chnage process completed on $(date)" >> "$LOGFILE" + +# Read rollback_group file and store groupname, gid, and subgroup in arrays +while IFS=: read -r groupname password gid subgroup; do + group_rollback["$groupname"]="$gid" + if [[ -n "$subgroup" ]]; then + subgroup_mapping["$groupname"]="$subgroup" + fi +done < $ROLLBACK_GROUP_PATH + +# Array to store booted groupname and GID + + +# Read booted_group file and store groupname and gid in an associative array +while IFS=: read -r groupname password gid subgroup; do + group_booted["$groupname"]="$gid" + if [[ ${subgroup_mapping["$groupname"]} == $subgroup ]]; then + unset "subgroup_mapping[$groupname]" + fi +done < $BOOTED_GROUP_PATH + +# Check for users in rollback_group but not in booted_group +for groupname in "${!group_rollback[@]}"; do + if [[ -z "${group_booted[$groupname]}" ]]; then + # Add group with details from rollback_group + missing_groups["${group_rollback[$groupname]}"]="$groupname" + elif [[ "${group_rollback[$groupname]}" != "${group_booted[$groupname]}" ]]; then + # GID mismatch - store groupname and mismatched GIDs + gid_mismatch["${group_rollback[$groupname]}"]="${group_booted[$groupname]}" + fi +done + +# Display missing groups +if [[ ${#missing_groups[@]} -gt 0 ]]; then + echo "Missing groups:" + for gid in "${!missing_groups[@]}"; do + echo "${missing_groups[$gid]} (GID: ${gid})" + done +else + echo "No missing groups found." +fi + +# Display GID mismatches +if [[ ${#gid_mismatch[@]} -gt 0 ]]; then + echo "GID mismatches found:" + for rollback_gid in "${!gid_mismatch[@]}"; do + booted_gid="${gid_mismatch[$rollback_gid]}" + rollback_group=$(get_groupname_by_gid "$rollback_gid" "rollback") + echo "$rollback_group : (Rollback GID: $rollback_gid) (Booted GID: $booted_gid)" + done +else + echo "No GID mismatches found." +fi + +# Display remaining groups with subgroups +if [[ ${#subgroup_mapping[@]} -gt 0 ]]; then + echo "Groups with subgroups:" + for groupname in "${!subgroup_mapping[@]}"; do + echo "Group: $groupname, Subgroup: ${subgroup_mapping[$groupname]}" + done +else + echo "No groups with subgroups found." +fi + + +# Output of the above script when ran on system switched form rpm-ostree to bootc: +# Missing Users +# core (UID: 1000) (GID: 1000) +# dnsmasq (UID: 994) (GID: 991) +# clevis (UID: 998) (GID: 996) +# UID mismatches found: +# nobody : (Rollback UID: 65534) (Booted UID: 99) +# chrony : (Rollback UID: 995) (Booted UID: 994) +# polkitd : (Rollback UID: 997) (Booted UID: 999) +# sssd : (Rollback UID: 996) (Booted UID: 995) +# systemd-coredump : (Rollback UID: 999) (Booted UID: 987) +# Missing groups: +# core (GID: 1000) +# dnsmasq (GID: 991) +# clevis (GID: 996) +# GID mismatches found: +# ssh_keys : (Rollback GID: 101) (Booted GID: 999) +# nobody : (Rollback GID: 65534) (Booted GID: 99) +# polkitd : (Rollback GID: 995) (Booted GID: 998) +# printadmin : (Rollback GID: 994) (Booted GID: 983) +# systemd-coredump : (Rollback GID: 997) (Booted GID: 984) +# input : (Rollback GID: 999) (Booted GID: 104) +# render : (Rollback GID: 998) (Booted GID: 985) +# Groups with subgroups: +# Group: tss, Subgroup: clevis +# Group: wheel, Subgroup: core +# END + +# Narrow down the search to UIDs and GIDs missing/mismatch found above instead of the whole file system. +# Use find with -uid and -gid flag, and prarllel processing to speed up the process. +# Some initial checks to prevent the need for processing to save execution time. + +# Need to process the files based the following 3 cases. +# 1. uid or gid = UNKNOWN as seen below which is due to the shift in ids and the old ID does not exist +# [root@vm-1 ~]# stat /etc/ssh/ssh_host_ecdsa_key +# File: /etc/ssh/ssh_host_ecdsa_key +# Size: 492 Blocks: 8 IO Block: 4096 regular file +# Device: fc03h/64515d Inode: 33879037 Links: 1 +# Access: (0640/-rw-r-----) Uid: ( 0/ root) Gid: ( 101/ UNKNOWN) +# Context: system_u:object_r:sshd_key_t:s0 +# Access: 2024-11-29 11:52:32.042510648 +0000 +# Modify: 2024-11-28 14:16:01.651000000 +0000 +# Change: 2024-11-29 11:52:07.833734902 +0000 +# Birth: 2024-11-29 11:52:07.833734902 +0000 + +# 2.Some files like below have different user probaly due to the shift in gid +# example: dsnmasq which has gid in 991 in rollback_passwd is now missing +# and the new bootc system has systemd-timesync assigned to 991 +# [root@localhost ~]# stat /var/lib/dnsmasq +# File: /var/lib/dnsmasq +# Size: 6 Blocks: 0 IO Block: 4096 dir +# ectory +# Device: fc03h/64515d Inode: 33735167 Links: 2 +# Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 991/ +# systemd-timesync) +# Context: system_u:object_r:dnsmasq_lease_t:s0 +# Access: 2024-12-01 18:39:07.618331912 +0000 +# Modify: 2024-11-29 20:38:37.992367043 +0000 +# Change: 2024-11-29 23:03:57.297643273 +0000 +# Birth: 2024-11-29 20:38:37.992367043 +0000 + +# 3.Missing user(core) when upgrading from rpm-ostree system in bootc,its +# kind of same behavior as rpm-ostree but do we want to change it, restore +# the user. +# [root@localhost etc]# stat /home/core/ +# File: /home/core/ +# Size: 53 Blocks: 0 IO Block: 4096 dir +# ectory +# Device: fc03h/64515d Inode: 33735133 Links: 3 +# Access: (0700/drwx------) Uid: ( 1000/ UNKNOWN) Gid: ( 1000/ +# UNKNOWN) +# Context: unconfined_u:object_r:user_home_dir_t:s0 +# Access: 2024-12-01 18:01:09.363246718 +0000 +# Modify: 2024-11-30 08:42:18.323172433 +0000 +# Change: 2024-11-30 08:42:18.323172433 +0000 +# Birth: 2024-11-29 20:34:36.496949664 +0000 diff --git a/tier-1/bootc-file-ownership.service b/tier-1/bootc-file-ownership.service index 37534f7..349ec8b 100644 --- a/tier-1/bootc-file-ownership.service +++ b/tier-1/bootc-file-ownership.service @@ -2,12 +2,10 @@ Description=Bootc system migration file ownership sync # This helps verify that we're running in a bootc/ostree based target. ConditionPathIsMountPoint=/sysroot -RequiresMountsFor=/boot -After=network.target +Requires=local-fs.target +After=local-fs.target [Service] ExecStart=/usr/libexec/bootc-file-ownership -# So we can temporarily remount the sysroot writable -MountFlags=slave diff --git a/tier-1/ownership-sync.yaml b/tier-1/ownership-sync.yaml index e6aeebb..a1a8025 100644 --- a/tier-1/ownership-sync.yaml +++ b/tier-1/ownership-sync.yaml @@ -8,5 +8,5 @@ postprocess: - | #!/bin/bash set -euo pipefail - mkdir -p /usr/lib/systemd/system/ostree-finalized-stage.service.requires - ln -s ../bootc-file-ownership.service /usr/lib/systemd/system/ostree-finalized-stage.service.requires/bootc-file-ownership.service \ No newline at end of file + mkdir -p /usr/lib/systemd/system/ostree-finalized-staged.service.requires + ln -s ../bootc-file-ownership.service /usr/lib/systemd/system/ostree-finalized-staged.service.requires/bootc-file-ownership.service \ No newline at end of file