24

I run these two commands all the time to connect to my rds instance on aws that's protected behind a firewall (so i tunnel through the ec2 instance) like so:

command 1: open the tunnel (run on background)

ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub &

command 2: connect to db through tunnel port:

PGPASSWORD=password psql dbname -U user -h ip_address -p port;

which is awesome, but I would like to put both these in a single function. But nothing worked with me:

attempt 1: run without background stuff:

function db()
{
    ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub &
    PGPASSWORD=password psql dbname -U user -h ip_address -p port;
}

simply tells me this:

$proddb
[1] 62924
psql: could not connect to server: Connection refused
    Is the server running on host "127.0.0.1" and accepting
    TCP/IP connections on port 6666?

although the initial command is running in the background:

ps aux | grep host
(standard input):435:abdullah         62924   0.0  0.0  4315660   5828 s006  S     3:06PM   0:00.03 ssh -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub

and if i immediately run the next command after it.. i connect to the db just fine!

PGPASSWORD=password psql dbname -U user -h ip_address -p port;
user=>

how do I make this work?

2 Answers 2

29

The first command didn't have time to establish a tunnel when the second command was run, thus giving a "Connection refused".

Just don't use & but use instead the option -f:

-f
Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies -n. The recommended way to start X11 programs at a remote site is with something like ssh -f host xterm.
If the ExitOnForwardFailure configuration option is set to “yes”, then a client started with -f will wait for all remote port forwards to be successfully established before placing itself in the background.

Putting all this together, replace the ssh line in the function with:

ssh -o ExitOnForwardFailure=yes -f -N -L port:host:5432 user@$ip -i ~/.ssh/key.pub

that way running the function multiple times, won't leave (multiple-1) useless ssh running.

It's also possible to replace -N with a short remote sleep command. That way there won't be a long lived idle ssh command that has to be searched if it has to be killed. Ssh will still wait the end of tunnel usage before ending, so the short delay is not an issue:

ssh -o ExitOnForwardFailure=yes -f -L port:host:5432 user@$ip -i ~/.ssh/key.pub sleep 15
9
  • Great answer.. but can you please explain the || : part or is it a typo?
    – abbood
    Commented Apr 14, 2018 at 11:35
  • If you set the shell -e option somewhere, a failure would cancel everything. : is no-op (a shorter true command). so using a || : construct just locally disables -e. ssh is expected to fail on second invocation, because the former ssh is still running (it forked like a daemon and won't die). anyway it's possibly useless to put it, perhaps -e is already disabled in a function. it's also possible to replace -N with a short sleep command: ssh would still wait the end of the tunnel usage before exiting
    – A.B
    Commented Apr 14, 2018 at 11:40
  • What happens if the first invocation fails? Ie my tunnel credentials are invalid? Would that immediately abort everything?
    – abbood
    Commented Apr 14, 2018 at 11:43
  • pgsql would complain like before "Connection refused"
    – A.B
    Commented Apr 14, 2018 at 11:44
  • @abbood added an alternate option
    – A.B
    Commented Apr 14, 2018 at 12:04
0

This solution by @A.B can also be used to create a "jump server" between dmz (internet or edge endpoints) and mz (backend) with ssh port forward tunnel to backend hosts, for example:

(all respective port have to be opened using firewall-cmd)

desthost_ip is the target ip for the service being access, it should have responding process bound to it, ie, tomcat, webapp, so foth.

On the dmz edge:

ssh -o ExitOnForwardFailure=yes -f -L localhost_ip:port:desthost_ip:port user@dest_ip -i xyz.key sleep 15

So, if localhost also has a public ip; assuming desthost has https based service running, then something like this can work:

https://public_ip_of_localhost:port

This should bring up desthost_ip:port service webpage.

ssh -o ExitOnForwardFailure=yes -f -L 192.168.1.199:8001:192.168.2.10:8081 [email protected] -i xyz.key sleep 15

And

https://192.168.1.199:8001 

or

https://public_ip_for_192.168.1.199:8001

will bring up service page on 192.168.2.10:8081.

You must log in to answer this question.

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