0

It seems a simple thing, but I am stuck.

On an Ubuntu machine, to ssh into a particular REMOTE_HOST machine, until now I could do

ssh REMOTE_HOST

as expected (keys are all setup fine). Now, I am forced to setup an ssh tunnel through a particular BRIDGE_HOST machine with

ssh -L 2222:REMOTE_HOST:22 BRIDGE_HOST -N -f

and then I can ssh to the REMOTE_HOST with

ssh -p 2222 localhost

This works perfectly fine although things are a little more complicated. Here are a few problems:

  • localhost runs OpenSSH_7.2p2 so there is no -J / ProxyJump option, if that would help. I am not able to update OpenSSH, or at least I would prefer to avoid it.

  • I must login on REMOTE_HOST with a password so I need to use sshpass (please do not tell me that I should not, I have no control on this). So the first ssh command above is not quite as shown but more complicated; yet, I think it adds nothing to show the more complex version. But this is a constraint I have and, I am afraid, prevents some cleaner solutions.

  • There is no shell on REMOTE_HOST and as far as I can tell (or I understand) I am only allowed to setup tunnels.

Anyway, I can ssh into REMOTE_HOST with these two steps, but what I would like to do is to hide all this in .ssh/config to make this transparent (especially for CVS, see below). Something like this appears to be fine:

Host REMOTE_HOST
   Hostname localhost
   Port 2222
   ProxyCommand tcsh -c "ssh -L 2222:REMOTE_HOST:22 BRIDGE_HOST -N -f; nc %h %p"

If I now run

ssh REMOTE_HOST

I can ssh into REMOTE_HOST as if the tunnel did not exist. Great.

The final source of complication (and the question) is that I want to use this ssh connection also for CVS. Everything is certainly setup fine from the CVS side (e.g., it was working when I did not have to use BRIDGE_HOST). And if I now run

cvs up

in an appropriate directory, everything seems to work fine... but the cvs command, after doing its job properly, never terminates. I seem to understand that this is because the ssh creating the tunnel is still running at the end. I would be happy to kill it, but I cannot figure out how. Relatedly, note that if I omit ProxyCommand in .ssh/config and if the tunnel is setup manually before, all is perfectly fine and CVS runs smoothly (and terminates). I would forget ProxyCommand and set up autossh but I understand it does not work with sshpass. I could setup a cron job or some script that manually keeps the tunnel alive, but I think (or hope) that there must be a better way....

What is the correct, simplest, and cleanest way to achieve the result I desire? That is, simply use cvs as always despite the insertion of BRIDGE_HOST on which I have no control whatsoever?

1
  • I am thinking that the correct thing to do would be to close the tunnel once nc has run. But I have a problem and I do not understand what happens. Imagine for a moment that the tunnel is setup manually before running cvs up and I set ProxyCommand sh -c ">&2 echo START ; /bin/nc %h %p ; >&2 echo STOP", CVS runs fine, "START" is sent to the console before CVS runs, but "STOP" never appears. If I run the same command in the shell with %h = localhost and %p = 2222, the behaviour is as expected and STOP appears once nc dies with a CTRL-D. Why?!
    – Paolo
    Commented Jul 26, 2022 at 4:54

2 Answers 2

0

localhost runs OpenSSH_7.2p2 so there is no -J / ProxyJump option, if that would help. I am not able to update OpenSSH, or at least I would prefer to avoid it.

A near-equivalent to ProxyJump is:

Host REMOTE_HOST
    ProxyCommand "ssh -W %h:%p BRIDGE_HOST"

This is actually what ProxyJump does in the background. The only real difference between the two is that ProxyJump would automatically copy certain other options (e.g. verbose -v mode) to all connections involved.

If the bridge server allows creating tunnels using -L then it automatically allows -W (and -D as well), as they all use the same SSHv2 channel type; the server doesn't know the difference.

