
PK 
| ADDRLIN : /usr/sbin/ |
|
|
| Current File : //usr/sbin/onesysprep |
#!/bin/sh
# ---------------------------------------------------------------------------- #
# Copyright 2020-2021, OpenNebula Project, OpenNebula Systems #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# ---------------------------------------------------------------------------- #
# This script tries to reimplement libguestfs.org's sysprep tool while trying
# to support as much sysprep's operations (below) as possible. The intention
# behind this tool is to provide the almost same functionality but from within
# the running VM (as opposed to the original libguest's sysprep).
#
# On top of the original operations there are extra new operations - prefixed
# with 'one-' to avoid a naming conflict. These are specific to this tool and
# extend the original functionality which was too much redhat-centric.
set -e
################################################################################
# globals
#
CMD=$(basename "$0")
CMDLINE="${0} ${*}"
# here are declared all sysprep operations - keep the following format:
# <operation>:<default>:<comment>
# for each operation there should be corresponding function:
# op_<operation-with_underscores>
ALL_SYSPREP_OPERATIONS='
abrt-data :1: Remove the crash data generated by ABRT
backup-files :1: Remove editor backup files from the guest
bash-history :1: Remove the bash history in the guest
blkid-tab :1: Remove blkid tab in the guest
ca-certificates :0: Remove CA certificates in the guest
crash-data :1: Remove the crash data generated by kexec-tools
cron-spool :1: Remove user at-jobs and cron-jobs
customize :1: (NOT IMPLEMENTED)
dhcp-client-state :1: Remove DHCP client leases
dhcp-server-state :1: Remove DHCP server leases
dovecot-data :1: Remove Dovecot (mail server) data
firewall-rules :0: Remove the firewall rules
flag-reconfiguration :0: Flag the system for reconfiguration
fs-uuids :0: (NOT IMPLEMENTED)
kerberos-data :0: Remove Kerberos data in the guest
logfiles :1: Remove many log files from the guest
lvm-uuids :1: (NOT IMPLEMENTED)
machine-id :1: Remove the local machine ID
mail-spool :1: Remove email from the local mail spool directory
net-hostname :1: Remove HOSTNAME and DHCP_HOSTNAME
net-hwaddr :1: Remove HWADDR (hard-coded MAC address)
pacct-log :1: Remove the process accounting log files
package-manager-cache :1: Remove package manager cache
pam-data :1: Remove the PAM data in the guest
passwd-backups :1: Remove /etc/passwd- and similar backup files
puppet-data-log :1: Remove the data and log files of puppet
rh-subscription-manager :1: Remove the RH subscription manager files
rhn-systemid :1: Remove the RHN system ID
rpm-db :1: Remove host-specific RPM database files
samba-db-log :1: Remove the database and log files of Samba
script :1: (NOT IMPLEMENTED)
smolt-uuid :1: Remove the Smolt hardware UUID
ssh-hostkeys :1: Remove the SSH host keys in the guest
ssh-userdir :1: Remove ".ssh" directories in the guest
sssd-db-log :1: Remove the database and log files of sssd
tmp-files :1: Remove temporary files
udev-persistent-net :1: Remove udev persistent net rules
user-account :0: Remove the user accounts in the guest
utmp :1: Remove the utmp file
yum-uuid :1: Remove the yum UUID
one-cleanup :1: Remove OpenNebula-owned working directories
one-shell-history :1: Remove the .history file
one-hostname :1: Remove hostname and fix hosts file
one-resolvconf :1: Remove nameservers
one-network :1: Nuke all the networking configuration
one-zerofill :0: Fill the free space with zeroes and discard it
one-trim :1: Trim the discarded/unused space
'
################################################################################
# functions
#
usage()
(
cat <<EOF
NAME
${CMD} - OpenNebula System Preparation Tool
DESCRIPTION
Tool for cleaning up the system from sensitive or undesirable information
and preparing it as a base for cloning or instantiation of VMs.
It tries to meet the functionality of the libguestfs sysprep tool with the
exception of being able to run from within already running virtual machine.
While the libguestfs sysprep operates on the poweroffed system and modifies
its disk image from outside. The libguestfs approach is safer but it also
means that not all images and their filesystems can be supported.
The idea is to prepare image for further usage - for example like this:
1. instantiate the VM on top of a persistent image
2. create necessary changes (updates, edits, add data, files, etc.)
3. optionally switch to the single user mode (if not updating packages)
4. run '${CMD}'
5. poweroff (manually or via ${CMD})
6. and lastly switch the image to non-persistent (!)
USAGE
${CMD} --help
This help
${CMD} --list-operations
It will list all supported operations and mark the default one with the
asterisk '*'.
${CMD} [--operations <operation-list>]... [--yes] [--strict] [--update]
[--selinux-relabel] [--remove-user-accounts <user-list>]
[--keep-user-accounts <user-list>] [--root-password <selector>]
[--password <user:selector>] [--verbose] [--poweroff]
'--operations'
If no '--operations' nor one option 'yes' is used then ${CMD} will
first ask you interactively if you really want to run this command and
if so then it will proceed with the execution of the default set of
operations.
With provided '--operations' or one option 'yes' it will execute
(without prompting you) the desired operations or default ones if no
other is specified.
'--operations' can be used more than once - the lists are then spliced
together.
The argument is a comma separated list of operations where amongst the
actual names of the operations are also recognized special:
'default' and 'all'
Their meaning is obvious. For the list of all supported operations
use the '--list-operations'.
Operation can also be prefixed with the '-' which will mean that this
particular operation will not be executed even if it would otherwise.
'--yes'
Never ask for permission - run non-interactively - otherwise in some
circumstances (for your own safety) program will refuse to continue
until you type 'yes'.
'--strict'
Abort the run of this program if system is not recognized.
'--update'
It will update installed packages.
'--selinux-relabel'
It will relabel all files if the system supports SElinux.
'--remove-user-accounts'
It will add users to the list of accounts to be deleted. When a user is
listed in both '--remove-user-accounts' and '--keep-user--accounts'
then the latter has a precedence and such user is preserved.
'--keep-user-accounts'
When there is a request for user account deletion either via previous
parameter or by 'user-account' operation - users specified here will be
whitelisted and preserved in all cases.
'--root-password'
This parameter manages root account - selector here is one of:
file:FILENAME
password:PASSWORD
random
disabled
locked
locked:file:FILENAME
locked:password:PASSWORD
locked:random
locked:disabled
'--password'
Same as with the root password but selector is on-top prefixed with the
username, e.g.: username:locked:disabled
'--verbose'
Print executed commands and show their output.
'--poweroff'
At the end of the run of ${CMD} - poweroff the system.
Although no such parameter is in virt-sysprep (because it operates on
the image of an already poweroffed system) - it makes sense here.
EXAMPLES
${CMD}
Run all default operations but only if explicitly replied 'YES'
${CMD} --operations default,-cron-spool
Run all default operations EXCEPT the cron-spool
${CMD} --operations machine-id,ca-certificates
Run only these two operations
${CMD} --yes --poweroff --root-password locked:disabled
Run all default operations, delete and lock root password and
poweroff the system immediately after - no questions asked
${CMD} --yes --strict --operations all,-one-zerofill
Run all the available operations *EXCEPT* the 'one-zerofill' and
without asking but abort promptly if system is not recognized.
BEWARE: 'one-zerofill' operation will fill up the free space of the VM
with zeroes to claim the unused space. By doing this the disk can be
effectively sparsified by the trim command in the next step. The issue
is that zerofill must basically claim the size of the whole disk - if
the definition of your disk image does not have discard='unmap' (on
QEMU/KVM for example) and the host system does not support it then you
will end up with *MAXIMIZED* disk image size instead of the shrinked
size...
EOF
)
print_help()
(
printf "${SETCOLOR_ON_COMMAND}[!] try help: %s${SETCOLOR_OFF}\n" \
"${CMD} --help" >&2
)
msg()
(
printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}: ${*}\n" \
"$CMD"
)
err()
(
{
printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}" \
"$CMD"
printf " ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n" \
"[!] ERROR: ${*}"
} >&2
)
warn()
(
{
printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}" \
"$CMD"
printf " ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n" \
"[!] WARNING: ${*}"
} >&2
)
# arg(s): <command> <arg>...
run_cmd()
{
if is_one_option 'verbose' ; then
# verbose command execution
printf "${SETCOLOR_ON_HIGHLIGHT2}[%s]${SETCOLOR_OFF}:" \
"$CMD"
printf " ${SETCOLOR_ON_COMMAND}%s${SETCOLOR_OFF}\n" \
"${*}"
fi
if ! "$@" && [ "$IGNORE_ERROR" != 'yes' ] ; then
err "Command failed - ABORT"
exit 1
fi
}
# trivial test of awk command capabilities and compliance - it will output
# either YES or NO
is_awk_correct()
(
_awk_result=$(echo 'abcABC xyz123.' | awk '
{
sub(/[[:upper:]]+[[:space:]]+[[:alnum:]]+/, "");
print;
}')
if [ "${_awk_result}" = 'abc.' ] ; then
echo 'YES'
else
echo 'NO'
fi
)
# arg: [normal|summary]
# if used with argument 'summary' then it will print out summary of supported
# operations - otherwise it will output parseable: <op>:<default>
parse_sysprep_operations()
(
echo "$ALL_SYSPREP_OPERATIONS" | awk -v output="${1:-normal}" '
BEGIN {
FS = ":";
count=0;
}
{
if ($0 == "")
next;
op = $1;
sub(/^[ ]*/, "", op);
sub(/[ ]*$/, "", op);
default_op = $2;
sub(/^[ ]*/, "", default_op);
sub(/[ ]*$/, "", default_op);
comment = $3;
sub(/^[ ]*/, "", comment);
sub(/[ ]*$/, "", comment);
count++;
ops[count] = op;
ops_comment[op] = comment;
ops_default[op] = default_op;
}
END {
if (output == "normal"){
for (i = 1; i <= count; i++) {
op = ops[i];
printf("%s:%s\n", op, ops_default[op]);
}
} else if (output == "summary") {
max_length = 0;
for (i = 1; i <= count; i++) {
if (length(ops[i]) > max_length)
max_length = length(ops[i]);
}
for (i = 1; i <= count; i++) {
op = ops[i];
if (ops_default[op] == "1")
asterisk = "*";
else
asterisk = " ";
# this sadly is not supported in busybox...
# printf("%s %-*s %s\n", asterisk, max_length, op, ops_comment[op]);
# busybox workaround
extra_spaces_count = max_length - length(op);
extra_spaces = "";
for (s = 1; s <= extra_spaces_count; s++)
extra_spaces = extra_spaces " ";
printf("%s %s%s %s\n", asterisk, op, extra_spaces, ops_comment[op]);
}
}
}
'
)
list_operations()
(
printf "LIST OF ALL SUPPORTED OPERATIONS:\n\n"
printf "(asterisk '*' designates a default operation)\n\n"
parse_sysprep_operations summary
)
ask_yes()
(
_reply=''
while [ -z "$_reply" ] ; do
printf "%s (${SETCOLOR_ON_COMMAND}%s${SETCOLOR_OFF})? " \
"Do you really want to continue" \
"'y/yes/Y/YES'"
read -r _reply
case "$_reply" in
y|Y|yes|YES)
return 0
;;
esac
done
return 1
)
ask_to_run_sysprep()
{
# shellcheck disable=SC2059
printf "${SETCOLOR_ON_FAILURE}"
printf "[!] BEWARE: This will erase some system data!\n"
printf "[!] If you are not certain what this program does - try help:\n"
printf " %s --help\n\n" "${CMD}"
# shellcheck disable=SC2059
printf "${SETCOLOR_OFF}"
if ! is_one_option 'yes' && ! ask_yes ; then
msg "ABORTED"
exit 0
fi
}
ask_to_enter_single_user_mode()
{
warn "You are about to enter the single user mode - this program will be terminated!"
printf "[!] NOTE: You must invoke this program again once you reach the single user mode:\n"
printf " %s\n\n" "${CMDLINE}"
if ! is_one_option 'yes' && ! ask_yes ; then
msg "ABORTED"
exit 0
fi
}
ask_to_poweroff()
{
warn "You are about to poweroff this system!"
if ! is_one_option 'yes' && ! ask_yes ; then
msg "ABORTED"
exit 0
fi
}
ask_to_proceed_on_unsupported_system()
{
if ! is_one_option 'yes' && ! ask_yes ; then
msg "ABORTED"
exit 0
fi
warn "We will continue on the best-effort basis..."
}
# arg: all|default|<op>
# it will return (based on argument):
# list of all operations
# list of all default operations
# operation if exists
# empty string if operation does not exist
get_operations()
(
case "$1" in
all)
parse_sysprep_operations normal | cut -d: -f1
;;
default)
parse_sysprep_operations normal | sed -n 's/^\(.*\):1$/\1/p'
;;
*)
parse_sysprep_operations normal | sed -n "s/^\(${1}\):[0-1]\$/\1/p"
;;
esac
)
sanity_check()
{
# test the sanity of an awk implementation
_ONE_VALID_AWK=$(is_awk_correct)
export _ONE_VALID_AWK
if [ "${_ONE_VALID_AWK}" = 'NO' ] ; then
warn "The currently used awk command does not support POSIX:"
printf "\n%s\n\n" "$(awk -W version 2>&1 || true)"
printf "[!] NOTE: Install an alternative (e.g. gawk) or some operations may be skipped!\n\n"
fi
}
# this will try to guess the system it is running on and if it is supported by
# this script
syscheck()
{
# detect the OS
if [ -f /etc/os-release ] ; then
ID=
# shellcheck disable=SC1091
. /etc/os-release
_ONE_OS_ID=$(echo "$ID" | tr '[:upper:]' '[:lower:]')
# check for legacy RHEL/CentOS 6
elif [ -f /etc/centos-release ]; then
_ONE_OS_ID='centos'
elif [ -f /etc/redhat-release ]; then
_ONE_OS_ID='rhel'
else
_ONE_OS_ID=$(uname | tr '[:upper:]' '[:lower:]')
fi
# return success if listed here:
#
# (I am breaking these into more cases just to avoid having long lines)
case "$_ONE_OS_ID" in
alpine|altlinux)
return 0
;;
debian|ubuntu|devuan)
return 0
;;
fedora|centos|rhel|almalinux|ol|rocky|amzn)
return 0
;;
opensuse*|sles|sled)
return 0
;;
freebsd)
return 0
;;
esac
# [!] we failed the check - this system is not supported...
# print the warning
warn "This system ('${_ONE_OS_ID:-UNKNOWN}') is not supported!"
# possibly abort
if is_one_option 'strict' ; then
err "System check failed while 'strict' option used - ABORT"
exit 1
else
ask_to_proceed_on_unsupported_system
return 0
fi
return 1
}
# it will either switch to the single user mode (if possible) or it will return
# immediately when it is already in one
enter_single_mode()
{
# are we already switched?
case "$_ONE_OS_ID" in
alpine)
_runlevel=$(rc-status -r)
if [ "$_runlevel" = 'single' ] ; then
return 0
fi
;;
debian|ubuntu|devuan|fedora|centos|rhel|almalinux|ol|rocky|altlinux|opensuse*)
_runlevel=$(runlevel | cut -d" " -f2)
case "$_runlevel" in
1|S)
return 0
;;
esac
;;
freebsd)
warn "FreeBSD has no (clever) means of detecting the current runlevel - SKIP"
printf "[!] NOTE: You can enter the single user mode prior to running this program:\n"
printf " shutdown now\n\n"
if ! is_one_option 'yes' && ! ask_yes ; then
msg "ABORTED"
exit 0
fi
;;
*)
warn "Unable to detect the runlevel on this system ('${_ONE_OS_ID:-UNKNOWN}')"
printf "[!] NOTE: You can abort this script and switch manually if you wish...\n\n"
if ! is_one_option 'yes' && ! ask_yes ; then
msg "ABORTED"
exit 0
fi
;;
esac
# we are not in single user mode - so we will attempt to enter it...
case "$_ONE_OS_ID" in
alpine)
ask_to_enter_single_user_mode
msg "Entering single user mode..."
rc single
;;
debian|ubuntu|devuan|fedora|centos|rhel|almalinux|ol|rocky|altlinux|opensuse*)
ask_to_enter_single_user_mode
msg "Entering single user mode..."
telinit 1
;;
freebsd)
:
# TODO: for future reference:
#shutdown now
## enable rw on rootfs again
#/sbin/mount -urw /
;;
esac
}
# arg: <passwd-selector-list>
# return: user:locked:disabled:file:password:random:value...
parse_password_arguments()
{
_raw_password_list=$(echo "$1" | tr ',' ' ')
_parsed_password_list=
# selector should have format:
# <user>:[locked:]disabled|file:<filename>|password:<passwd>|random
for _selector in ${_raw_password_list} ; do
_username=$(echo "$_selector" | cut -d":" -f1)
_locked_or_atr=$(echo "$_selector" | cut -d":" -f2)
_atr_or_value=$(echo "$_selector" | cut -d":" -f3)
_value=$(echo "$_selector" | cut -d":" -f4)
_error=$(echo "$_selector" | cut -d":" -f5)
# simple check to catch extra fields
if [ -n "$_error" ] ; then
err "Wrong password selector: ${_selector} - ABORT"
exit 1
fi
# selector attributes
_locked=
_disabled=
_file=
_password=
_random=
case "$_locked_or_atr" in
locked)
_locked=yes
;;
disabled)
_disabled=yes
;;
file)
_file=yes
;;
password)
_password=yes
;;
random)
_random=yes
;;
*)
err "Wrong password selector: ${_selector} - ABORT"
exit 1
;;
esac
case "$_atr_or_value" in
'')
# is missing a value?
if [ -n "${_file}${_password}" ] ; then
# selector is not complete
err "Wrong password selector: ${_selector} - ABORT"
exit 1
fi
;;
disabled|file|password|random)
# it can be non-locked attribute or a value
if [ -n "${_locked}" ] ; then
# locked was set - so this is attribute
eval "_${_atr_or_value}=yes"
elif [ -z "${_value}${_disabled}${_random}" ] ; then
# it must be value
_value="$_atr_or_value"
else
err "Wrong password selector: ${_selector} - ABORT"
exit 1
fi
;;
*)
# it must be value or error
if [ -z "${_locked}${_value}${_disabled}${_random}" ] ; then
_value="$_atr_or_value"
else
err "Wrong password selector: ${_selector} - ABORT"
exit 1
fi
;;
esac
# we should have all the pieces...
_passwd_arg="${_username}:${_locked}:${_disabled}:${_file}:${_password}:${_random}:${_value}"
_parsed_password_list="${_parsed_password_list} ${_passwd_arg}"
done
echo "$_parsed_password_list"
}
# arg: <length>
# shellcheck disable=SC2120
gen_password()
{
pw_length="${1:-16}"
new_pw=''
while true ; do
if command -v pwgen >/dev/null ; then
new_pw=$(pwgen -s "${pw_length}" 1)
break
elif command -v openssl >/dev/null ; then
# shellcheck disable=SC2086
new_pw="${new_pw}$(openssl rand -base64 ${pw_length} | tr -dc '[:alnum:]')"
else
new_pw="${new_pw}$(head /dev/urandom | tr -dc '[:alnum:]')"
fi
# shellcheck disable=SC2000
# shellcheck disable=SC2086
[ "$(echo $new_pw | wc -c)" -ge "$pw_length" ] && break
done
echo "$new_pw" | cut -c1-"${pw_length}"
}
# arg: <user:locked:disabled:file:password:random:value...>
set_passwords()
{
for _selector in ${1} ; do
_username=$(echo "$_selector" | cut -d":" -f1)
_locked=$(echo "$_selector" | cut -d":" -f2)
_disabled=$(echo "$_selector" | cut -d":" -f3)
_file=$(echo "$_selector" | cut -d":" -f4)
_password=$(echo "$_selector" | cut -d":" -f5)
_random=$(echo "$_selector" | cut -d":" -f6)
_value=$(echo "$_selector" | cut -d":" -f7)
# skip if user does not exist
_uid=$(id -u "$_username" 2>/dev/null || true)
if [ -z "$_uid" ] ; then
msg "Password change: the user '${_username}' does not exist"
continue
fi
case "$(uname | tr '[:upper:]' '[:lower:]')" in
linux)
if [ -n "$_disabled" ] ; then
msg "Password delete: ${_username}"
run_cmd passwd -d "$_username"
elif [ -n "$_file" ] ; then
if [ -f "$_value" ] ; then
msg "Password change: ${_username}"
_newpasswd=$(sed -n 1p "$_value")
if command -v chpasswd > /dev/null ; then
# busybox
echo "${_username}:${_newpasswd}" | run_cmd chpasswd
else
echo "${_newpasswd}" | run_cmd passwd --stdin "$_username"
fi
else
err "Password file '${_value}' does not exist - ABORT"
exit 1
fi
elif [ -n "$_password" ] ; then
msg "Password change: ${_username}"
if command -v chpasswd > /dev/null ; then
# busybox
echo "${_username}:${_value}" | run_cmd chpasswd
else
echo "${_value}" | run_cmd passwd --stdin "$_username"
fi
elif [ -n "$_random" ] ; then
msg "Password change: ${_username}"
_newpasswd=$(gen_password)
if command -v chpasswd > /dev/null ; then
# busybox
echo "${_username}:${_newpasswd}" | run_cmd chpasswd
else
echo "${_newpasswd}" | run_cmd passwd --stdin "$_username"
fi
fi
if [ -n "$_locked" ] ; then
msg "Password lock: ${_username}"
run_cmd passwd -l "$_username"
fi
;;
freebsd)
if [ -n "$_disabled" ] ; then
msg "Password delete: ${_username}"
run_cmd pw usermod -n "$_username" -h -
elif [ -n "$_file" ] ; then
if [ -f "$_value" ] ; then
msg "Password change: ${_username}"
sed -n 1p "$_value" | \
run_cmd pw usermod -n "$_username" -h 0
else
err "Password file '${_value}' does not exist - ABORT"
exit 1
fi
elif [ -n "$_password" ] ; then
msg "Password change: ${_username}"
echo "$_value" | \
run_cmd pw usermod -n "$_username" -h 0
elif [ -n "$_random" ] ; then
msg "Password change: ${_username}"
_newpasswd=$(gen_password)
echo "$_newpasswd" | \
run_cmd pw usermod -n "$_username" -h 0
fi
if [ -n "$_locked" ] ; then
msg "Password lock: ${_username}"
IGNORE_ERROR=yes run_cmd pw lock "$_username"
IGNORE_ERROR=
fi
;;
*)
warn "Password change: this system ('$(uname)') is not supported"
;;
esac
done
}
run_prep()
{
# run compatibiltity/support check and abort here if 'strict' is used
syscheck
# --update ?
if is_one_option 'update' ; then
msg "Update the system..."
if command -v apk >/dev/null ; then
run_cmd apk --update-cache upgrade
elif command -v apt-get >/dev/null ; then
DEBIAN_FRONTEND=noninteractive
export DEBIAN_FRONTEND
run_cmd apt-get -qy update
run_cmd apt-get -qy \
-o Dpkg::Options::="--force-confdef" \
-o DPkg::Options::="--force-confold" \
upgrade
run_cmd apt-get -qy autoremove
elif command -v dnf >/dev/null ; then
run_cmd dnf -y --best upgrade
elif command -v yum >/dev/null ; then
run_cmd yum -y --obsoletes update
elif command -v zypper >/dev/null ; then
run_cmd zypper --non-interactive update --auto-agree-with-licenses
elif command -v pkg >/dev/null ; then
# TODO: FreeBSD returns error when no update is available...
run_cmd freebsd-update --not-running-from-cron fetch || true
run_cmd freebsd-update --not-running-from-cron install || true
else
warn "No package manager found - SKIP"
fi
fi
# --remove-user-accounts ?
if [ -n "$ARG_USERS_REMOVE" ] ; then
remove_arbitrary_users "$ARG_USERS_REMOVE" "$ARG_USERS_KEEP"
fi
# --root-password|--password ?
if [ -n "$ARG_PASSWORD" ] ; then
set_passwords "$ARG_PASSWORD"
fi
# TODO: this requires too much fiddling for just saving a user one command
# if the single runlevel is requested then try to switch to it...or maybe
# we already did that and we just want to continue...
#if is_one_option 'single' ; then
# enter_single_mode
#fi
}
run_last()
{
# --selinux-relabel ?
if is_one_option 'selinux-relabel' ; then
msg "Try to relabel SELinux context..."
# is this SELinux system?
if [ "$(uname | tr '[:upper:]' '[:lower:]')" = 'linux' ] && \
command -v restorecon >/dev/null ;
then
if command -v fixfiles >/dev/null ; then
run_cmd fixfiles -f relabel
else
run_cmd touch /.autorelabel
fi
else
warn "This not a SELinux enabled system - SKIP"
fi
fi
# this does not hurt - sync to disk
if command -v sync >/dev/null ; then
msg "Running 'sync'..."
sync
fi
# --poweroff?
if is_one_option 'poweroff' ; then
# firstly ask if not 'yes'
ask_to_poweroff
msg "We will try to turn off the machine..."
case "$_ONE_OS_ID" in
freebsd)
run_cmd poweroff
;;
*)
if command -v poweroff >/dev/null ; then
run_cmd poweroff
elif command -v shutdown >/dev/null ; then
run_cmd shutdown -p now
elif command -v halt >/dev/null ; then
run_cmd halt -p
else
err "Poweroff requested but no relevant command was found!"
fi
;;
esac
fi
}
# arg: <list-of-operations>
run_ops()
{
_all_ops=$(echo "$1" | tr ',' ' ')
_whitelist=
_blacklist=
for _op in ${_all_ops} ; do
# is it blacklisted operation?
_black_op=$(echo "$_op" | sed -n 's/^-\(.*\)/\1/p')
if [ -n "$_black_op" ] ; then
# add excluded operation(s) to the blacklist
_ops=$(get_operations "$_black_op")
if [ -n "$_ops" ] ; then
_blacklist="${_blacklist} ${_ops}"
else
err "Unsupported operation expr.: ${_op}"
exit 1
fi
else
# extend the whitelist for requested operation(s)
_ops=$(get_operations "$_op")
if [ -n "$_ops" ] ; then
_whitelist="${_whitelist} ${_ops}"
else
err "Unsupported operation expr.: ${_op}"
exit 1
fi
fi
done
# deduplicate and sort the both lists
_whitelist=$(echo "$_whitelist" | tr ' ' '\n' | sort -u)
_blacklist=$(echo "$_blacklist" | tr ' ' '\n' | sort -u)
# filter out blacklisted operations and execute only the whitelisted ones
_ops=
_max_oplength=0
# loop over the ALL available operations to honor the original order
for _op in $(get_operations all) ; do
# NOTE: on FreeBSD newlines are not preserved in the variable which is
# strange because POSIX says that only trailing newlines are removed...
# if the operation is NOT whitelisted/requested then skip it
if ! echo "$_whitelist" | tr ' ' '\n' | grep -q "^${_op}\$" ; then
continue
fi
# if the operation is NOT blacklisted then add it to the final list
if ! echo "$_blacklist" | tr ' ' '\n' | grep -q "^${_op}\$" ; then
# find the longest operation name
if [ "${#_op}" -gt "$_max_oplength" ] ; then
_max_oplength="${#_op}"
fi
_ops="${_ops} ${_op}"
fi
done
# export arguments into operation subshells
export ARG_ONE_OPTION_LIST
export ARG_OPS_LIST
export ARG_PASSWORD
export ARG_USERS_KEEP
export ARG_USERS_REMOVE
# execute requested operations one by one
for _op in ${_ops} ; do
_op_func=$(echo "$_op" | tr '-' '_')
if is_one_option 'verbose' ; then
# run verbosely
printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}:" \
"Run operation"
printf " ${SETCOLOR_ON_COMMAND}%s${SETCOLOR_OFF}\n" \
"${_op}"
# if-wrap
if eval "op_${_op_func}" ; then
_op_func_return_code=$?
else
_op_func_return_code=$?
fi
case "${_op_func_return_code}" in
0)
printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}" \
"Result"
printf " ... ${SETCOLOR_ON_OK}%s${SETCOLOR_OFF}\n\n" \
"OK"
;;
1)
printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}" \
"Result"
printf " ... ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n\n" \
"FAILED"
;;
2)
printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}" \
"Result"
printf " ... ${SETCOLOR_ON_FAILURE}%s${SETCOLOR_OFF}\n\n" \
"SKIPPED"
;;
esac
else
# run non-verbosely
printf "${SETCOLOR_ON_HIGHLIGHT}%s${SETCOLOR_OFF}:" \
"Run operation"
printf " ${SETCOLOR_ON_COMMAND}%-*s${SETCOLOR_OFF} ... " \
"$_max_oplength" \
"${_op}"
# if-wrap
if _op_func_output=$(eval "op_${_op_func}" 2>&1) ; then
_op_func_return_code=$?
else
_op_func_return_code=$?
fi
case "${_op_func_return_code}" in
0)
# shellcheck disable=SC2059
printf "${SETCOLOR_ON_OK}OK${SETCOLOR_OFF}\n"
;;
1)
# shellcheck disable=SC2059
printf "${SETCOLOR_ON_FAILURE}FAILED${SETCOLOR_OFF}\n"
echo "$_op_func_output"
;;
2)
# shellcheck disable=SC2059
printf "${SETCOLOR_ON_FAILURE}SKIPPED${SETCOLOR_OFF}\n"
echo "$_op_func_output"
;;
esac
fi
done
}
# arg: <option-list>
# this function initialize a global env. variable _ONE_OPTIONS based on the
# recognized options and force an abort when an unknown option is found
#
# NOTE: this is left as it is for when the need for '--one-options' rise again
# - that is why the options are replicated here...
#
# _ONE_OPTIONS is then used inside the is_one_option function for convenience
parse_one_options()
{
# comb the list
_all_options=$(echo "$1" | \
sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \
sort -u)
# leave empty
_ONE_OPTIONS=
_abort=
for _option in ${_all_options} ; do
case "$_option" in
yes|strict|verbose|update|poweroff|selinux-relabel)
_ONE_OPTIONS="${_ONE_OPTIONS} ${_option}"
;;
*)
err "Unknown option: ${_option}"
_abort=yes
;;
esac
done
# there were found unknown options - abort
if [ -n "$_abort" ] ; then
print_help
exit 1
fi
}
# arg: <option>
# it will simply check if option from argument was set (inside _ONE_OPTIONS)
is_one_option()
(
for _option in ${_ONE_OPTIONS} ; do
if [ "$1" = "$_option" ] ; then
return 0
fi
done
return 1
)
# inspired here:
# https://unix.stackexchange.com/questions/485070/all-round-100-portable-terminal-color-support-check
# and modified for alpine's busybox (which supports colors)
is_terminal_colorful()
{
{
command -v tput && { tput setaf || tput AF ; }
} >/dev/null 2>&1
# shellcheck disable=SC2181
if [ $? -eq 0 ] ; then
return 0
fi
# naive workaround for systems without ncurses
test "$TERM" = 'xterm-256color'
return $?
}
# args: <remove-user-list> <keep-user-list>
# if a user is in both lists then preserving will take precedence
# TODO: ERRROR FreeBSD does not save newlines in vars - echo | grep does not work!!!!!!!!!§
remove_arbitrary_users()
{
# comb the lists
_remove_list=$(echo "$1" | \
sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \
sort -u)
_keep_list=$(echo "$2" | \
sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \
sort -u)
# finalize the remove list by removing the preserved users
_users=
for _user in ${_remove_list} ; do
# NOTE: on FreeBSD newlines are not preserved in the variable which is
# strange because POSIX says that only trailing newlines are removed...
if ! echo "$_keep_list" | tr ' ' '\n' | grep -q "^${_user}\$" ; then
# user is not preserved so we can delete it if exists
_uid=$(id -u "$_user" 2>/dev/null || true)
if [ -n "$_uid" ] ; then
_users="${_users} ${_user}:${_uid}"
else
msg "Removing user '${_user}': not present on the system"
fi
fi
done
case "$(uname | tr '[:upper:]' '[:lower:]')" in
linux)
for _user in ${_users} ; do
_username=$(echo "$_user" | cut -d":" -f1)
_uid=$(echo "$_user" | cut -d":" -f2)
msg "Removing user '${_username}'..."
# keep deluser first: alpine has both commands but does not
# have the --selinux-user option for userdel...
if command -v deluser >/dev/null ; then
run_cmd deluser --remove-home "$_username"
elif command -v userdel >/dev/null ; then
run_cmd userdel --remove --selinux-user "$_username"
fi
# wipe-out all deleted user's files on the system
IGNORE_ERROR=yes run_cmd find / -depth -user "$_uid" -delete
IGNORE_ERROR=
done
;;
freebsd)
for _user in ${_users} ; do
_username=$(echo "$_user" | cut -d":" -f1)
_uid=$(echo "$_user" | cut -d":" -f2)
msg "Removing user '${_username}'..."
run_cmd pw userdel "$_username" -r
# wipe-out all deleted user's files on the system
IGNORE_ERROR=yes run_cmd find / -depth -user "$_uid" -delete
IGNORE_ERROR=
done
;;
*)
warn "Removing users: this system ('$(uname)') is not supported"
;;
esac
}
################################################################################
# sysprep operations
#
# reimplemented operations
op_abrt_data()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf /var/spool/abrt/*
)
op_backup_files()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op find / \( -name '*.bak' -o -name '*~' \) -delete || true
)
op_bash_history()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf /home/*/.bash_history /root/.bash_history
)
op_blkid_tab()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/var/run/blkid.tab \
/var/run/blkid.tab.old \
/etc/blkid/blkid.tab \
/etc/blkid/blkid.tab.old \
/etc/blkid.tab \
/etc/blkid.tab.old \
/dev/.blkid.tab \
/dev/.blkid.tab.old \
;
)
op_ca_certificates()
(
run_op() { echo "+ ${*}" ; "$@" ; }
for f in \
/etc/pki/CA/certs/*.crt \
/etc/pki/CA/crl/*.crt \
/etc/pki/CA/newcerts/*.crt \
/etc/pki/CA/private/*.key \
/etc/pki/tls/private/*.key \
/etc/pki/tls/certs/*.crt \
; do
case "$f" in
/etc/pki/tls/certs/ca-bundle.crt)
# skip
:;;
/etc/pki/tls/certs/ca-bundle.trust.crt)
# skip
:;;
*)
# all else delete
run_op rm -vf "$f"
;;
esac
done
)
op_crash_data()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf /var/crash/* /var/log/dump/*
)
op_cron_spool()
(
run_op() { echo "+ ${*}" ; "$@" ; }
for d in \
/var/spool/cron/ \
/var/spool/cron/atjobs/ \
/var/spool/atjobs/ \
/var/spool/atspool/ \
/var/spool/at/ \
; do
if [ -d "$d" ] ; then
run_op find "$d" -type f -not -name .SEQ -delete
find "$d" -type f -name .SEQ -print | while read -r _seq_file ; do
echo "+ truncate ${_seq_file}"
cat /dev/null > "$_seq_file"
done
fi
done
)
# TODO: should we support customize?
op_customize()
(
echo NOOP
)
op_dhcp_client_state()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf /var/lib/dhclient/* /var/lib/dhcp/*
)
op_dhcp_server_state()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf /var/lib/dhcpd/*
)
op_dovecot_data()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf /var/lib/dovecot/*
)
op_firewall_rules()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/etc/sysconfig/iptables \
/etc/firewalld/services/* \
/etc/firewalld/zones/* \
;
)
op_flag_reconfiguration()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op touch /.unconfigured
)
# TODO: is this possible on mounted fs?
op_fs_uuids()
(
echo NOOP
)
op_kerberos_data()
(
run_op() { echo "+ ${*}" ; "$@" ; }
for f in /var/kerberos/krb5kdc/* ; do
case "$f" in
/var/kerberos/krb5kdc/kadm5.acl)
# skip
:;;
/var/kerberos/krb5kdc/kdc.conf)
# skip
:;;
*)
# all else delete
run_op rm -vf "$f"
;;
esac
done
)
# TODO: can this be improved? Stopping all services, syslog etc.?
# TODO: can the list be extended? This is copy-pasted from the latest libguestfs
op_logfiles()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf \
/var/log/*.log* \
/var/log/audit/* \
/var/log/btmp* \
/var/log/cron* \
/var/log/dmesg* \
/var/log/lastlog* \
/var/log/maillog* \
/var/log/mail/* \
/var/log/messages* \
/var/log/secure* \
/var/log/spooler* \
/var/log/tallylog* \
/var/log/wtmp* \
/var/log/apache2/*_log \
/var/log/apache2/*_log-* \
/var/log/ntp \
/var/log/tuned/tuned.log \
/var/log/debug* \
/var/log/syslog* \
/var/log/faillog* \
/var/log/firewalld* \
/var/log/grubby* \
/var/log/xferlog* \
/var/log/BackupPC/LOG \
/var/log/ceph/*.log \
/var/log/chrony/*.log \
/var/log/cups/*_log* \
/var/log/glusterfs/*glusterd.vol.log \
/var/log/glusterfs/glusterfs.log \
/var/log/httpd/*log \
/var/log/jetty/jetty-console.log \
/var/log/libvirt/libxl/*.log \
/var/log/libvirt/libvirtd.log \
/var/log/libvirt/lxc/*.log \
/var/log/libvirt/qemu/*.log \
/var/log/libvirt/uml/*.log \
/var/named/data/named.run \
/var/log/ppp/connect-errors \
/var/log/setroubleshoot/*.log \
/var/log/squid/*.log \
/var/lib/logrotate.status \
/root/install.log \
/root/install.log.syslog \
/root/anaconda-ks.cfg \
/root/anaconda-post.log \
/root/initial-setup-ks.cfg \
/root/original-ks.cfg \
/var/log/anaconda.syslog \
/var/log/anaconda/* \
/var/log/installer/* \
/var/cache/gdm/* \
/var/lib/AccountService/users/* \
/var/lib/fprint/* \
/var/cache/fontconfig/* \
/var/cache/man/* \
/var/log/sa/* \
/var/log/gdm/* \
/var/log/lightdm/* \
/var/log/ntpstats/* \
/etc/Pegasus/*.cnf \
/etc/Pegasus/*.crt \
/etc/Pegasus/*.csr \
/etc/Pegasus/*.pem \
/etc/Pegasus/*.srl \
/var/log/rhsm/* \
/var/log/journal/* \
/var/log/aptitude* \
/var/log/apt/* \
/var/log/exim4/* \
/var/log/ConsoleKit/* \
/var/log/landscape/* \
/var/log/unattended-upgrades/* \
;
)
# TODO: can ths be implemented?
op_lvm_uuids()
(
echo NOOP
)
op_machine_id()
(
run_op() { echo "+ ${*}" ; "$@" ; }
for f in /etc/machine-id /var/lib/dbus/machine-id ; do
if [ -f "$f" ] ; then
echo "+ truncate ${f}"
cat /dev/null > "$f"
fi
done
)
op_mail_spool()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf /var/spool/mail/* /var/mail/*
)
# this is redhat-centric but fixed in one-network and one-hostname
op_net_hostname()
(
run_op() { echo "+ ${*}" ; "$@" ; }
for f in /etc/sysconfig/network-scripts/ifcfg-* ; do
if [ -f "$f" ] ; then
run_op sed -i -e '/^HOSTNAME=/d' -e '/^DHCP_HOSTNAME=/d' "$f"
fi
done
)
# this is redhat-centric but fixed in one-network
op_net_hwaddr()
(
run_op() { echo "+ ${*}" ; "$@" ; }
for f in /etc/sysconfig/network-scripts/ifcfg-* ; do
if [ -f "$f" ] ; then
run_op sed -i -e '/^HWADDR=/d' "$f"
fi
done
)
op_pacct_log()
(
run_op() { echo "+ ${*}" ; "$@" ; }
if [ -f /var/account/pacct ] ; then
run_op rm -vf /var/account/pacct*
run_op touch /var/account/pacct
fi
if [ -f /var/log/account/pacct ] ; then
run_op rm -vf /var/log/account/pacct*
run_op touch /var/log/account/pacct
fi
)
# this one is extended from libguestfs sysprep version
op_package_manager_cache()
(
run_op() { echo "+ ${*}" ; "$@" ; }
if command -v apk >/dev/null ; then
run_op apk cache clean
elif command -v apt-get >/dev/null ; then
run_op apt-get clean
elif command -v apt >/dev/null ; then
run_op apt clean
elif command -v dnf >/dev/null ; then
run_op dnf clean all --enablerepo=\*
elif command -v yum >/dev/null ; then
run_op yum clean all --enablerepo=\*
elif command -v zypper >/dev/null ; then
run_op zypper clean --all
elif command -v pkg >/dev/null ; then
run_op pkg clean --all --yes
fi
for d in \
/var/cache/apt/archives/ \
/var/cache/dnf/ \
/var/cache/yum/ \
/var/cache/zypp* \
/var/cache/apk/* \
; do
if [ -d "$d" ] ; then
run_op find "$d" -type f -delete
fi
done
)
op_pam_data()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/var/run/console/* \
/var/run/faillock/* \
/var/run/sepermit/* \
;
)
op_passwd_backups()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/etc/group- \
/etc/gshadow- \
/etc/passwd- \
/etc/shadow- \
/etc/subuid- \
/etc/subgid- \
;
)
op_puppet_data_log()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/var/log/puppet/* \
/var/lib/puppet/*/* \
/var/lib/puppet/*/*/* \
;
)
op_rhn_systemid()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/etc/sysconfig/rhn/systemid \
/etc/sysconfig/rhn/osad-auth.conf \
;
)
op_rh_subscription_manager()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf /etc/pki/consumer/* /etc/pki/entitlement/*
)
op_rpm_db()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf /var/lib/rpm/__db.*
)
op_samba_db_log()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/var/log/samba/old/* \
/var/log/samba/* \
/var/lib/samba/*/* \
/var/lib/samba/* \
;
)
op_script()
(
echo NOOP
)
op_smolt_uuid()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/etc/sysconfig/hw-uuid \
/etc/smolt/uuid \
/etc/smolt/hw-uuid \
;
)
op_ssh_hostkeys()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf /etc/ssh/*_host_*
)
op_ssh_userdir()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -rvf /home/*/.ssh /root/.ssh
)
op_sssd_db_log()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/var/log/sssd/* \
/var/lib/sss/db/* \
;
)
op_tmp_files()
(
run_op() { echo "+ ${*}" ; "$@" ; }
for d in /tmp/ /var/tmp/ ; do
if [ -d "$d" ] ; then
run_op find "$d" -maxdepth 1 -mindepth 1 -exec rm -rvf '{}' \;
fi
done
)
op_udev_persistent_net()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf /etc/udev/rules.d/70-persistent-net.rules
)
# it honors the --keep-user-accounts list
op_user_account()
(
run_op() { echo "+ ${*}" ; "$@" ; }
if [ "${_ONE_VALID_AWK}" = 'NO' ] ; then
printf "[!] This test is skipped due to the incompatible awk implementation!\n"
return 2
fi
# comb the list
_keep_list=$(echo "$ARG_USERS_KEEP" | \
sed -e 's/,/ /g' -e 's/[[:space:]]\+/\n/g' -e '/^$/d' | \
sort -u)
case "$(uname | tr '[:upper:]' '[:lower:]')" in
linux)
if [ -f /etc/login.defs ] ; then
_uid_min=$(</etc/login.defs \
awk '/^[[:space:]]*UID_MIN[[:space:]]/ {print $2}')
_uid_max=$(</etc/login.defs \
awk '/^[[:space:]]*UID_MAX[[:space:]]/ {print $2}')
# setup the default values according to manual pages for login.defs
if [ -z "$_uid_min" ] ; then
_uid_min=1000
fi
if [ -z "$_uid_max" ] ; then
_uid_max=60000
fi
_users=$(getent passwd | \
awk -v uid_min="$_uid_min" -v uid_max="$_uid_max" \
'
BEGIN {
FS = ":";
}
{
if (($3 >= uid_min) && ($3 <= uid_max))
printf("%s:%s\n", $1, $3);
}' | sort -u)
for _user in ${_users} ; do
_username=$(echo "$_user" | cut -d":" -f1)
_uid=$(echo "$_user" | cut -d":" -f2)
# is this user preserved?
# NOTE: on FreeBSD newlines are not preserved in the variable
# which is strange because POSIX says that only trailing
# newlines are removed...just to be on the safe side I will
# reintroduce them on the linux too...
if echo "$_keep_list" | tr ' ' '\n' | grep -q "^${_username}\$" ; then
continue
fi
# keep deluser first: alpine has both commands but does not
# have the --selinux-user option for userdel...
if command -v deluser >/dev/null ; then
run_op deluser --remove-home "$_username"
elif command -v userdel >/dev/null ; then
run_op userdel --remove --selinux-user "$_username"
fi
# wipe-out all deleted user's files on the system
run_op find / -depth -user "$_uid" -delete || true
done
else
err "No /etc/login.defs - we cannot guess the MIN-MAX of UIDs/GIDs"
return 1
fi
;;
freebsd)
if [ -f /etc/pw.conf ] ; then
_uid_min=$(</etc/pw.conf \
awk '/^[[:space:]]*minuid[[:space:]]/ {print $2}')
_uid_max=$(</etc/pw.conf \
awk '/^[[:space:]]*maxuid[[:space:]]/ {print $2}')
fi
# setup the default values according to manual pages for pw.conf
if [ -z "$_uid_min" ] ; then
_uid_min=1000
fi
if [ -z "$_uid_max" ] ; then
_uid_max=32000
fi
_users=$(getent passwd | \
awk -v uid_min="$_uid_min" -v uid_max="$_uid_max" \
'
BEGIN {
FS = ":";
}
{
if (($3 >= uid_min) && ($3 <= uid_max))
printf("%s:%s\n", $1, $3);
}' | sort -u)
for _user in ${_users} ; do
_username=$(echo "$_user" | cut -d":" -f1)
_uid=$(echo "$_user" | cut -d":" -f2)
# is this user preserved?
# NOTE: on FreeBSD newlines are not preserved in the variable
# which is strange because POSIX says that only trailing
# newlines are removed...
if echo "$_keep_list" | tr ' ' '\n' | grep -q "^${_username}\$" ; then
continue
fi
run_op pw userdel "$_username" -r
# wipe-out all deleted user's files on the system
run_op find / -depth -user "$_uid" -delete || true
done
;;
*)
warn "Removing users: this system ('$(uname)') is not supported"
;;
esac
)
op_utmp()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf /var/run/utmp
)
op_yum_uuid()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf /var/lib/yum/uuid
)
# extra one operations
op_one_cleanup()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vrf \
/var/lib/one-context/ \
;
)
op_one_shell_history()
(
run_op() { echo "+ ${*}" ; "$@" ; }
run_op rm -vf \
/home/*/.history \
/root/.history \
/home/*/.*_history \
/root/.*_history \
;
)
# TODO: this is disabled for now because it depends on user-account and due to
# the alphabetical order this is executed sooner which then causes problem...
op_one_group_account()
(
run_op() { echo "+ ${*}" ; "$@" ; }
if [ "${_ONE_VALID_AWK}" = 'NO' ] ; then
printf "[!] This test is skipped due to the incompatible awk implementation!\n"
return 2
fi
case "$(uname | tr '[:upper:]' '[:lower:]')" in
linux)
if [ -f /etc/login.defs ] ; then
_gid_min=$(</etc/login.defs \
awk '/^[[:space:]]*GID_MIN[[:space:]]/ {print $2}')
_gid_max=$(</etc/login.defs \
awk '/^[[:space:]]*GID_MAX[[:space:]]/ {print $2}')
# setup the default values according to manual pages for login.defs
if [ -z "$_gid_min" ] ; then
_gid_min=1000
fi
if [ -z "$_gid_max" ] ; then
_gid_max=60000
fi
_groups=$(getent group | \
awk -v gid_min="$_gid_min" -v gid_max="$_gid_max" \
'
BEGIN {
FS = ":";
}
{
if (($3 >= gid_min) && ($3 <= gid_max))
printf("%s:%s\n", $1, $3);
}' | sort -u)
for _group in ${_groups} ; do
_groupname=$(echo "$_group" | cut -d":" -f1)
_gid=$(echo "$_group" | cut -d":" -f2)
if command -v groupdel >/dev/null ; then
run_op groupdel "$_groupname"
elif command -v delgroup >/dev/null ; then
run_op delgroup "$_groupname"
fi
# wipe-out all deleted group's files on the system
run_op find / -depth -group "$_gid" -delete || true
done
else
err "No /etc/login.defs - we cannot guess the MIN-MAX of UIDs/GIDs"
return 1
fi
;;
freebsd)
if [ -f /etc/pw.conf ] ; then
_gid_min=$(</etc/pw.conf \
awk '/^[[:space:]]*mingid[[:space:]]/ {print $2}')
_gid_max=$(</etc/pw.conf \
awk '/^[[:space:]]*maxgid[[:space:]]/ {print $2}')
fi
# setup the default values according to manual pages for pw.conf
if [ -z "$_gid_min" ] ; then
_gid_min=1000
fi
if [ -z "$_gid_max" ] ; then
_gid_max=32000
fi
_groups=$(getent group | \
awk -v gid_min="$_gid_min" -v gid_max="$_gid_max" \
'
BEGIN {
FS = ":";
}
{
if (($3 >= gid_min) && ($3 <= gid_max))
printf("%s:%s\n", $1, $3);
}' | sort -u)
for _group in ${_groups} ; do
_groupname=$(echo "$_group" | cut -d":" -f1)
_gid=$(echo "$_group" | cut -d":" -f2)
run_op pw groupdel "$_groupname"
# wipe-out all deleted group's files on the system
run_op find / -depth -group "$_gid" -delete || true
done
;;
*)
warn "Removing groups: this system ('$(uname)') is not supported"
;;
esac
)
op_one_hostname()
(
run_op() { echo "+ ${*}" ; "$@" ; }
# wipe-out hostname
if [ -f /etc/hostname ] ; then
echo "+ echo localhost"
echo localhost > /etc/hostname
fi
# wipe-out hosts
cat > /etc/hosts <<EOF
# The following lines are desirable for IPv4 capable hosts
127.0.0.1 localhost
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
EOF
)
op_one_resolvconf()
(
run_op() { echo "+ ${*}" ; "$@" ; }
# wipe-out resolv.conf
echo "+ nameserver 127.0.0.1"
echo nameserver 127.0.0.1 > /etc/resolv.conf
)
op_one_network()
(
run_op() { echo "+ ${*}" ; "$@" ; }
case "$(uname | tr '[:upper:]' '[:lower:]')" in
linux)
if [ -f /etc/network/interfaces ] ; then
for f in /etc/network/interfaces* ; do
if [ -f "$f" ] ; then
run_op rm -vf /etc/network/interfaces*
fi
done
echo "+ reset /etc/network/interfaces"
cat > /etc/network/interfaces <<EOF
auto lo
iface lo inet loopback
EOF
fi
if [ -f /etc/networks ] ; then
echo "+ reset /etc/networks"
cat > /etc/networks <<EOF
default 0.0.0.0
loopback 127.0.0.0
link-local 169.254.0.0
EOF
fi
# systemd
run_op rm -rvf /etc/systemd/network/*
# redhat/suse scripts
if [ -d /etc/sysconfig/network-scripts ] || \
[ -d /etc/sysconfig/network/ ] ;
then
run_op find /etc/sysconfig/network*/ \
-maxdepth 1 \
-type f \
\( -name ifcfg-\* -a -not -name ifcfg-lo \) \
-delete
run_op find /etc/sysconfig/network*/ \
-maxdepth 1 \
-type f \
\( -name route-\* -o -name ifroute-\* \) \
-delete
fi
;;
freebsd)
run_op rm -vf /etc/hostid
run_op rm -vf /etc/rc.conf.d/network
;;
*)
warn "Cleaning network settings: this system ('$(uname)') is not supported"
;;
esac
# wipe-out hosts.allow|deny|etc files
if [ -f /etc/hosts.allow ] ; then
echo "+ clear /etc/hosts.allow"
cat /dev/null > /etc/hosts.allow
fi
if [ -f /etc/hosts.deny ] ; then
echo "+ clear /etc/hosts.deny"
cat /dev/null > /etc/hosts.deny
fi
if [ -f /etc/hosts.equiv ] ; then
echo "+ clear /etc/hosts.equiv"
cat /dev/null > /etc/hosts.equiv
fi
if [ -f /etc/hosts.lpd ] ; then
echo "+ clear /etc/hosts.lpd"
cat /dev/null > /etc/hosts.lpd
fi
)
# fill up the free space with zeroes to claim the whole free/unused space for
# sparsify or trim (below) - to fully utilize this - one should create virtual
# disk similar to this (on QEMU/KVM):
# <disk type='file' device='disk'>
# <driver name='qemu' type='raw' discard='unmap'/>
# <source file='/some.img'/>
# <target dev='sda' bus='scsi'/>
# <address type='drive' controller='0' bus='0' target='0' unit='0'/>
# </disk>
op_one_zerofill()
(
run_op() { echo "+ ${*}" ; "$@" ; }
msg "Fill the free space with zeroes and delete it"
# 1MiB - busybox workaround
_bytes=$(( 1024 * 1024 ))
run_op dd if=/dev/zero of=/.zero bs=${_bytes} || true
run_op sync
run_op rm -f /.zero
)
# trim/discard the unused space
# TODO: FreeBSD version will break if the device path has some whitespace which
# is very unlikely...
op_one_trim()
(
run_op() { echo "+ ${*}" ; "$@" ; }
case "$(uname | tr '[:upper:]' '[:lower:]')" in
linux)
msg "Trim/discard the unused space"
if command -v fstrim > /dev/null ; then
run_op fstrim -a -v || run_op fstrim -v /
else
warn "Missing fstrim tool - SKIP"
fi
;;
freebsd)
# TODO: I don't know what device to use here - let that be an
# improvement for the future...
#msg "Trim/discard the unused space"
# TODO: check awk sanity if used...
#if command -v trim > /dev/null ; then
# _devs=$(< /etc/fstab awk '
# {
# if ($0 !~ /^[[:space:]]*#/)
# print $1;
# }')
# for _dev in ${_devs} ; do
# run_op trim -f "$_dev"
# done
#else
# warn "Missing fstrim tool - SKIP"
#fi
warn "Trim not supported on the FreeBSD - SKIP"
;;
esac
)
################################################################################
# main
#
# are we colorful?
if is_terminal_colorful ; then
SETCOLOR_ON_OK='\033[0;32;1m' # green
SETCOLOR_ON_FAILURE='\033[0;31;1m' # red
SETCOLOR_ON_HIGHLIGHT='\033[0;32;1m' # green
SETCOLOR_ON_HIGHLIGHT2='\033[0;36;1m' # cyan
SETCOLOR_ON_COMMAND='\033[0;33;1m' # yellow
SETCOLOR_OFF='\033[0m'
else
SETCOLOR_ON_OK=
SETCOLOR_ON_FAILURE=
SETCOLOR_ON_HIGHLIGHT=
SETCOLOR_ON_HIGHLIGHT2=
SETCOLOR_ON_COMMAND=
SETCOLOR_OFF=
fi
# print banner
cat <<EOF
___ _ __ ___
/ _ \ | '_ \ / _ \ OpenNebula System Preparation Tool
| (_) || | | || __/ (c) 2002-2021, OpenNebula Project, OpenNebula Systems
\___/ |_| |_| \___| Version 6.4.0
EOF
# leave this empty
IGNORE_ERROR=
ARG_ONE_OPTION_LIST=
ARG_OPS_LIST=
ARG_PASSWORD=
ARG_USERS_KEEP=
ARG_USERS_REMOVE=
# parse arguments
state=nil
action=
abort=
while [ $# -gt 0 ] ; do
case "$state" in
nil)
case "$1" in
--help)
state=help
case "$action" in
'')
action=help
;;
help)
err "Redundant argument: ${1}"
;;
*)
err "Arguments mismatch"
abort=yes
;;
esac
;;
--list-operations)
state=list
case "$action" in
'')
action=list-operations
;;
list-operations)
err "Redundant argument: ${1}"
;;
*)
err "Arguments mismatch"
abort=yes
;;
esac
;;
--operations)
state=ops
case "$action" in
''|operations)
action=operations
;;
*)
err "Arguments mismatch"
abort=yes
;;
esac
;;
# DISABLED:
#--one-options)
# state=options
# ;;
--yes|--strict|--update|--verbose|--poweroff|--selinux-relabel)
ARG_ONE_OPTION_LIST="${ARG_ONE_OPTION_LIST},${1##--}"
;;
--root-password)
state=root-password
;;
--password)
state=password
;;
--remove-user-accounts)
state=remove-users
;;
--keep-user-accounts)
state=keep-users
;;
*)
err "Unknown argument: ${1}"
abort=yes
;;
esac
;;
help)
err "Extra argument: ${1}"
abort=yes
;;
list)
err "Extra argument: ${1}"
abort=yes
;;
ops)
ARG_OPS_LIST="${ARG_OPS_LIST},${1}"
state=nil
;;
# DISABLED:
#options)
# ARG_ONE_OPTION_LIST="${ARG_ONE_OPTION_LIST},${1}"
# state=nil
# ;;
root-password)
ARG_PASSWORD="${ARG_PASSWORD},root:${1}"
state=nil
;;
password)
ARG_PASSWORD="${ARG_PASSWORD},${1}"
state=nil
;;
remove-users)
ARG_USERS_REMOVE="${ARG_USERS_REMOVE},${1}"
state=nil
;;
keep-users)
ARG_USERS_KEEP="${ARG_USERS_KEEP},${1}"
state=nil
;;
esac
# there were usage errors - abort
if [ -n "$abort" ] ; then
print_help
exit 1
fi
shift
done
# verify that arguments are not short of values...
case "$state" in
ops|options|root-password|password|remove-users|keep-users)
err "Missing argument(s)"
exit 1
;;
esac
# validate one options
parse_one_options "$ARG_ONE_OPTION_LIST"
# validate password arguments and save them again in parsed format
ARG_PASSWORD=$(parse_password_arguments "$ARG_PASSWORD")
# execute the requested action
case "$action" in
'')
sanity_check
# ask before running
ask_to_run_sysprep
# run default operations
run_prep
run_ops default
;;
help)
usage
exit 0
;;
list-operations)
list_operations
exit 0
;;
operations)
sanity_check
# ask before running
ask_to_run_sysprep
run_prep
run_ops "$ARG_OPS_LIST"
;;
esac
# optional last sysprep steps (like poweroff)
run_last
msg "DONE"
exit 0


PK 99