5

I'd like to prepare a ssh connection for an external admin to a centos-machine.

Following setup:

  • HostA: Server with static IP-Adress
  • HostB: Local-CentOS-Machine
  • HostC: External-Admin-System

Connection Layout: HostC > HostA > (Router) > HostB

I've tried the follwing commands at HostB:

ssh -R Port:user@HostB:22 user@HostA

ssh user@HostB -p Port # --> Timeout here

I dont want to touch the router, i guess it's not strictly necessary. The ssh-connection into HostA (linode-server) works fine. Netstat show that the PORT is listening. But the second command gets timed out. What could be the reason?

How to connect from HostC, would the following command work?

ssh -t HostA 'ssh HostB -p Port'

Other Conditions:

  • I've already set GatewayPorts yes at HostA.
  • HostA just allows connections via key file.
  • HostA already knows the publickeys from HostB and HostC.
  • HostB uses ssh default configurations
  • Ufw firewalls on all machines (port 22 and 10022 is open)

Is there any easier way for a save connection, without worrying about the router?

1 Answers1

4

There are at least two solutions with ssh -R, you seem to mix their parts. There's little more what is wrong.


Listening on HostA, the port open to the outside

It's almost a duplicate of this question: Accessing localhost web server via reverse SSH tunnel and URL. I believe the main problem here is the same, my answer to the other question applies but it's not enough.

Your command was like:

# on HostB
ssh -R 10022:user@HostB:22 user@HostA

It should rather be like:

# on HostB
ssh -R :10022:localhost:22 user@HostA

What was fixed?

  1. The : before 10022, with nothing in front, defines an empty bind_address. Without it the SSH server on HostA would bind to the loopback interface only, i.e. localhost:10022 on HostA, not accessible from the outside. An empty bind_address means "all interfaces". See my answer to the linked question for details.

  2. The target of remote forwarding is resolved on the local computer, in your case on HostB. To reach the SSH server on HostB from HostB itself, most likely localhost is enough. This is because usually SSH servers do listen on their loopback interfaces. Another address that points to HostB may work as well, but localhost or 127.0.0.1 is the most KISS-able one in such case.

  3. When specifying the target, you mustn't specify any user. I mean HostB as a target might work, but not user@HostB. In your case the latter seemed to work because the address is resolved only when someone actually uses the tunnel. You have never managed to use the tunnel. If you did, the ssh process (the one with -R, on HostB) would yield:

    connect_to user@HostB: unknown host (Name or service not known)
    

    At lest this is what OpenSSH in my Debian 10 says.

Notes:

  • Consider -N or -N -f options (see man 1 ssh); or autossh.
  • GatewayPorts yes in the SSH server config on HostA is the Right Thing.
  • A sane firewall on HostB should not block connections to the loopback interface, no configuration needed. It's irrelevant if TCP port 22 is open to the outside world.
  • The firewall on HostA should open TCP port 10022 (and of course 22 or whatever port the SSH server listens on).

Then from the Internet you connect like this:

# on HostC or wherever
ssh -p 10022 userB@HostA

Note the command for HostC does not specify HostB in any way, but userB should be valid on HostB and one should use credentials for userB@hostB. If one wants to use key-based authentication, HostC should hold the key; keys on HostA don't matter. It's like SSH server from HostB is listening on HostA:10022. The address belongs to HostA, but one is reaching the SSH server running on HostB. The tunnel itself is transparent. I mean you can forget the tunnel exists, treat HostA:10022 as the address of HostB and simply use ssh syntax. E.g. the following command:

# on HostC or wherever
ssh -p 10022 userB@HostA hostname

will (try to) run hostname on HostB.


Listening on HostA, the port available from HostA

If you do:

# on HostB
ssh -R :10022:localhost:22 user@HostA

then the SSH server on HostA will listen on all interfaces, including the loopback interface of HostA. If you do something more similar to what you tried:

# on HostB
ssh -R 10022:localhost:22 user@HostA

(note there's not : before 10022) then the server will listen on the loopback interface only. In any case you should be able to connect to HostB from HostA like this:

# on HostA
ssh -p 10022 userB@localhost

where userB is valid on HostB. This means one can do this:

# on HostC or wherever
ssh user@HostA    # or any other userA valid on HostA
# now we're on HostA
ssh -p 10022 userB@localhost

Or in a single line:

# on HostC or wherever
ssh -t userA@HostA 'ssh -p 10022 userB@localhost'

Differences with respect to the first method:

  • In some cases one needs to use -t explicitly.
  • A sane firewall on HostA should not block connections to the loopback interface, no configuration needed. It's irrelevant if TCP port 10022 is open to the outside world. This means the method may work even if you're not an admin on HostA. Additionally …
  • GatewayPorts (another thing a non-admin cannot adjust) does not necessarily need to be yes.
  • The connection to HostB is from HostA. If one wants to use key-based authentication then HostA should hold the key. There's a concept of forwarding the authentication agent connection, so HostC could hold the key. Alternatively it's possible to create a tunnel from HostC to HostB, so later HostC can reach the SSH server of HostB via two chained tunnels and use its own key. I won't elaborate.
  • One must have SSH access to HostA. If it's not you who is going to connect from HostC, then you may want to avoid this. Strongly prefer the first method then.