Assume you have two hosts:
- A target host
- A control host (backup server in case of ccollect, configuration server in case of cdist)
Assume further that the target host can directly reach the control host, but the control host cannot connect to the target host directly.
For instance, it's the case, when the target host is hidden by NAT or protected by a firewall.
Create a tunnel from the target host to the control host
A very simple solution is to create a static tunnel from the target host to the control host, which allows the control host to connect back:
targethost% ssh -R 42523:localhost:22 controlhost controlhost% ssh -p 42523 localhost
The drawback is that the remote port needs to be defined beforehand and both sides needs to know about it.
This is especially nasty, if you have a lot of target hosts that need to be backed up / configured.
Use dynamic port allocation
The OpenSSH developers seem to have spotted this problem and include an option to use a random free port: If port 0 is chosen as the remote forwarding port, the port is dynamically chosen by the ssh server, which in our case runs on the controlhost.
Even better, the port information is also displayed on stdout:
targethost% ssh -R 0:localhost:22 controlhost Allocated port 59818 for remote forward to localhost:22
The problem here is: The shell on the remote side does not know which port was chosen, as it is only printed on stdout by the ssh client.
Expose remote forwarding ports
This patch against OpenSSH 6.2p1 creates a new environment variable SSH_REMOTE_FORWARDING_PORTS, which contains all ports that are used for remote forwarding:
targethost % ssh -R 1234:localhost:22 controlhost controlhost % echo $SSH_REMOTE_FORWARDING_PORTS 1234
As this works for all remotely forwarded ports, this can also be used for dynamic port assignments:
targethost % ssh -R 0:localhost:22 controlhost controlhost % echo $SSH_REMOTE_FORWARDING_PORTS 54294
If more than one port forwarding definition is given, they are listed space separated:
targethost % ssh -R 0:localhost:22 -R 1234:localhost:22 controlhost controlhost % echo $SSH_REMOTE_FORWARDING_PORTS 59056 1234
Adapted from a proposal of Philipp Marek.
A different approach is using socat like this:
targethost% socat TCP:localhost:22,retry=forever "EXEC:ssh controlhost" controlhost% cat .ssh/authorized_keys command="~/myscript 1224" ssh-rsa ... controlhost% cat ~/myscript socat - TCP-LISTEN:1234 & ssh -p 1234 ...
The drawback with this solution is to use pre-defined ports as well as socat on the targethost exiting after the first connection has been closed. It works for a single shot callback, though.
Use ProxyCommand with stdin/stdout
As proposed by Darren Tucker (some parts are copied & pasted from his original mail):
# Create fifo/named pipe for sshd targethost% mkfifo sshd_in sshd_out # Start ssh on the controlhost from the targethost # and create a control socket. Use ProxyCommand=- # to make use of stdin/stdout for proxying packets through. targethost$ ssh <sshd_in >sshd_out -T -y controlhost "ssh -y -N -T -MS/tmp/ctl -oProxyCommand=- targethost" & # Start a new sshd on the client, which listens on the newly # created fifos targethost$ /usr/sbin/sshd -i -f < sshd_in > sshd_out # on the server, use the control socket to talk to the # sshd running on the targethost controlhost% ssh -S /tmp/ctl targethost
Drawback: Quite complicated setup required, thus probably error prone on day-to-day use. Advantage: Very beautiful use of FIFOs, ssh, controlsockets and proxycommand. A setup every geek must love.
The given patch has some known limitations:
- The destination of the remote forwarding is not shown. Debugging the ssh server shows that this information was present in ssh1, but is absent in ssh2.
- The number of listed ports is limited by the buffer size of 256 characters
- Includes only remote port forwardings specified at startup, not the ones added later