bashsshssh-config

SSH Config Dynamic Port with ProxyCommand and BASH Script


I've got a server setup to randomise it's SSH Port after a certain amount of time and publish the port to a .txt file available on it's web server. I have then written a simple script on my client which fetches the new port from the web server .txt file and updates a specific hosts Port number in ~/.ssh/config.

Because ~/.ssh/config cannot parse Bash variables I call the script with ProxyCommand (I am using a JumpHost and the JH Port is the dynamic Port).

My ~/.ssh/config as follows:

Host jumphost
HostName jumphost.example.com
Port 51638
User bob
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null

Host myserver
HostName myserver.com
Port 2222
User bob
ProxyCommand ~/.ssh/get_dynamic_port.sh ssh -W %h:%p jumphost

Bash script as follows (get_dynamic_port.sh):

#!/bin/sh
PORT=$(curl -s http://jumphost.example.com/port.txt)
OLDIP=`grep -w "jumphost.example.com" -A 1 ~/.ssh/config | awk '/Port/ {print $2}'`
LINE_NUMBER=`grep -n "jumphost.example.com" -A 1 ~/.ssh/config | grep -v "jumphost.example.com" | awk '{print $1}' FS="-"`
sed -i'.bak' -e "${LINE_NUMBER}s/$OLDIP/$PORT/" ~/.ssh/config

The script works fine and updates the Port for jumphost.example.com but unfortunately I cannot connect, ssh running in debug output below:

macosx:$ ssh -vvv myserver
OpenSSH_7.9p1, LibreSSL 2.7.3
debug1: Reading configuration data ~/.ssh/config
debug1: ~/.ssh/config line 54: Applying options for myserver
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 48: Applying options for *
debug1: Executing proxy command: exec ~/.ssh/get_dynamic_port.sh ssh -W myserver:2222 jumphost
debug1: identity file ~/.ssh/id_rsa type -1
debug1: identity file ~/.ssh/id_rsa-cert type -1
debug1: identity file ~/.ssh/id_dsa type -1
debug1: identity file ~/.ssh/id_dsa-cert type -1
debug1: identity file ~/.ssh/id_ecdsa type -1
debug1: identity file ~/.ssh/id_ecdsa-cert type -1
debug1: identity file ~/.ssh/id_ed25519 type -1
debug1: identity file ~/.ssh/id_ed25519-cert type -1
debug1: identity file ~/.ssh/id_xmss type -1
debug1: identity file ~/.ssh/id_xmss-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_7.9
ssh_exchange_identification: Connection closed by remote host

It should be noted that tailing the secure log file on the jumphost server doesn't show any connection attempts which might be a sign as to what is wrong.

The jumphost config works fine without the dynamic port script, and as stated above the script is actually changing the port of the jumphost correctly but then ssh just fails after that.

Any ideas on how to achieve this or what I could be doing wrong would be appreciated. I could just use a crontab entry to run the script every so often to update the jumphost port, but I would prefer to only have the jumphost port updated when a connection is being made to it, just seems a bit cleaner that way.

Thanks :)


Solution

  • The modern way to use "jumphosts" is with the -J option (ProxyJump).

    Using the ProxyCommand option still works and has the flexibility to run arbitrary setup code by calling a script, as here. However, your code must, ultimately, run an appropriate ssh command to perform the "jump".

    The typical config option looks like:

    Host jump
        Hostname jumphost.fqdn
        User juser
    
    Host final
        Hostname final.fqdn
        User fuser
        ProxyCommand ssh -W %h:%p jump
    

    You run ssh final which opens a connection from localhost to jump, then another from jump to final with the necessary forwarding enabled.

    In your configuration, you have replaced ProxyCommand with a shell script that performs some setup. You still need to run something like the normal ssh command afterwards.

    Given a config line like your:

    ProxyCommand ~/.ssh/get_dynamic_port.sh ssh -W %h:%p jumphost
    

    the simplest way to invoke the normal ssh command (which you are passing as arguments to the shell script), is to invoke it at the end:

    #!/bin/sh
    
    # ... custom stuff ...
    
    # new final line:
    eval "$@"