|
#!/bin/bash
|
|
# vim:sw=2:ts=2:et
|
|
|
|
export SCLNAME=tfm
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
$0 - configuration and log data collector
|
|
|
|
USAGE: $0 [options]
|
|
|
|
Collects configuration and log data for Foreman, Smart Proxies, backend
|
|
services and system information while removing security information like
|
|
passwords, tokens and keys.
|
|
|
|
This program can be used on Foreman instances, Smart Proxy instances or
|
|
backend services separately.
|
|
|
|
OPTIONS:
|
|
-d DIR Directory to place the tarball in (default /var/tmp/foreman-XYZ)
|
|
-g Skip generic info (CPU, memory, firewall etc.)
|
|
-a Keep directory and do not generate a compressed tarball
|
|
-s SIZE Maximum log set size in MB (current+rotated files, defaults to 10 MB)
|
|
-j PRG Filter with provided program when creating a tarball
|
|
-p Additionally print password patterns being filtered out
|
|
-q Quiet mode
|
|
-v Verbose mode
|
|
-h Shows this message
|
|
|
|
USAGE
|
|
}
|
|
|
|
# Filter for patterns like password=XYZ, -storepass XYZ or secret: XYZ
|
|
FILTER_WORDS=(
|
|
password
|
|
PASSWORD
|
|
default_password
|
|
oauth_consumer_key
|
|
secret
|
|
token
|
|
keystorePass
|
|
truststorePass
|
|
storepass
|
|
)
|
|
FILTER_WORDS_STR=$(IFS=$'|'; echo "${FILTER_WORDS[*]}")
|
|
FILTER="s/($FILTER_WORDS_STR)(\"?\s*[:=]?\s*)\S+/\1\2+FILTERED+/g"
|
|
|
|
error() {
|
|
echo "$*" >&2
|
|
}
|
|
|
|
qprintf() {
|
|
[ $QUIET -ne 1 ] && printf "$@"
|
|
}
|
|
|
|
printv() {
|
|
[ $QUIET -ne 1 ] && [ $VERBOSE -eq 1 ] && echo "[$SECONDS]" $*
|
|
}
|
|
|
|
clean_stdin() {
|
|
while read -e -t 0.1; do : ; done
|
|
}
|
|
|
|
# add outout of the command and redirect possible errors there
|
|
add_cmd() {
|
|
CMD=$1
|
|
OUT=$2
|
|
printv " - $OUT"
|
|
echo -e "COMMAND> $CMD\n" > "$DIR/$OUT"
|
|
eval $CMD >> "$DIR/$OUT" 2>&1
|
|
}
|
|
|
|
# add and filter file of known MIME type, calculate size
|
|
add_file() {
|
|
FILE=$1
|
|
SIZE=$(stat -c "%s" $FILE)
|
|
MIME=$(file -bi "$FILE" | cut -d\; -f1)
|
|
case $MIME in
|
|
application/x-gzip)
|
|
OUTFILE="$DIR${FILE/%.gz/}"
|
|
zcat "$FILE" | sed -r "$FILTER" > "$OUTFILE"
|
|
touch -c -r "$FILE" "$OUTFILE"
|
|
;;
|
|
application/x-bzip2)
|
|
OUTFILE="$DIR${FILE/%.bz2/}"
|
|
bzcat "$FILE" | sed -r "$FILTER" > "$OUTFILE"
|
|
touch -c -r "$FILE" "$OUTFILE"
|
|
;;
|
|
application/x-xz)
|
|
OUTFILE="$DIR${FILE/%.xz/}"
|
|
xzcat "$FILE" | sed -r "$FILTER" > "$OUTFILE"
|
|
touch -c -r "$FILE" "$OUTFILE"
|
|
;;
|
|
text/plain | application/xml)
|
|
OUTFILE="$DIR$FILE"
|
|
sed -r "$FILTER" "$FILE" > "$OUTFILE"
|
|
[ $PRINTPASS -eq 1 ] && grep -H "+FILTERED+" "$OUTFILE"
|
|
touch -c -r "$FILE" "$OUTFILE"
|
|
;;
|
|
*)
|
|
echo "Skipping file $FILE: unknown MIME type $MIME" >> "$DIR/skipped_files"
|
|
SIZE=0 # don't count the size to collected files
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# add files (from newest to oldest) that are non zero, readable, regular file or symlink, until $MAXSIZE limit is reached
|
|
add_files() {
|
|
SUMSIZE=0
|
|
# sort regular+symlink readable nonempty files per modification time, newest first - assuming no space in a filename
|
|
for FILE in $(find -L $* -type f -readable -size +0b -printf "%T@ %p\n" 2> /dev/null | sort -nr | cut -d' ' -f2 2> /dev/null); do
|
|
# if we are over size limit, skip rest older files
|
|
if [ \( $SUMSIZE -gt $MAXSIZE \) -a \( $MAXSIZE -gt 0 \) ]; then
|
|
printv " - $FILE (skipped due to size)"
|
|
else
|
|
printv " - $FILE"
|
|
SUBDIR=$(dirname $FILE)
|
|
[ ! -d "$DIR$SUBDIR" ] && mkdir -p "$DIR$SUBDIR"
|
|
# if the file is symlink, copy source and add target
|
|
if [ -h $FILE ]; then
|
|
cp -a "$FILE" "$DIR$FILE"
|
|
FILE=$(readlink -f $FILE)
|
|
fi
|
|
add_file $FILE
|
|
SUMSIZE=$(($((SUMSIZE))+$((SIZE))))
|
|
fi
|
|
touch -c -r "$SUBDIR" "$DIR$SUBDIR"
|
|
done
|
|
}
|
|
|
|
# default values
|
|
DIR=""
|
|
NOGENERIC=0
|
|
NOTAR=0
|
|
MAXSIZE=10485760 # 10 MB in bytes
|
|
COMPRESS=""
|
|
PRINTPASS=0
|
|
QUIET=0
|
|
VERBOSE=0
|
|
DEBUG=0
|
|
|
|
if type -p xz >/dev/null; then
|
|
COMPRESS="xz -1"
|
|
EXTENSION=".xz"
|
|
elif type -p bzip2 >/dev/null; then
|
|
COMPRESS="bzip2 -1"
|
|
EXTENSION=".bz2"
|
|
elif type -p gzip >/dev/null; then
|
|
COMPRESS="gzip -5"
|
|
EXTENSION=".gz"
|
|
else
|
|
COMPRESS="cat"
|
|
EXTENSION=""
|
|
fi
|
|
|
|
# read optional configuration file with user-defined defaults
|
|
CONF_FILE=/usr/share/foreman/config/foreman-debug.conf
|
|
test -f $CONF_FILE && source $CONF_FILE
|
|
|
|
while getopts "d:gam:s:j:uqpvhx" opt; do
|
|
case $opt in
|
|
d)
|
|
DIR="$OPTARG"
|
|
;;
|
|
g)
|
|
NOGENERIC=1
|
|
;;
|
|
a)
|
|
NOTAR=1
|
|
;;
|
|
p)
|
|
PRINTPASS=1
|
|
;;
|
|
q)
|
|
QUIET=1
|
|
;;
|
|
v)
|
|
VERBOSE=1
|
|
;;
|
|
m)
|
|
error "Warning: -m option has no effect, use -s option"
|
|
;;
|
|
j)
|
|
COMPRESS="$OPTARG"
|
|
EXTENSION=".$(echo "$OPTARG" | awk '{ print $1 }')"
|
|
;;
|
|
s)
|
|
#read the value and convert from MB to bytes
|
|
MAXSIZE="$OPTARG"
|
|
MAXSIZE=$((MAXSIZE*1024*1024))
|
|
;;
|
|
x)
|
|
# this option is not docummented - use for extra output,
|
|
# skip slow items and to disable root check
|
|
DEBUG=1
|
|
;;
|
|
h)
|
|
usage
|
|
exit
|
|
;;
|
|
?)
|
|
error "Invalid option: $OPTARG"
|
|
usage
|
|
exit
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[ $DEBUG -eq 0 -a $EUID -ne 0 ] && error "This script must be run as root" && exit 1
|
|
|
|
# some tasks take long time, print a banner (unless quiet mode was selected)
|
|
qprintf "Processing... (takes a while)\n"
|
|
|
|
# determine distribution family
|
|
if [ -f /etc/debian_version ]; then
|
|
OS=debian
|
|
OS_RELEASE=$(head -n1 /etc/debian_version)
|
|
elif [ -f /etc/redhat-release ]; then
|
|
OS=redhat
|
|
OS_RELEASE=$(head -n1 /etc/redhat-release)
|
|
elif type -p lsb_release >/dev/null; then
|
|
OS=$(lsb_release -si 2>/dev/null)
|
|
OS_RELEASE=$(lsb_release -sr 2>/dev/null)
|
|
elif type -p rpm >/dev/null; then
|
|
OS=$(rpm -q --whatprovides redhat-release --queryformat '%{NAME}')
|
|
OS_RELEASE=$(rpm -q --whatprovides redhat-release --queryformat '%{VERSION}')
|
|
else
|
|
OS=$(uname -s)
|
|
OS_RELEASE="Unknown"
|
|
fi
|
|
printv "Determined $OS distribution"
|
|
|
|
clean_temp() {
|
|
# prevent from removing anything user-defined
|
|
if [[ "$NOTAR" -eq 0 && "$DIR" == /var/tmp* ]]; then
|
|
printv "Cleaning $DIR"
|
|
rm -rf "$DIR"
|
|
fi
|
|
if [[ "$TMPDIR" == /var/tmp* ]]; then
|
|
printv "Cleaning $TMPDIR"
|
|
rm -rf "$TMPDIR"
|
|
fi
|
|
}
|
|
|
|
install_clean_trap() {
|
|
trap clean_temp EXIT
|
|
}
|
|
|
|
if [ -z "$DIR" ]; then
|
|
DIR=$(mktemp -d $(basename $0)-XXXXX -p /var/tmp)
|
|
else
|
|
[ ! -d "$DIR" ] && mkdir -p "$DIR"
|
|
fi
|
|
export TMPDIR=$(mktemp -d foreman-debug-auxtmp-XXXXX -p /var/tmp)
|
|
install_clean_trap
|
|
printv "Created $DIR and $TMPDIR"
|
|
|
|
TARBALL="$DIR.tar$EXTENSION"
|
|
|
|
# GENERIC ARTIFACTS
|
|
|
|
if [ $NOGENERIC -eq 0 ]; then
|
|
printv "Collecting generic system information"
|
|
add_cmd "date" "date"
|
|
add_cmd "lsb_release -a" "lsb_release"
|
|
add_cmd "uname -a" "uname"
|
|
add_cmd "cat /proc/cpuinfo" "cpuinfo"
|
|
add_cmd "cat /proc/meminfo" "meminfo"
|
|
add_cmd "ulimit -a" "ulimit"
|
|
add_cmd "lsmod" "lsmod"
|
|
add_cmd "iptables -L -v -n" "iptables"
|
|
add_cmd "ifconfig -a" "ifconfig"
|
|
add_cmd "route -n" "route"
|
|
add_cmd "netstat -putna" "netstat"
|
|
add_cmd "ip a" "ip_a"
|
|
add_cmd "ip r" "ip_r"
|
|
add_cmd "ss -putna" "ss"
|
|
add_cmd "cat /etc/hosts" "hosts"
|
|
add_cmd "ping -c1 -W1 localhost" "ping_localhost"
|
|
add_cmd "ping -c1 -W1 $(hostname)" "ping_hostname"
|
|
add_cmd "ping -c1 -W1 $(hostname -f)" "ping_hostname_full"
|
|
add_cmd "host $(hostname -f)" "hostname_dns_check"
|
|
type scl &>/dev/null && \
|
|
add_cmd "scl -l" "software_collections"
|
|
|
|
add_cmd "ps auxwwwZ" "process_list"
|
|
add_files /var/log/messages*
|
|
add_files /var/log/audit/audit.log*
|
|
add_files /var/log/syslog*
|
|
add_cmd "getenforce" "selinux_state"
|
|
add_cmd "ausearch -m AVC -m USER_AVC -m SELINUX_ERR | head -n 100" "selinux_first_denials.log"
|
|
add_cmd "ausearch -m AVC -m USER_AVC -m SELINUX_ERR || grep AVC /var/log/audit/audit.log" "selinux_denials.log"
|
|
if [ -f /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled; then
|
|
[[ -f /var/lib/sepolgen/interface_info ]] || sepolgen-ifgen &>/dev/null
|
|
add_cmd "audit2allow -Ra || audit2allow -a" "selinux_audit2allow"
|
|
add_cmd "semodule -l" "selinux_modules"
|
|
add_cmd "semanage boolean -l" "selinux_booleans"
|
|
add_cmd "semanage fcontext -l" "selinux_fcontext"
|
|
fi
|
|
|
|
if [ "$OS" = "redhat" ]; then
|
|
[ $DEBUG -eq 0 ] && add_cmd "rpm -qa" "installed_packages"
|
|
elif [ "$OS" = "debian" ]; then
|
|
[ $DEBUG -eq 0 ] && add_cmd "dpkg --list" "installed_packages"
|
|
fi
|
|
|
|
add_cmd "virt-who -dop" "virt_who"
|
|
fi
|
|
|
|
# FOREMAN RELATED ARTIFACTS
|
|
|
|
printv "Collecting Foreman-related information"
|
|
add_cmd "rpm -qa '*foreman*' || dpkg -l '*foreman*' | sort" "foreman_packages"
|
|
add_cmd "ruby --version" "version_ruby"
|
|
add_cmd "puppet --version" "version_puppet"
|
|
add_cmd "gem list" "gem_list"
|
|
add_cmd "scl enable $SCLNAME 'gem list'" "gem_list_scl"
|
|
add_cmd "bundle --local --gemfile=/usr/share/foreman/Gemfile" "bundle_list"
|
|
add_cmd "facter" "facts"
|
|
|
|
add_files /var/log/foreman/apipie_cache*.log*
|
|
add_files /var/log/foreman/cron*.log*
|
|
add_files /var/log/foreman/db_migrate*.log*
|
|
add_files /var/log/foreman/db_seed*.log*
|
|
add_files /var/log/foreman/production.log-*
|
|
add_files /var/log/foreman/production.log
|
|
add_files /var/log/foreman/dynflow_executor.output*
|
|
add_files /var/log/foreman-proxy-certs-generate.*log
|
|
|
|
# Dynflow Sidekiq
|
|
add_files /etc/foreman/dynflow/*
|
|
add_cmd "systemctl list-units dynflow*" "dynflow_units"
|
|
add_cmd "journalctl -u dynflow-sidekiq@*" "dynflow_sidekiq_logs"
|
|
add_cmd 'systemctl status "system-dynflow\x2dsidekiq.slice"' "dynflow_sidekiq_status"
|
|
|
|
# exclude *key.pem files and encryption_key.rb
|
|
add_files /etc/foreman/*.{yml,yaml,conf} /etc/foreman/plugins/*.yaml
|
|
|
|
add_files /etc/foreman-installer/scenarios.d/{*,*/*,*/.*}
|
|
add_files /var/log/foreman-installer/
|
|
add_files /var/log/foreman-maintain/
|
|
add_files /etc/foreman-installer/custom-hiera.yaml
|
|
add_files /var/log/foreman-selinux-install.log
|
|
|
|
add_files /usr/share/foreman/Gemfile*
|
|
add_cmd "virsh list" "virsh_list"
|
|
add_files /etc/libvirt/* /etc/libvirt/storage/* /etc/libvirt/qemu/* /etc/libvirt/qemu/networks
|
|
add_files /var/lib/pgsql/data/*.conf
|
|
add_files /var/lib/puppet/ssl/certs/$(hostname -f).pem /var/lib/puppet/ssl/certs/ca.pem
|
|
add_files /etc/{httpd,apache2}/conf/*
|
|
add_files /etc/{httpd,apache2}/conf.d/*
|
|
add_files /etc/{httpd,apache2}/conf.d/*/*
|
|
add_files /var/log/{httpd,apache2}/*error_log*
|
|
add_files /var/log/{httpd,apache2}/foreman-ssl_access_ssl.log*
|
|
add_cmd "echo \"select id,name,value from settings where name not similar to '%(pass|key|secret)'\" | su postgres -c 'psql foreman'" "foreman_settings_table"
|
|
add_cmd "echo 'select type,name,host,port,account,base_dn,attr_login,onthefly_register,tls from auth_sources' | su postgres -c 'psql foreman'" "foreman_auth_table"
|
|
add_cmd "foreman-selinux-relabel -nv" "foreman_filecontexts"
|
|
|
|
add_files /etc/{sysconfig,default}/foreman
|
|
add_files /etc/{sysconfig,default}/libvirt*
|
|
add_files /etc/sysconfig/pgsql
|
|
add_files /var/lib/pgsql/data/pg_log/*
|
|
add_cmd "passenger-status --show=pool" "passenger_status_pool"
|
|
add_cmd "passenger-status --show=requests" "passenger_status_requests"
|
|
add_cmd "passenger-status --show=backtraces" "passenger_status_backtraces"
|
|
add_cmd "passenger-memory-stats" "passenger_memory"
|
|
|
|
# Look for any debug extensions provided by plugins
|
|
if [ -d "/usr/share/foreman/script/foreman-debug.d" ]; then
|
|
for extension in /usr/share/foreman/script/foreman-debug.d/* ; do
|
|
if [ -x "$extension" ]; then
|
|
printv "Processing extension $extension"
|
|
source "$extension" 2>/dev/null
|
|
# install cleanup trap again just in case it got overwritten
|
|
install_clean_trap
|
|
fi
|
|
done
|
|
fi
|
|
|
|
qprintf "\n\n"
|
|
qprintf "%10s %s\n" "HOSTNAME:" "$(hostname -f 2>/dev/null)"
|
|
qprintf "%10s %s\n" "OS:" "$OS"
|
|
qprintf "%10s %s\n" "RELEASE:" "$OS_RELEASE"
|
|
qprintf "%10s %s\n" "FOREMAN:" "$(cat /usr/share/foreman/VERSION 2>/dev/null)"
|
|
qprintf "%10s %s\n" "RUBY:" "$(ruby --version 2>/dev/null)"
|
|
qprintf "%10s %s\n" "PUPPET:" "$(puppet --version 2>/dev/null)"
|
|
test -f /var/log/audit/audit.log && \
|
|
qprintf "%10s %s\n" "DENIALS:" "$(ausearch -m AVC -r | wc -l)"
|
|
qprintf "\n\n"
|
|
|
|
if [ "$NOTAR" -eq 0 ]; then
|
|
pushd "$DIR" >/dev/null
|
|
printv "Compressing directory structure"
|
|
tar -c ../$(basename $DIR) 2>/dev/null | $COMPRESS > "$TARBALL"
|
|
popd >/dev/null
|
|
qprintf "%s: %s\n\n" "A debug file has been created" "$TARBALL ($(stat -c %s "$TARBALL") bytes)"
|
|
else
|
|
qprintf "%s: %s\n\n" "A debug directory has been created" "$DIR"
|
|
fi
|
|
|
|
printv "Finished in $SECONDS seconds"
|
|
|
|
exit 0
|