Introduction

This article describes a patch to OpenSSH 6.2 that I wrote to enable ssh callback using dynamic ports. This is rather useful to have for various types of software, including cdist and ccollect.

Background

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.

Approaches

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

Use socat

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.

Limitations

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

Future

The patch has been submitted to the openssh-unix-dev mailinglist for discussion.