This should also work with sshpass as the command. Alternatively, to avoid having to retype the password multiple times, you could use connection multiplexing for the bridge host:

Host BRIDGE_HOST
    ControlMaster auto
    ControlPath ~/.ssh/S.%r@%h:%p

The first connection to BRIDGE_HOST will leave a process in background; alternatively, you can use ssh -fNM BRIDGE_HOST (with sshpass if you want) to start the background process, which future ssh -w ... BRIDGE_HOST will automatically use.


In your tcsh+nc example, the command would be better written as:

Host REMOTE_HOST
    ProxyCommand tcsh -c "ssh -L 2222:%h:%p BRIDGE_HOST -N -f; nc localhost 2222"
5
  • user1686, about the near equivalence to ProxyJump, I assume you mean -W instead of -w (the latter is even more obscure to me and it looks like I am not alone: stackoverflow.com/questions/72270772/what-does-ssh-w). I did try it as ProxyCommand sshpass -p PASSWORD ssh -W %h:%p BRIDGE_HOST -l BRIDGE_USER and, although I am not sure to understand it exactly, I get channel 0: open failed: connect failed: open failed --stdio forwarding failed -- ssh_exchange_identification: Connection closed by remote host. I am afraid that I have no clue of what this means....
    – Paolo
    Commented Jul 26, 2022 at 19:52
  • Yeah, it was supposed to be uppercase -W. Try running the command separately from shell (replacing %h:%p with the exact same REMOTE_HOST:22 as you had in your original -L value – minus the local port) and see if it connects you to an "SSH-2.0-..." greeting message. Commented Jul 26, 2022 at 19:56
  • Yeees! Fantastic! I think you may have got wrong the %h and %p as I believe your correction to my tsch+nc example is incorrect in a similar way: %h is localhost and $p is 2222. But ProxyCommand sshpass -p PASSWORD ssh -W REMOTE_HOST:22 BRIDGE_HOST -l BRIDGE_USER appears to work perfectly well.
    – Paolo
    Commented Jul 26, 2022 at 20:04
  • No, I think that's because you're still adding HostName localhost and Port 2222 parameters to your config... They were deliberately left out in the examples, the original hostname from the ssh REMOTE_HOST command should carry over to %h by default. Commented Jul 26, 2022 at 20:06
  • I must be dumb but should it not work also with Host *.REMOTE_DOMAIN instead of Host REMOTE_HOST? Checking with sh -c ">&2 echo [...]", the command issued appears exactly the same (as it should), but then it hangs if I use the *....
    – Paolo
    Commented Jul 26, 2022 at 20:43
0

Since I did not manage to understand how to kill the tunnel once CVS is done with it and since I cannot use autossh with sshpass, I decided to add a cron job.

I first created a small script putting together a bunch of recipes and ideas found around on the web. Here is the script:

myself=${BASH_SOURCE[0]}

for pid in $(pgrep -d " " $myself); do
    if [ $pid != $$ ]; then
        kill -9 $pid
    fi 
done

while true
do
    pkill -f "sshpass.*2222:"
    sshpass -p PASSWORD ssh -o ServerAliveInterval=60 -L 2222:REMOTE_HOST:22 BRIDGE_HOST -l USER -N
    sleep 1
done

Essentially it kills any other copy of itself first, then kills existing tunnel(s) on the same port and brings the required tunnel up, repeating this forever anytime ssh dies. It is a sort of poor man version of autossh.

Together with this .ssh/config file

Host REMOTE_HOST
   Hostname localhost
   Port 2222

this makes ssh (and CVS) behave as if REMOTE_HOST were still directly accessible.

The script can be conveniently invoked through cron:

  @reboot                            /PATH/TO/THE/SCRIPT &
  30         4     *     *     *     /PATH/TO/THE/SCRIPT &

The daily restart at 4:30am is probably perfectly unnecessary, but somehow seems a safe thing to do, in case something goes wrong.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .