From c653846833f4c073cd9d6feccb74c3117b1b527e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ospal=C3=BD?= Date: Wed, 24 Jun 2020 06:30:31 +0200 Subject: [PATCH] F #127: Add one-sysprep initial implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Petr OspalĂ˝ --- src/usr/sbin/one-sysprep | 873 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 873 insertions(+) create mode 100755 src/usr/sbin/one-sysprep diff --git a/src/usr/sbin/one-sysprep b/src/usr/sbin/one-sysprep new file mode 100755 index 0000000..49c4d0b --- /dev/null +++ b/src/usr/sbin/one-sysprep @@ -0,0 +1,873 @@ +#!/bin/sh + +# ---------------------------------------------------------------------------- # +# Copyright 2020, 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). + +set -e + +# +# globals +# + +CMD=$(basename "$0") + +# here are declared all sysprep operations - keep the following format: +# :: +# for each operation there should be corresponding function: +# op_ +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: Customize the guest (NOOP - NOT YET 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: Change filesystem UUIDs (NOOP - NOT YET IMPLEMENTED) +kerberos-data :0: Remove Kerberos data in the guest +logfiles :1: Remove many log files from the guest +lvm-uuids :1: Change LVM2 PV and VG UUIDs (NOOP - NOT YET 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 in network interface configuration +net-hwaddr :1: Remove HWADDR (hard-coded MAC address) configuration +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: Run arbitrary scripts against the guest +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 +' + +# +# functions +# + +usage() +( + cat < [--operations ]... + It will execute (without prompting you) the desired operations. + + '--operations' can be used more than once - the lists are then spliced + together. + + 'list-of-operations' is comma separated list of operations where + amongst the actual names of the operations are also recognized: + '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 otherwise would. + +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 all + Run all available operations + + ${CMD} --operations machine-id,ca-certificates + Run only two operations +EOF +) + +print_help() +( + echo "[!] try help: ${CMD} --help" >&2 +) + +err() +( + echo "[!] ${*}" >&2 +) + +# arg: [normal|summary] +# if used with argument 'summary' then it will print out summary of supported +# operations - otherwise it will output parseable: : +parse_sysprep_operations() +( + echo "$ALL_SYSPREP_OPERATIONS" | sort | awk -v output="${1:-normal}" ' + BEGIN { + FS = ":"; + count=0; + } + { + if ($0 == "") + next; + + op = $1; + sub(/^[[:space:]]*/, "", op); + sub(/[[:space:]]*$/, "", op); + + default_op = $2; + sub(/^[[:space:]]*/, "", default_op); + sub(/[[:space:]]*$/, "", default_op); + + comment = $3; + sub(/^[[:space:]]*/, "", comment); + sub(/[[:space:]]*$/, "", 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 = " "; + printf("%s %-*s %s\n", asterisk, max_length, op, 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='' + + 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}" + + while [ -z "$_reply" ] ; do + printf "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 +) + +# arg: all|default| +# 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 +) + +# arg: +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}" + 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}" + 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= + for _op in ${_whitelist} ; do + # if the operation is not blacklisted then add it to the final list + if ! echo "$_blacklist" | grep -q "^${_op}\$" ; then + _ops="${_ops} ${_op}" + fi + done + + # execute requested operations one by one + for _op in ${_ops} ; do + _op_func=$(echo "$_op" | tr '-' '_') + printf "Run operation: %s ... " "${_op}" + if _op_func_output=$(eval "op_${_op_func}" 2>&1) ; then + echo OK + else + echo FAILED + echo "$_op_func_output" + fi + done +) + +# reimplemented operations + +op_abrt_data() +( + for dump in /var/spool/abrt/* ; do + if [ -e "$dump" ] ; then + rm -rf "$dump" + fi + done +) + +op_backup_files() +( + find / \( -name '*.bak' -o -name '*~' \) -delete +) + +op_bash_history() +( + for f in /home/*/.bash_history /root/.bash_history ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_blkid_tab() +( + for f in \ + /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 \ + ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_ca_certificates() +( + 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 + if [ -f "$f" ] ; then + rm -f "$f" + fi + ;; + esac + done +) + +op_crash_data() +( + rm -rf /var/crash/* /var/log/dump/* +) + +op_cron_spool() +( + for d in \ + /var/spool/cron/ \ + /var/spool/cron/atjobs/ \ + /var/spool/atjobs/ \ + /var/spool/atspool/ \ + /var/spool/at/ \ + ; do + if [ -d "$d" ] ; then + find "$d" -type f -not -name .SEQ -delete + find "$d" -type f -name .SEQ -print | while read -r _seq_file ; do + cat /dev/null > "$_seq_file" + done + fi + done +) + +# TODO: should we support customize? +op_customize() +( + echo NOOP +) + +op_dhcp_client_state() +( + rm -rf /var/lib/dhclient/* /var/lib/dhcp/* +) + +op_dhcp_server_state() +( + rm -rf /var/lib/dhcpd/* +) + +op_dovecot_data() +( + for f in /var/lib/dovecot/* ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_firewall_rules() +( + for f in \ + /etc/sysconfig/iptables \ + /etc/firewalld/services/* \ + /etc/firewalld/zones/* \ + ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_flag_reconfiguration() +( + touch /.unconfigured +) + +# TODO: is this possible on mounted fs? +op_fs_uuids() +( + echo NOOP +) + +op_kerberos_data() +( + 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 + if [ -f "$f" ] ; then + rm -f "$f" + fi + ;; + 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() +( + for f in \ + /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/* \ + ; do + if [ -e "$f" ] ; then + rm -rf "$f" + fi + done +) + +# TODO: can ths be implemented? +op_lvm_uuids() +( + echo NOOP +) + +op_machine_id() +( + for f in /etc/machine-id /var/lib/dbus/machine-id ; do + if [ -f "$f" ] ; then + cat /dev/null > "$f" + fi + done +) + +op_mail_spool() +( + rm -rf /var/spool/mail/* /var/mail/* +) + +# TODO: this is RedHat-centric and also whatabout /etc/hostname? +op_net_hostname() +( + for f in /etc/sysconfig/network-scripts/ifcfg-* ; do + if [ -f "$f" ] ; then + sed -i -e '/^HOSTNAME=/d' -e '/^DHCP_HOSTNAME=/d' "$f" + fi + done +) + +# TODO: this is RedHat-centric +op_net_hwaddr() +( + for f in /etc/sysconfig/network-scripts/ifcfg-* ; do + if [ -f "$f" ] ; then + sed -i -e '/^HWADDR=/d' "$f" + fi + done +) + +op_pacct_log() +( + if [ -f /var/account/pacct ] ; then + rm -f /var/account/pacct* + touch /var/account/pacct + fi + + if [ -f /var/log/account/pacct ] ; then + rm -f /var/log/account/pacct* + touch /var/log/account/pacct + fi +) + +op_package_manager_cache() +( + for d in \ + /var/cache/apt/archives/ \ + /var/cache/dnf/ \ + /var/cache/yum/ \ + /var/cache/zypp* \ + ; do + if [ -d "$d" ] ; then + find "$d" -type f -delete + fi + done +) + +op_pam_data() +( + for f in \ + /var/run/console/* \ + /var/run/faillock/* \ + /var/run/sepermit/* \ + ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_passwd_backups() +( + rm -f \ + /etc/group- \ + /etc/gshadow- \ + /etc/passwd- \ + /etc/shadow- \ + /etc/subuid- \ + /etc/subgid- \ + ; +) + +op_puppet_data_log() +( + echo TODO +) + +op_rhn_systemid() +( + echo TODO +) + +op_rh_subscription_manager() +( + echo TODO +) + +op_rpm_db() +( + for f in /var/lib/rpm/__db.* ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_samba_db_log() +( + echo TODO +) + +op_script() +( + echo TODO +) + +op_smolt_uuid() +( + echo TODO +) + +op_ssh_hostkeys() +( + for f in /etc/ssh/*_host_* ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_ssh_userdir() +( + for d in /home/*/.ssh /root/.ssh ; do + if [ -d "$d" ] ; then + rm -rf "$d" + fi + done +) + +op_sssd_db_log() +( + for f in \ + /var/log/sssd/* \ + /var/lib/sss/db/* \ + ; do + if [ -f "$f" ] ; then + rm -f "$f" + fi + done +) + +op_tmp_files() +( + for d in /tmp/ /var/tmp/ ; do + if [ -d "$d" ] ; then + find "$d" -maxdepth 1 -mindepth 1 -exec rm -rf '{}' \; + fi + done +) + +op_udev_persistent_net() +( + if [ -f /etc/udev/rules.d/70-persistent-net.rules ] ; then + rm -f /etc/udev/rules.d/70-persistent-net.rules + fi +) + +op_user_account() +( + echo TODO +) + +op_utmp() +( + if [ -f /var/run/utmp ] ; then + rm -f /var/run/utmp + fi +) + +op_yum_uuid() +( + if [ -f /var/lib/yum/uuid ] ; then + rm -f /var/lib/yum/uuid + fi +) + +# +# main +# + +# leave this empty +ARG_OPS_LIST= + +# 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 + ;; + *) + 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 + ;; + esac + + # there were usage errors - abort + if [ -n "$abort" ] ; then + print_help + exit 1 + fi + + shift +done + +# execute the requested action +case "$action" in + '') + # no argument - ask before running default operations + if ! ask_yes ; then + printf "\n%s: ABORTED\n" "${CMD}" + exit 0 + fi + run_ops default + ;; + help) + usage + ;; + list-operations) + list_operations + ;; + operations) + run_ops "$ARG_OPS_LIST" + ;; +esac + +printf "\n%s: DONE\n" "${CMD}" + +exit 0