Host *+*
ProxyCommand ssh $(echo %h | sed 's/+[^+]*$//;/+/!{s/%%/@/;s/:/ -p /};s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/') PATH=.:\$PATH nc -w1 $(echo %h | sed 's/^.*+//;/:/!s/$/ %p/;s/:/ /')
Host *+*
ProxyCommand ssh $(echo %h | sed 's/+[^+]*$//;/+/!{s/%%/@/;s/:/ -p /};s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/') PATH=.:\$PATH nc -w1 $(echo %h | sed 's/^.*+//;/:/!s/$/ %p/;s/:/ /')
The syntax you can use to connect through jump hosts is the following:
ssh login1%host1:port1+login2%host2:port2+host3:port3 -l login3
We'll see further down why we can't put a login for the final destination host. Or if you want to skip the boring implementation details, you can also skip to the end, where a slightly more compact version lies: while writing this post, I got some optimization ideas.
Let me try to split the sed syntax to make it a little more understandable:
s/+[^+]*$//; # Remove all characters starting from the last "+" in the string (i.e. keep the n - 1 first hops)
/+/!{ # If the string doesn't contain a "+" after the previous command (i.e. there is only one hop remaining), do the following, otherwise, skip until the closing curly brace
s/%%/@/; # Replace "%" with "@" (% is doubled because of the ProxyCommand)
s/:/ -p / # Replace ":" with " -p ". Combined with the previous command, we rewrite "login%host:port" as "login@host -p port"
};
s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/ # Rewrite "hop1+hop2+login%lasthop" as "hop1+hop2+lasthop -l login"
The second sed goes like this:
s/^.*+//; # Remove everything up to the last occurrence of "+" in the string (i.e. only keep the last hop)
/:/! # If there is no ":" in the string, do the following, otherwise, skip the next statement
s/$/ %p/; # Add " %p" at the end of the line
s/:/ / # Replace ":" with " ". These last three instructions prepare a "host port" combination for use with nc.
Let's see what the ProxyCommand looks like for some examples, skipping PATH setting and nc option for better readability:
host1+host2
ssh host1 nc host2 %p
login1%host1+host2:port2
ssh login1@host1 nc host2 port2
login1%host1:port1+host2:port2
ssh login1@host1 -p port1 nc host2 port2
Now, maybe you start to see why we can't put a login on the last hop: not only does it make no sense for nc, but also the main ssh process that runs the ProxyCommand and will actually talk to the remote host won't have any knowledge of it.
From the above examples, it also appears obvious why we replace `login%host:port` with `login@host -p port`: so that the ssh command in the ProxyCommand gets the proper arguments for login and port. Note we could also replace with `host -p port -l login` for the same effect.
With even more hops, this is what happens:
host1+host2+host3
ssh host1+host2 nc host3 %p
login1%host1:port1+host2:port2+host3
ssh login1%host1:port1+host2:port2 nc host3 %p
Each of these ProxyCommands will trigger another ProxyCommand quite looking like our first few examples.
In the first hops, if we'd just replace all "%" with "@", in cases where logins are given everywhere, we'd end up with a ProxyCommand like the following:
ssh login%host1+login%host2 nc host3 %p
As we saw above, we can't use a login on the last hop, which means this ProxyCommand wouldn't work as expected and is why we have to rewrite `login1%host1+login2%hop2` as `login%host1+host2 -l login2` with the last instruction in the first sed.
In the end, this is what happens:
login1%host1+login2%host2+host3
ssh login1%host1+host2 -l login2 nc host3 %p
login1%host1:port1+login2%host2:port2+host3:port3
ssh login1%host1:port1+host2:port2 -l login2 nc host3 %p
In this last example, you see the `:port2` part is not changed into `-p port2`. Actually, it would still work with the latter form: the ProxyCommand `ssh login1%host1:port1+host2 -p port2 -l login2` would itself have `ssh login1@host1 -p port1 nc host2 %p` as ProxyCommand, in which %p would be replaced by port2, given as argument to -p.
Based on this and with further small optimizations, we can slightly shorten our ProxyCommand to the following:
Host *+*
ProxyCommand ssh $(echo %h | sed 's/+[^+]*$//;s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/;s/:/ -p /') PATH=.:\$PATH nc -w1 $(echo %h | sed 's/^.*+//;/:/!s/$/ %p/;s/:/ /')
`sed` instructions "decryption" is left as an exercise to the reader ;)
© 1995-2023 Mike Hommey <mh (at) glandium (dot) org>.
Creative Commons License
The contents of this site are licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.