ssh tunnel bind to interface

My networking knowledge is always spotty, never have done much network administration; a lot is done by trial and error. And restricting my ssh tunnel isn't working right for some reason. Let me explain:

I have a home network, with a home server, let's call it home.example.com. My internet connection is ... too complicated to explain, it goes through several NAT layers to get to the outside world. And does not have a static address on the outside. But I have several server ports at home that I need to be able to access from the big world. I have a cloud-hosted external FreeBSD server, which is accessible on the public internet. Let's call it external.example.com. So to be able to access my home server via ssh, I first set its sshd up to listen on port 2222, and then I run the following ssh command at home:
ssh -N -R *:44444:localhost:2222 user@external.example.com

This works good (obviously, with a few extra -o options to make the connection more reliable), and now if I go do "ssh -p 44444 user@external.example.com", that is forwarded to port 2222 on my home machine, and I get into the ssh server at home and am logged in. Great. If I look on the external server with netstat, I see the following (my external machine's network address is 10.99.88.77, and the dynamic external IP address of my home server is 123.45.67.89):
Code:
netstat -n4a
Active Internet connections (including servers)
Proto Recv-Q Send-Q Local Address          Foreign Address        (state)
tcp4       0    440 10.99.88.77.22         123.45.67.89.17638      ESTABLISHED
tcp4       0      0 *.44444                *.*                    LISTEN
...

So far, so good. But for security reasons, I want to restrict this further: I want the tunneled port to only be visible on the loopback port of the external machine, so it doesn't expose it on its external interface. That would mean that I would have to "double hop", first ssh to the external machine itself, get a shell, and then do a second ssh to the home server, but I can live with that. Looking at the ssh documentation, it says that the -R option can take a bind address, so I try this:
ssh -N -R 127.0.0.1:44444: localhost:2222 user@external.example.com

But it doesn't do anything: The port 44444 on the external machine is still accepting connections from everywhere, and I get exactly the same netstat output. And yes, before you even ask: The option GatewayPorts in /etc/ssh/sshd.config is set to yes. There are no messages when starting the ssh tunnel, and no warnings or errors in any system log (auth.log or messages or any others).

What am I doing wrong? Do I not understand what the discussion of "bind address" in the man page for ssh means?
 
Doing it without any address (-R 44444:localhost:2222) should only bind to loopback on the remote side. You can further block that incoming port (firewall) on your external interface of your cloud device.
 
And yes, before you even ask: The option GatewayPorts in /etc/ssh/sshd.config is set to yes.
You don’t want this if you want it to bind to localhost. This is only if you want to allow binding to *.

If you have other forwardings where you want that (externally reachable) behavior, then obviously keep it yes.
 
Doing it without any address (-R 44444:localhost:2222) should only bind to loopback on the remote side. You can further block that incoming port (firewall) on your external interface of your cloud device.
Thank you, will try that in an hour or two when I'm back home.

I was thinking about adding a some pf anyway, but I was hoping that just configuring the ssh tunnel would be an easier way to restrict access. Complex pf configuration have a habit of biting me in the behind.
 
I am still not clear on what exactly you want (too many words ;-) but I used to use this sort of thing:

Code:
ssh -p 12345 -L2222:privatehost1:22 -L17010:privatehost2:17010 -L5901:privatehost3:5901 -N -f gatewayhost

Here local ports (only on the machine that does ssh) 2222, 17010 5901 connect to three internal hosts (behind firewall & NAT - not visible on public net) for various services. The only external port being listened to on "gatewayhost" is 12345 (or whatever you use).
 
It feels like a VPN might be a more appropriate tool here. Tailscale literally gives an ssh connection without much complication, even behind various NAT.

But regardless, if you're going to use SSH do yourself a favor and use certificates.
 
I think I'm doing exactly what you're trying to do. I use RemoteForward in an ssh_config(5) file and autossh(1) like gpw928 suggests.

On host internal.example.com, I have a /usr/home/me/.ssh/config like this:
Code:
Host external
  Hostname external.example.com
  IdentityFile ~/.ssh/nopw20231022
  RemoteForward 127.0.0.1:2202 127.0.0.1:22
  RemoteForward 127.0.0.1:8993 internalmail:993
  RemoteForward 127.0.0.1:2525 internalmail:25
  LocalForward 2526 real.external.ip:25

And I have an /etc/rc.local that looks somewhat like this:
Code:
export AUTOSSH_POLL=120
export AUTOSSH_GATETIME=0
su me -c 'autossh -M 6106 -fNg me@external'

This does four things:
  1. I can ssh to external.example.com and do ssh -p 2202 localhost and that connects me to internal.example.com.
  2. My internal IMAP server appears at port 8993 on localhost at external.example.com. I configure Roundcube on that port.
  3. My internal mail exchanger appears at port 2525 on localhost at external.example.com. I configure a mail/postfix transport map on that. (A single line like this will serve a simple single-domain transport: example.com smtp:[127.0.0.1]:2525).
  4. My external mail exchanger appears at port 2526 on localhost at internal.example.com. I configure Postfix's relayhost on that.
 
Solution found, it was much easier than I expected, but in a well-hidden place. The problem was in the sshd configuration on the external host: It had "GatewayPorts=yes" set. That means that ALL remote tunnels (created with a -R flag on an incoming connection) will bind to all interfaces. The correct setting for that variable is clientspecified, which means the bind address on the client's -R flag is actually honored.

I agree that certificates are nice, and I'm going to look at autossh, but for now my tunnels are working fine. Instead of autossh, I use a much simpler system: I have the ssh session configured with various ServerAlive... flags so it fails quickly if the network is disrupted, and then I start it from the daemon program. The whole thing is in a service in /usr/local/etc/rc.d.

Thank you for all the help!
 
Back
Top