mirror of
https://github.com/activecm/rita
synced 2026-06-08 13:02:45 +00:00
2bd5475e76
* 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>
315 lines
11 KiB
Bash
Executable File
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 "$@"
|