SSH wrapper that tries several connection parameters
I'm looking for a SSH wrapper (or SSH option, if there was one), that can try connecting to several IP addresses sequentially until one of them succeeds.
For example 10.0.0.1
, then my_machine.example.com
and finally my_machine.example.com -J me@other_machine.example.com
.
Is there any tool that does this?
Solution 1:
This is my general purpose ssh
wrapper. No option nor address is hardcoded. The only thing you may need to adjust is the path to your ssh
executable in the line 3 (you can use executable=ssh
, I chose the full path). You will find my full code down below.
Let's say you saved it as sshmt
("ssh, multi target") where your $PATH
points to, made executable with chmod
. Then get familiar with the syntax:
sshmt -h
Excerpt:
USAGE
sshmt [-v] ARGS [+[N] ARGS]... [-- COMMON] sshmt -h
SYNOPSIS
Invokes
ssh
command with the first set of argumentsARGS
and common argumentsCOMMON
. If this command returns exit status of255
and the second set of argumentsARGS
exists, then the secondssh
will be invoked with these newARGS
andCOMMON
; then the third and so on.
In your example case you want to invoke it like this:
sshmt 10.0.0.1 + my_machine.example.com + my_machine.example.com -J me@other_machine.example.com
or better with some convenient timeouts:
sshmt 10.0.0.1 +2 my_machine.example.com +3 my_machine.example.com -J me@other_machine.example.com +5
To remotely execute df -h
in a straightforward way, invoke:
sshmt 10.0.0.1 df -h +2 my_machine.example.com df -h +3 my_machine.example.com -J me@other_machine.example.com df -h +5
but you don't want to repeat yourself, so use this instead:
sshmt 10.0.0.1 +2 my_machine.example.com +3 my_machine.example.com -J me@other_machine.example.com +5 -- df -h
Pipes should work as well:
echo 123 | sshmt 10.0.0.1 +2 my_machine.example.com +3 my_machine.example.com -J me@other_machine.example.com +5 -- sh -c "cat > /tmp/foo"
In practice you may want to define an alias:
alias myssh='sshmt 10.0.0.1 +2 my_machine.example.com +3 my_machine.example.com -J me@other_machine.example.com +5 --'
then log in with
myssh
or execute a command like
myssh uptime
This is the code. All of its logic just parses the command line really.
#!/usr/bin/env bash
executable=/usr/bin/ssh
exename="${executable##*/}"
myname="${0##*/}"
declare -a args
declare -a seq_opts
declare -a common_opts
main () {
split_opts "$@"
process_seq "${seq_opts[@]}" "+"
exit 255
}
split_opts () {
while [ $# -ne 0 ]; do
if [ "$1" = "--" ]; then
shift
common_opts=("$@")
break
else
seq_opts=("${seq_opts[@]}" "$1")
shift
fi
done
}
process_seq() {
if [ "$*" = "+" ] || [ "$1" = "-h" ]; then
print_help; exit 0
fi
while [ $# -ne 0 ]; do
if [ "${1:0:1}" != "+" ]; then
args=("${args[@]}" "$1")
else
timeout="${1:1}"
[[ "$timeout" =~ ^[0-9]*$ ]] || print_error
if [ "${#args[*]}" -ne 0 ]; then
printf '%s\n' "${myname}: trying ${args[*]}" >&2
"$executable" ${timeout:+-o ConnectTimeout=$timeout} "${args[@]}" "${common_opts[@]}"
status=$?
[ $status -ne 255 ] && exit $status
args=()
fi
fi
shift
done
}
print_error() {
cat >&2 << EOF
${myname}: error parsing command line
Try '$myname -h' for more information.
EOF
exit 254
}
print_help() {
cat << EOF
USAGE
$myname [-v] ARGS [+[N] ARGS]... [-- COMMON]
$myname -h
SYNOPSIS
Invokes \`${exename}' command with the first set of arguments ARGS
and common arguments COMMON. If this command returns
exit status of 255 and the second set of arguments ARGS
exists, then the second \`ssh' will be invoked with these
new ARGS and COMMON; then the third and so on.
Empty set of arguments is discarded without invoking \`ssh'.
Successful invocation of \`ssh' stops parsing the command
line and makes the script exit.
OPTIONS
-h print this help and exit (must be the first option)
+, +N execute \`ssh' with preceding ARGS and COMMON
N, if given, specifies timeout for \`ssh' invoked with
immediately preceding ARGS. This is just a convenient
alternative for \`-o ConnectTimeout=N'.
The final set of arguments may or may not have a terminating \`+'.
EXIT STATUS
The exit status is 254 in case of an error while parsing
the command line; 255, if none of \`${exename}' managed
to connect; or an exit status of successfully connected
\`${exename}' otherwise.
EXAMPLES
To try 10.0.0.1 and, if failed, the alternative address:
$myname 10.0.0.1 + my_machine.example.com
To execute \`df -h' with timeouts:
$myname 10.0.0.1 +3 my_machine.example.com +5 -- df -h
LICENCE
Creative Commons CC0.
EOF
}
main "$@"
Solution 2:
As far as I known there is no such built-in feature. However this can be easily scripted:
#!/bin/bash
usage ()
{
echo "usage:"
echo " $0 MYHOST"
echo "or"
echo " $0 IP DNS PROXYJUMP"
}
if [[ $# -eq 1 ]]; then
host="$1"
ssh ${host}_ip && exit 0
ssh ${host}_dns && exit 0
ssh ${host}_proxyjump && exit 0
exit 1
else if [[ $# -eq 3 ]]; then
ip="$1"
dns="$2"
proxy="$3"
ssh "$ip" && exit 0
ssh "$dns" && exit 0
ssh "$dns" -J "$proxy" && exit 0
exit 1
else
echo "Illegal number of argument"
usage
exit 1
fi
With the following .ssh/config
file:
Host MYHOST_ip
Hostname 10.0.0.1
Host MYHOST_dns
Hostname my_machine.example.com
Host MYHOST_proxyjump
Hostname my_machine.example.com
ProxyJump me@other_machine.example.com
Note that connection may take a long time for example in case of the use of the proxyjump configuration. In fact, connection may occur after 2 timeouts.
Solution 3:
This sound pretty similar to a This Other Post
Shameless repost
This is a GREAT question, I always wondered too.
The short answer is No. You cannot. So I decided to do it myself.
You can use a simple script to get the desired functionality.
I wrote a Github Pages post for this
TLDR
On UNIX, Try this:
(On Windows, read my Github Pages Post)
~/.ssh/scripts/check-host-fingerprint.sh
#!/bin/bash
fingerprints=$(ssh-keygen -lf <(ssh-keyscan $1 2>/dev/null))
for fingerprint in $fingerprints
do
if [ "$fingerprint" == "$2" ]
then
exit 0
fi
done
exit 1
~/.ssh/config
# Host with Global Fallback
Match host "my_auto_host" exec "/bin/bash %d/.ssh/scripts/check-host-fingerprint.sh 192.168.0.100 SHA256:12345678901234567890123456789012345678901234567"
Hostname 192.168.0.100
Port 22
Host my_auto_host
User username
Hostname server.domain.org
Port 1022
# Secondary Host using Primary as Proxy
Host secondary
User u5ernam3
Hostname 192.168.0.101
Port 22
ProxyJump my_auto_host
Please read my Github Pages post if this doesn't work for you.