diff --git a/bin/admin/fix-group-gid.sh b/bin/admin/fix-group-gid.sh index 7d92417b4..4c1de70cc 100755 --- a/bin/admin/fix-group-gid.sh +++ b/bin/admin/fix-group-gid.sh @@ -107,7 +107,7 @@ main() fi echo - echo "$group: OK to proceed ? (CTRL+C to abort). You'll still have to validate each commands I'm going to run" + echo "$group: OK to proceed? (CTRL+C to abort). You'll still have to validate each commands I'm going to run" # shellcheck disable=SC2034 read -r ___ really_run_commands=1 diff --git a/bin/admin/rename-account.sh b/bin/admin/rename-account.sh new file mode 100644 index 000000000..097e2dc3a --- /dev/null +++ b/bin/admin/rename-account.sh @@ -0,0 +1,109 @@ +#! /usr/bin/env bash +# vim: set filetype=sh ts=4 sw=4 sts=4 et: +basedir=$(readlink -f "$(dirname "$0")"/../..) +# shellcheck source=lib/shell/functions.inc +. "$basedir"/lib/shell/functions.inc + +from=$1 +to=$2 + +if [ -n "$3" ] || [ -z "$2" ] ; then + echo "Usage: $0 original_account_name new_account_name" + exit 2 +fi + +fail() +{ + echo "Error, will not proceed: $*" + exit 1 +} + +really_run_commands=0 + +finalret=0 +_run() +{ + local ret + if [ "$really_run_commands" = "1" ] ; then + echo "Executing: $*" + "$@"; ret=$? + echo "... return code: $ret" + if [ "$ret" -ne 0 ]; then + (( ++finalret )) + fi + else + echo "DRY RUN: would execute: $*" + fi +} + +batchrun() +{ +# first, rename the main account group +getent group "$from" >/dev/null || fail "group $from doesn't exist" +getent group "$to" >/dev/null && fail "group $to already exists" +if [ "$OS_FAMILY" = FreeBSD ]; then + _run pw groupmod -n "$from" -l "$to" +else + _run groupmod -n "$to" "$from" +fi + +# then, rename the account itself +getent passwd "$from" >/dev/null || fail "user $from doesn't exist" +getent passwd "$to" >/dev/null && fail "user $to already exists" +test -d "/home/$from" || fail "directory /home/$from doesn't exist" +test -d "/home/$to" && fail "directory /home/$to already exists" +if [ "$OS_FAMILY" = FreeBSD ]; then + # FreeBSD doesn't move the home on rename, do it ourselves + _run mv -v "/home/$from" "/home/$to" + _run pw usermod -n "$from" -d /home/"$to" -l "$to" +else + _run usermod -m -d /home/"$to" -l "$to" "$from" +fi + +# then, rename all other groups linked to the account (appart from the main one already done) +# shellcheck disable=SC2043 +for suffix in tty; do + if getent group "$from-$suffix" >/dev/null ; then + getent group "$to-$suffix" >/dev/null && fail "group $to-$suffix already exists" + if [ "$OS_FAMILY" = FreeBSD ]; then + _run pw groupmod -n "$from-$suffix" -l "$to-$suffix" + else + _run groupmod -n "$to-$suffix" "$from-$suffix" + fi + fi +done + +# now handle the allowkeeper folder of the account +test -d "/home/allowkeeper/$from" || fail "directory /home/allowkeeper/$from doesn't exist" +test -d "/home/allowkeeper/$to" && fail "directory /home/allowkeeper/$to already exists" +_run mv -v "/home/allowkeeper/$from" "/home/allowkeeper/$to" + +# if the account has passwords, handle them: filenames contain the account name +passdir="/home/$from/pass" +[ "$really_run_commands" = "1" ] && passdir="/home/$to/pass" +if [ -d "$passdir" ]; then + pushd "$passdir" >/dev/null || exit 1 + # shellcheck disable=SC2044 + for passfile in $(find . -type f -name "$from" -print -o -type f -name "$from.*" -print) + do + _run mv -v "$passfile" "${passfile/$from/$to}" + done + popd >/dev/null || exit 1 +fi + +# finally, regenerate the account's sudoers +_run "$basedir"/bin/sudogen/generate-sudoers.sh create account "$to" +_run "$basedir"/bin/sudogen/generate-sudoers.sh delete account "$from" +} + + +really_run_commands=0 +batchrun +echo +echo "OK to proceed? (CTRL+C to abort)" +# shellcheck disable=SC2034 +read -r ___ +really_run_commands=1 +batchrun +echo "Done." +exit $finalret diff --git a/bin/admin/rename-group.sh b/bin/admin/rename-group.sh index eb97ef049..5e2407581 100755 --- a/bin/admin/rename-group.sh +++ b/bin/admin/rename-group.sh @@ -1,6 +1,8 @@ #! /usr/bin/env bash # vim: set filetype=sh ts=4 sw=4 sts=4 et: basedir=$(readlink -f "$(dirname "$0")"/../..) +# shellcheck source=lib/shell/functions.inc +. "$basedir"/lib/shell/functions.inc from=$1 to=$2 @@ -18,6 +20,7 @@ fail() really_run_commands=0 +finalret=0 _run() { local ret @@ -25,6 +28,9 @@ _run() echo "Executing: $*" "$@"; ret=$? echo "... return code: $ret" + if [ "$ret" -ne 0 ]; then + (( ++finalret )) + fi else echo "DRY RUN: would execute: $*" fi @@ -34,18 +40,33 @@ batchrun() { getent group "key$from" >/dev/null || fail "group key$from doesn't exist" getent group "key$to" >/dev/null && fail "group key$to already exists" -_run groupmod -n "key$to" "key$from" +if [ "$OS_FAMILY" = FreeBSD ]; then + _run pw groupmod -n "key$from" -l "key$to" +else + _run groupmod -n "key$to" "key$from" +fi + getent passwd "key$from" >/dev/null || fail "user key$from doesn't exist" getent passwd "key$to" >/dev/null && fail "user key$to already exists" test -d "/home/key$from" || fail "directory /home/key$from doesn't exists" test -d "/home/key$to" && fail "directory /home/key$to already exists" -_run usermod -m -d /home/"key$to" -l "key$to" "key$from" +if [ "$OS_FAMILY" = FreeBSD ]; then + # FreeBSD doesn't move the home on rename, do it ourselves + _run mv -v "/home/key$from" "/home/key$to" + _run pw usermod -n "key$from" -d /home/"key$to" -l "key$to" +else + _run usermod -m -d /home/"key$to" -l "key$to" "key$from" +fi for suffix in gatekeeper aclkeeper owner; do if getent group "key$from-$suffix" >/dev/null ; then getent group "key$to-$suffix" >/dev/null && fail "group key$to-$suffix already exists" - _run groupmod -n "key$to-$suffix" "key$from-$suffix" + if [ "$OS_FAMILY" = FreeBSD ]; then + _run pw groupmod -n "key$from-$suffix" -l "key$to-$suffix" + else + _run groupmod -n "key$to-$suffix" "key$from-$suffix" + fi fi done @@ -94,9 +115,10 @@ _run "$basedir"/bin/sudogen/generate-sudoers.sh delete group "key$from" really_run_commands=0 batchrun echo -echo "OK to proceed ? (CTRL+C to abort)" +echo "OK to proceed? (CTRL+C to abort)" # shellcheck disable=SC2034 read -r ___ really_run_commands=1 batchrun echo "Done." +exit $finalret diff --git a/bin/cron/osh-orphaned-homedir.sh b/bin/cron/osh-orphaned-homedir.sh index 633684da6..bcdd3f296 100755 --- a/bin/cron/osh-orphaned-homedir.sh +++ b/bin/cron/osh-orphaned-homedir.sh @@ -39,6 +39,46 @@ esac mkdir -p "/home/oldkeeper/orphaned" +archive_dir() { + _dir="$1" + + archive="/home/oldkeeper/orphaned/$(basename "$_dir").at-$(date +%s).by-orphaned-homedir-script.tar.gz" + _log "Found orphaned $_dir [$(ls -ld "$_dir")], archiving..." + chmod 0700 /home/oldkeeper/orphaned + if [ "$OS_FAMILY" = "Linux" ]; then + find "$_dir" -mindepth 1 -maxdepth 1 -type f -name "*.log" -print0 | xargs -r0 chattr -a + fi + + # remove empty directories if we have some + find "$_dir" -type d -delete 2>/dev/null || true + acls_param='' + [ "$OS_FAMILY" = "Linux" ] && acls_param='--acls' + [ "$OS_FAMILY" = "FreeBSD" ] && acls_param='--acls' + + set +e + tar czf "$archive" $acls_param --one-file-system -p --remove-files --exclude=ttyrec "$_dir" 2>/dev/null; ret=$? + set -e + + if [ $ret -ne 0 ]; then + # $? can be 2 if we can't delete because ttyrec dir remains so it might not be a problem + if [ $ret -eq 2 ] && [ -s "$archive" ] && [ -d "$_dir" ] && [ "$(find "$_dir" -name ttyrec -prune -o -print | wc -l)" = 1 ]; then + # it's OK. We chown all to root to avoid orphan UID/GID and we let the backup script take care of those + # if we still have files under $dir/ttyrec, chown all them to root:root to avoid orphan UID/GID, + # and just wait for them to be encrypted/rsynced out of the bastion by the usual ttyrec archiving script + _log "Archived $_dir to $archive" + chmod 0 "$archive" + + chown -R root:root "$_dir" + _warn "Some files remain in $_dir, we chowned everything to root" + else + _err "Couldn't archive $_dir to $archive" + fi + else + _log "Archived $_dir to $archive" + chmod 0 "$archive" + fi +} + while IFS= read -r -d '' dir do # just in case, check ourselves again that the folder's UID/GID don't resolve @@ -70,42 +110,46 @@ do continue fi - archive="/home/oldkeeper/orphaned/$(basename "$dir").at-$(date +%s).by-orphaned-homedir-script.tar.gz" - _log "Found orphaned $dir [$(ls -ld "$dir")], archiving..." - chmod 0700 /home/oldkeeper/orphaned - if [ "$OS_FAMILY" = "Linux" ]; then - find "$dir" -mindepth 1 -maxdepth 1 -type f -name "*.log" -print0 | xargs -r0 chattr -a - fi + archive_dir "$dir" +done < <(find /home/ -mindepth 1 -maxdepth 1 -type d -nouser -nogroup -mmin +3 -print0) - # remove empty directories if we have some - find "$dir" -type d -delete 2>/dev/null || true - acls_param='' - [ "$OS_FAMILY" = "Linux" ] && acls_param='--acls' - [ "$OS_FAMILY" = "FreeBSD" ] && acls_param='--acls' +# corner case for manually renamed accounts on master: the old homedir will stay on slaves, +# with only logs, ttyrecs and such (files excluded from the cluster sync). +# in that case, the old homedir is owned by the new account name, and there is no corresponding allowkeeper directory. +# look for those, and archive them if found. +if [ "$role" = "slave" ]; then + while IFS= read -r -d '' file + do + # we're getting all the folders with a "lastlog" file, skip all those that are present + # in the proper home of their file owner + file_owner=$(stat -c %U "$file" 2>/dev/null) + [ -n "$file_owner" ] || continue + + owner_home=$(getent passwd "$file_owner" 2>/dev/null | cut -d: -f6) + [ -n "$owner_home" ] || continue + [ -d "$owner_home" ] || continue + + if [ "$file" = "$owner_home/lastlog" ]; then + # all is good, nothing to see here + continue + fi - set +e - tar czf "$archive" $acls_param --one-file-system -p --remove-files --exclude=ttyrec "$dir" 2>/dev/null; ret=$? - set -e + _log "Found a misowned lastlog file as $file, owned by $file_owner who's home is $owner_home" - if [ $ret -ne 0 ]; then - # $? can be 2 if we can't delete because ttyrec dir remains so it might not be a problem - if [ $ret -eq 2 ] && [ -s "$archive" ] && [ -d "$dir" ] && [ "$(find "$dir" -name ttyrec -prune -o -print | wc -l)" = 1 ]; then - # it's ok. we chown all to root to avoid orphan UID/GID and we let the backup script take care of those - # if we still have files under $dir/ttyrec, chown all them to root:root to avoid orphan UID/GID, - # and just wait for them to be encrypted/rsynced out of the bastion by the usual ttyrec archiving script - _log "Archived $dir to $archive" - chmod 0 "$archive" + orphanhome="$(dirname "$file")" - chown -R root:root "$dir" - _warn "Some files remain in $dir, we chowned everything to root" - else - _err "Couldn't archive $dir to $archive" + # ensure that the only other present files in this home are: ttyrec/*, *.log and *.sqlite + otherfiles=$(find "$orphanhome/" -mindepth 1 -name ttyrec -prune -o -name tmp -prune \ + -o ! -name lastlog ! -name "*.log" ! -name "*.sqlite" -print | head -n 1) + if [ -n "$otherfiles" ]; then + _warn "... at least another file has been found in $orphanhome, skipping" + continue fi - else - _log "Archived $dir to $archive" - chmod 0 "$archive" - fi -done < <(find /home/ -mindepth 1 -maxdepth 1 -type d -nouser -nogroup -mmin +3 -print0) + + _log "Proceeding in archiving $orphanhome..." + archive_dir "$orphanhome" + done < <(find /home/ -mindepth 2 -maxdepth 2 -type f -name lastlog -print0) +fi # there are also temporary files that might not be cleaned when an account disappears, # so check for those here diff --git a/tests/functional/tests.d/200-scripts.sh b/tests/functional/tests.d/200-scripts.sh index eb72c2c1e..348682b5e 100644 --- a/tests/functional/tests.d/200-scripts.sh +++ b/tests/functional/tests.d/200-scripts.sh @@ -108,6 +108,7 @@ testsuite_scripts() contain "Done" nocontain "WARN:" nocontain "ERROR:" + nocontain "Unexpected termination" # encrypt rsync (nothing to encrypt) @@ -116,6 +117,7 @@ testsuite_scripts() contain "Done" nocontain "WARN:" nocontain "ERROR:" + nocontain "Unexpected termination" # ttyrec subfolders cleanup success ttyrec_cleanup $r0 /opt/bastion/bin/cron/osh-remove-empty-folders.sh @@ -146,9 +148,46 @@ testsuite_scripts() nocontain "ERROR:" nocontain "Unexpected termination" + # rename account + script account_rename $r0 /opt/bastion/bin/admin/rename-account.sh $account1 $account2 '/dev/null; then + if scp -O -S $(command -v true) a: b 2>/dev/null; then echo "scp: will use new version params" scp_options="-O" else