{"id":308,"date":"2009-04-11T23:25:50","date_gmt":"2009-04-11T21:25:50","guid":{"rendered":"http:\/\/glandium.org\/blog\/?p=308"},"modified":"2010-01-27T08:52:24","modified_gmt":"2010-01-27T07:52:24","slug":"following-up-on-ssh-through-jump-hosts","status":"publish","type":"post","link":"https:\/\/glandium.org\/blog\/?p=308","title":{"rendered":"Following up on SSH through jump hosts"},"content":{"rendered":"<p>I got <a href=\"\/blog\/?p=303#comments\">interesting comments<\/a> on my <a href=\"\/blog\/?p=303\">previous post about SSH through jump hosts<\/a>, that made me think a bit.<\/p>\n<p>The first one of them suggested to use a syntax that unfortunately doesn't work on anything other than zsh, but had the nice idea to add a login for each jump server. Sadly, this only works when there is only one hop, for reasons similar to those we will explore below.<\/p>\n<p>The second one suggested \"\/\" would not be so good a separator, because it would fail in svn+ssh urls and such, which is a good point that I didn't think of only by lack of such use case. The simplicity of the resulting configuration was nice enough.<\/p>\n<p>This decided me to take a shot at implementing something that would allow adding both login and port number for each jump server.<\/p>\n<p>The first implementation detail to set was which characters to use for login, port number and hop separators. I chose respectively \"%\" (@ is unfortunately impossible to use because ssh would take it, leaving out any hop before the character), \":\" (pretty standard) and \"+\" (not allowed in DNS, and not too illogical as such a separator, I thought).<\/p>\n<p>The result can look like dark magic when you are not savvy with <code>sed<\/code> and regular expressions:<\/p>\n<blockquote><p><code>Host *+*<br \/>\nProxyCommand ssh $(echo %h | sed 's\/+[^+]*$\/\/;\/+\/!{s\/%%\/@\/;s\/:\/ -p \/};s\/\\([^+%%]*\\)%%\\([^+]*\\)$\/\\2 -l \\1\/') PATH=.:\\$PATH nc -w1 $(echo %h | sed 's\/^.*+\/\/;\/:\/!s\/$\/ %p\/;s\/:\/ \/')<\/code><\/p><\/blockquote>\n<p>The syntax you can use to connect through jump hosts is the following:<\/p>\n<blockquote><p><code>ssh login1%host1:port1+login2%host2:port2+host3:port3 -l login3<\/code><\/p><\/blockquote>\n<p>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.<\/p>\n<p>Let me try to split the <code>sed<\/code> syntax to make it a little more understandable:<\/p>\n<blockquote><p><code>s\/+[^+]*$\/\/;<\/code> <em># Remove all characters starting from the last \"+\" in the string (i.e. keep the n - 1 first hops)<\/em><br \/>\n<code>\/+\/!{<\/code> <em># 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<\/em><br \/>\n<code>s\/%%\/@\/;<\/code> <em># Replace \"%\" with \"@\" (% is doubled because of the <code>ProxyCommand<\/code>)<\/em><br \/>\n<code>s\/:\/ -p \/<\/code> <em># Replace \":\" with \" -p \". Combined with the previous command, we rewrite \"login%host:port\" as \"login@host -p port\"<\/em><br \/>\n<code>};<\/code><br \/>\n<code>s\/\\([^+%%]*\\)%%\\([^+]*\\)$\/\\2 -l \\1\/<\/code> <em># Rewrite \"hop1+hop2+login%lasthop\" as \"hop1+hop2+lasthop -l login\"<\/em>\n<\/p><\/blockquote>\n<p>The second <code>sed<\/code> goes like this:<\/p>\n<blockquote><p><code>s\/^.*+\/\/;<\/code> <em># Remove everything up to the last occurrence of \"+\" in the string (i.e. only keep the last hop)<\/em><br \/>\n<code>\/:\/!<\/code> <em># If there is no \":\" in the string, do the following, otherwise, skip the next statement<br \/>\n<code>s\/$\/ %p\/;<\/code> <\/em><em># Add \" %p\" at the end of the line<\/em><br \/>\n<code>s\/:\/ \/<\/code> <em># Replace \":\" with \" \". These last three instructions prepare a \"host port\" combination for use with <code>nc<\/code>.<\/em><\/p><\/blockquote>\n<p>Let's see what the <code>ProxyCommand<\/code> looks like for some examples, skipping <code>PATH<\/code> setting and <code>nc<\/code> option for better readability:<\/p>\n<dl>\n<dt><code>host1+host2<\/code><\/dt>\n<dd><code>ssh host1 nc host2 %p<\/code><\/dd>\n<dt><code>login1%host1+host2:port2<\/code><\/dt>\n<dd><code>ssh login1@host1 nc host2 port2<\/code><\/dd>\n<dt><code>login1%host1:port1+host2:port2<\/code><\/dt>\n<dd><code>ssh login1@host1 -p port1 nc host2 port2<\/code><\/dd>\n<\/dl>\n<p>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 <code>ssh<\/code> process that runs the <code>ProxyCommand<\/code> and will actually talk to the remote host won't have any knowledge of it.<\/p>\n<p>From the above examples, it also appears obvious why we replace \"login%host:port\" with \"login@host -p port\" : so that the <code>ssh<\/code> command in the <code>ProxyCommand<\/code> gets the proper arguments for login and port. Note we could also replace with \"host -p port -l login\" for the same effect.<\/p>\n<p>With even more hops, this is what happens:<\/p>\n<dl>\n<dt><code>host1+host2+host3<\/code><\/dt>\n<dt>\n<dd><code>ssh host1+host2 nc host3 %p<\/code><\/dd>\n<\/dt>\n<dt><code>login1%host1:port1+host2:port2+host3<\/code><\/dt>\n<dd><code>ssh login1%host1:port1+host2:port2 nc host3 %p<\/code><\/dd>\n<\/dl>\n<p>Each of these <code>ProxyCommand<\/code>s will trigger another <code>ProxyCommand<\/code> quite looking like our first few examples.<\/p>\n<p>In the first hops, if we'd just replace all \"%\" with \"@\", in cases where logins are given everywhere, we'd end up with a <code>ProxyCommand<\/code> like the following:<\/p>\n<blockquote><p><code>ssh login%host1+login%host2 nc host3 %p<\/code><\/p><\/blockquote>\n<p>As we saw above, we can't use a login on the last hop, which means this <code>ProxyCommand<\/code> 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 <code>sed<\/code>.<\/p>\n<p>In the end, this is what happens:<\/p>\n<dl>\n<dt>login1%host1+login2%host2+host3<\/dt>\n<dd>ssh login1%host1+host2 -l login2 nc host3 %p<\/dd>\n<dt>login1%host1:port1+login2%host2:port2+host3:port3<\/dt>\n<dd>ssh login1%host1:port1+host2:port2 -l login2 nc host3 %p<\/dd>\n<\/dl>\n<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 <code>ProxyCommand<\/code> <code>ssh login1%host1:port1+host2 -p port2 -l login2<\/code> would itself have <code>ssh login1@host1 -p port1 nc host2 %p<\/code> as <code>ProxyCommand<\/code>, in which <code>%p<\/code> would be replaced by port2, given as argument to <code>-p<\/code>.<\/p>\n<p>Based on this and with further small optimizations, we can slightly shorten our <code>ProxyCommand<\/code> to the following :<\/p>\n<blockquote><p><code>Host *+*<br \/>\nProxyCommand ssh $(echo %h | sed 's\/+[^+]*$\/\/;s\/\\([^+%%]*\\)%%\\([^+]*\\)$\/\\2 -l \\1\/;s\/:\/ -p \/') PATH=.:\\$PATH nc -w1 $(echo %h | sed 's\/^.*+\/\/;\/:\/!s\/$\/ %p\/;s\/:\/ \/')<\/code><\/p><\/blockquote>\n<p><code>sed<\/code> instructions \"decryption\" is left as an exercise to the reader ;).<\/p>\n<p><b>Update :<\/b> modified the shortened version to fix the issue spotted in the comments.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I got interesting comments on my previous post about SSH through jump hosts, that made me think a bit. The first one of them suggested to use a syntax that unfortunately doesn&#8217;t work on anything other than zsh, but had the nice idea to add a login for each jump server. Sadly, this only works [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[23],"class_list":["post-308","post","type-post","status-publish","format-standard","hentry","category-pdo","tag-en"],"_links":{"self":[{"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=\/wp\/v2\/posts\/308","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=308"}],"version-history":[{"count":23,"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=\/wp\/v2\/posts\/308\/revisions"}],"predecessor-version":[{"id":645,"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=\/wp\/v2\/posts\/308\/revisions\/645"}],"wp:attachment":[{"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=308"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=308"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/glandium.org\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=308"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}