Files
activecm-rita/installer/install_scripts/sshprep.sh
T
Liza Tsibur 2bd5475e76 Installer and Import Logic Updates (#90)
* Disable permission flag checks on imported logs (#86)

* Disable permission flag checks on imported logs

* Display individual log errors even if there are no viable logs

---------

Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com>

* Installer Import Logic Updates (#89)

* rename sshprep to sshprep.sh

* installer updates

removed mass upgrades, cleaned up installer, removed references to unsupported OS's, improved error handling and usage messages

* threat intel feed error handling

* cleaned up installer scripts and improved helper functions

* Update ansible-installer.sh

---------

Co-authored-by: Naomi Kramer <naomi@activecountermeasures.com>
2026-01-20 17:50:56 -07:00

315 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
askYN() {
# Prints a question mark, reads repeatedly until the user
# responds with t/T/y/Y or f/F/n/N.
TESTYN=""
while [ "$TESTYN" != 'Y' ] && [ "$TESTYN" != 'N' ] ; do
echo -n '? ' >&2
read -e TESTYN <&2 || :
case $TESTYN in
T*|t*|Y*|y*) TESTYN='Y' ;;
F*|f*|N*|n*) TESTYN='N' ;;
esac
done
if [ "$TESTYN" = 'Y' ]; then
return 0 #True
else
return 1 #False
fi
}
fail() {
echo "$@ , exiting." >&2
exit 1
}
require_util() {
#Returns true if all binaries listed as parameters exist somewhere in the path, False if one or more missing.
while [ -n "$1" ]; do
if ! type -path "$1" >/dev/null 2>/dev/null ; then
echo Missing utility "$1". Please install it. >&2
return 1 #False, app is not available.
fi
shift
done
return 0 #True, app is there.
} #End of require_util
status() {
if [ "$verbose" = 'yes' ]; then
echo "== $@" >&2
fi
}
step() {
if [ "$step" = 'yes' ]; then
echo "About to $@ , continue" >&2
if askYN ; then
return
else
exit 99
fi
fi
}
show_help() {
echo "This tool does all the setup needed to ssh to one or more hosts using ssh keys." >&2
echo "Usage:" >&2
echo >&2
echo "$0 [host] [host] [host]..." >&2
echo -e "\tDo basic setup and install ssh key on all hosts specified on the command line." >&2
echo -e "\tHost may optionally include a username, like bob@www.example.com" >&2
echo "$0 -v [host]..." >&2
echo -e "\tBe verbose - show steps to be taken." >&2
echo "$0 -s [host]..." >&2
echo -e "\tSingle step mode - ask permission before making changes (verbose mode is automatically enabled in single step mode)." >&2
echo "$0 --authpath /etc/ssh/keys-root/ [host]..." >&2
echo -e "\tLocation of the authorized_keys file (useful for VMWare ESXi). This path is used for _all_ hosts on this command line." >&2
echo "$0 -h" >&2
echo "$0 --help" >&2
echo -e "\tShow this help text." >&2
exit 0
}
load_params() {
target_list=''
if [ "z$1" = "z" ]; then
echo "No target systems requested, this will perform basic ssh setup only." >&2
fi
while [ -n "$1" ]; do
case "$1" in
-v|--verbose)
verbose='yes'
;;
-s|--step)
verbose='yes'
step='yes'
;;
-h|--help)
show_help
;;
--authpath)
authpath="$2"
shift
;;
*)
target_list="$target_list $1"
;;
esac
shift
done
}
check_requirements() {
status "Checking system requirements"
require_util awk basename chmod cp date dig egrep head mkdir mktemp ssh ssh-add ssh-keygen touch uniq || fail "Missing one or more tools, please install and rerun"
step "perform system checks"
mkdir -p "$HOME/.ssh"
chmod -R go-rwx "$HOME/.ssh"
if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
fail 'We do not appear to have an ssh-agent to talk to. You may want to add the following 3 lines to ~/.bashrc on this system :
if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
eval $(ssh-agent -s)
fi
(and then logout, log back in, and try again.)'
fi
[ -w "$HOME/.ssh" ] || fail "User .ssh directory does not appear to be writeable"
[ -s "$HOME/.ssh/config" ] || touch "$HOME/.ssh/config"
}
check_key() {
status "Checking that we have a keypair available to load into the agent"
#Confirm we have an ssh RSA keypair; create if not.
if [ ! -s "$HOME/.ssh/id_rsa" ] && [ ! -s "$HOME/.ssh/id_rsa.pub" ]; then
status "No SSH RSA keypair, creating one. We strongly encourage you to provide a strong passphrase."
step "create ssh rsa keypair"
ssh-keygen -t rsa-sha2-512 -b 4096 -f "$HOME/.ssh/id_rsa" #We don't force the comment; "user@host" is the default
elif [ -s "$HOME/.ssh/id_rsa" ] && [ ! -s "$HOME/.ssh/id_rsa.pub" ]; then
status "private key available but no public; we'll create the public key. You will be asked for your id_rsa private key passphrase."
step "regenerate public key from private"
ssh-keygen -y -f "$HOME/.ssh/id_rsa" >"$HOME/.ssh/id_rsa.pub"
elif [ ! -s "$HOME/.ssh/id_rsa" ] && [ -s "$HOME/.ssh/id_rsa.pub" ]; then
status "Public key available, but no private"
fail "Missing private key, please provide $HOME/.ssh/id_rsa"
elif [ -s "$HOME/.ssh/id_rsa" ] && [ -s "$HOME/.ssh/id_rsa.pub" ]; then
status "Both private and public keys available"
fi
}
check_agent() {
status "Checking that keys are loaded into the agent"
#Confirm we have at least one key loaded into ssh-agent.
if [ $(ssh-add -l | wc -l) -eq 0 ] || [ "$(ssh-add -l)" = 'The agent has no identities.' ]; then
#No keys currently loaded into the agent, load at least one
[ -s "$HOME/.ssh/id_rsa" -a -s "$HOME/.ssh/id_rsa.pub" ] || check_key
status "About to load keys into ssh-agent; you may be asked for your passphrase(s)"
step "load keys into agent"
ssh-add
fi
if [ $(ssh-add -l | wc -l) -eq 0 ] || [ "$(ssh-add -l)" = 'The agent has no identities.' ]; then
fail "Unable to get any keys into the ssh-agent"
fi
status "ssh-agent has at least one key loaded, continuing"
}
prepend_config_block() {
#We prepend new stanzas since the end of the file may contain a default stanza like "Host * ......"
if [ -s "$HOME/.ssh/config" ]; then
cp -p "$HOME/.ssh/config" "$HOME/.ssh/config.$(date '+%Y%m%d%H%M%S%N')"
elif [ ! -e "$HOME/.ssh/config" ]; then
touch "$HOME/.ssh/config"
fi
new_config=$(mktemp -q /tmp/$(basename $0).XXXXXX) || fail "Unable to make temporary file during ~/.ssh/config update."
echo -e "$1" >"$new_config"
cat "$HOME/.ssh/config" >>"$new_config"
step "move new config file ($new_config) over existing $HOME/.ssh/config"
mv "$new_config" "$HOME/.ssh/config"
}
check_config_block() {
#Make sure we have a config block for this host, and add one if not
#Param 1: user, Param 2: host, Param 3: optional hostname or ip address to use as "Hostname"
status "Checking if we have a config block for $2"
if [ -n "$1" ]; then
user_line=" User ${1}"
else
user_line=''
fi
if egrep -q "(^Host.*[[:space:]]${2}[[:space:]]|^Host.*[[:space:]]${2}\$)" "$HOME/.ssh/config" ; then
status "$2 has a configuration block in ~/.ssh/config"
else
status "$2 does not have a configuration block in ~/.ssh/config, adding one."
if [ -n "$3" ]; then
status "User-supplied target IP or hostname ($3) for $2"
prepend_config_block "\nHost ${2} ${3}\n\tHostname\t\t${3}\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
elif [ -n "$(which getent 2>/dev/null)" ] && [ -n "$(getent hosts "$2")" ]; then
status "running on non-mac, appears we have an entry in /etc/hosts"
ip4=$(getent ahostsv4 "$2" | awk '{print $1}' | uniq | head -1)
ip6=$(getent ahostsv6 "$2" | awk '{print $1}' | uniq | head -1)
if [ -n "$ip4" -a -n "$ip6" ]; then
status "Have both ipv4 and ipv6 addresses for $2, adding both blocks"
prepend_config_block "\nHost ${2} ${ip4}\n\tHostname\t\t$ip4\n\tHostKeyAlias\t\t${2}\n${user_line}\n\nHost ${2}-v6 ${ip6}\n\tHostname\t\t$ip6\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
elif [ -n "$ip4" -o -n "$ip6" ]; then #One or the other but not both, which is why we can have both ip4 and ip6 on the Hostname line.
prepend_config_block "\nHost ${2} ${ip4}${ip6}\n\tHostname\t\t${ip4}${ip6}\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
#We don't include a block for neither ip4 nor ip6 set as "getent hosts" did successfully get one or the other.
fi
elif [ -n "$(which dscacheutil 2>/dev/null)" ] && [ -n "$(dscacheutil -q host -a name "$2")" ]; then
status "running on mac, appears we have an entry in /etc/hosts"
local_ip=$(dscacheutil -q host -a name "$2" | grep '^ip_address' | awk '{print $2}')
prepend_config_block "\nHost ${2} ${local_ip}\n\tHostname\t\t${local_ip}\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
else
status "No user-supplied target IP or hostname for $2, look one up"
ip4=$(dig +nocomment +short ${2} A 2>/dev/null | sed -e 's/;;.*//' | grep -v '^$' | head -1)
ip6=$(dig +nocomment +short ${2} AAAA 2>/dev/null | sed -e 's/;;.*//' | grep -v '^$' | head -1)
if [ -n "$ip4" ]; then
if [ -n "$ip6" ]; then
status "Have both ipv4 and ipv6 addresses for $2, adding both blocks"
prepend_config_block "\nHost ${2} ${ip4}\n\tHostname\t\t${ip4}\n\tHostKeyAlias\t\t${2}\n${user_line}\n\nHost ${2}-v6 ${ip6}\n\tHostname\t\t${ip6}\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
else
status "Have an IPv4 address only for $2"
prepend_config_block "\nHost ${2} ${ip4}\n\tHostname\t\t${ip4}\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
fi
else
if [ -n "$ip6" ]; then
status "Have an ipv6 address only for $2"
prepend_config_block "\nHost ${2} ${2}-v6 ${ip6}\n\tHostname\t\t${ip6}\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
else
#echo "== No ipv4 or ipv6 address found for ${2}. Please enter an IP address to use:" >&2
#read ip_or_hostname <&2
#if [ -z "$ip_or_hostname" ]; then
# ip_or_hostname="$2"
#fi
status "We could not lookup a DNS address for $2 so we will just use $2 in the ssh Hostname field."
ip_or_hostname="$2"
prepend_config_block "\nHost ${2} ${ip_or_hostname}\n\tHostname\t\t${ip_or_hostname}\n\tHostKeyAlias\t\t${2}\n${user_line}\n"
fi
fi
fi
fi
}
sshprep_main() {
load_params "$@"
check_requirements
check_agent
status "Retrieving public key(s)"
pub_key_content="$(ssh-add -L)"
retval=0
for one_target in $target_list ; do
step "start setting up $one_target"
status "Setting up ssh connection to $one_target"
if echo "$one_target" | grep -q '@' - ; then
#We have an "@" in the target, so this is "user@host"
one_user="${one_target%%@*}"
one_host="${one_target##*@}"
else
#No "@" in target, so this is just a host
one_user=''
one_host="$one_target"
fi
#Check if we have a config block for this target; create one if not, place at top of file
check_config_block "$one_user" "$one_host" #FIXME - arrange a way for the user to supply an optional hostname/ip as param3
status "Checking that we can now connect without using a password."
status "If this is your first time connecting to $one_target, you may be asked to verify a fingerprint. To see the correct value, run 'ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key.pub' on that system's console."
step "confirm $one_target reachable by key"
if ssh -o 'PasswordAuthentication=no' -o 'PreferredAuthentications=publickey' "$one_target" 'touch .ssh/reached-by-key ; true' 2>/dev/null ; then
status "Key appears to be successfully installed on $one_target"
else
status "Sending public key(s) over to $one_target . You may be asked for your password for this account."
step "send public keys over to $one_target"
if [ -n "$authpath" ]; then
echo "$pub_key_content" \
| ssh "$one_target" 'mkdir -p '"$authpath"' ; cat >>'"$authpath"'/authorized_keys ; chmod go-rwx '"$authpath"' '"$authpath"'/authorized_keys'
else
echo "$pub_key_content" \
| ssh "$one_target" 'mkdir -p .ssh ; cat >>.ssh/authorized_keys ; chmod go-rwx ./ .ssh/ .ssh/authorized_keys'
fi
step "confirm $one_target reachable by key"
if ssh -o 'PasswordAuthentication=no' -o 'PreferredAuthentications=publickey' "$one_target" 'touch .ssh/reached-by-key ; true' 2>/dev/null ; then
status "Key is now successfully installed on $one_target"
else
status "Unable to reach $one_target by ssh key."
retval=2
fi
fi
done
exit "$retval"
}
sshprep_main "$@"