SSH Tips and Tricks

Posted on

Throughout my career I’ve always been the one that knew more that your typical ssh command. Colleges would often ping me if they ever needed to do anything “advanced” with ssh. Recently I found myself in the depths of man ssh and scripting my fair share of ssh shell scripts. So I thought I’d dump some of that knowledge in a post.

The Config File

If you ever find yourself making an alias like alias foo=ssh you@192.168.1.1 -o StrictHostKeyChecking=no then your doing it wrong. ssh allows you to predefine settings on a per host basis in the ~/.ssh/config file. For example, the alias before can be defined as the following:

Host foo
    HostName 192.168.1.1
    User you
    StrictHostKeyChecking no

With that configured, you can now just type ssh foo and you should be sshing with the settings above.

Running a Remote Command

What if you just want to run one command on the remote host? Well you can actually pass the remote command to the end of the ssh command, for example, the command ssh you@192.168.1.1 id will ssh to the host at 192.168.1.1 and after successful authentication, execute the id command. If the remote command requires arguments, I’ve found that the -t option works best for this.

This is also useful when you only want the output of say a remote log file, and you don’t need a full shell on the remote host.

Forking

Have you ever wanted to have all the benefits of ssh but without a shell ? Suppose you just want to access a remote service (more on that next). Or how about you want to tunnel a service through to another host and don’t need the shell on the proxy. Well you can do this with the -f option. However, your’ll likely need the -N option also. The -f option requests ssh to go to background just before command execution, and the -N option says to not execute a remote command.

For example, the following command can be used to tunnel port 2222 over localhost’s port 22, meaning after this command you can now run ssh -p 2222 username@localhost to ssh to a service behind a different host. You wont need the initial shell still open, and if you fork it, then its less prone to you accidentally closing it.

ssh -Nf -R2222:localhost:22 username@ip

The only caveat with forking a ssh session, is if you want to close it, you have to kill the process. Thats not overly annoying, but its worth mentioning.

Port Forwarding

Sometimes access to a service on a remote system is only available to the localhost of the system. But this should not stop you accessing it from your host.

ssh supports a local and remote port forward, ssh actually allows you to proxy through systems to access remote services. In the following example the remote system only allows access to the database from localhost but you can connect via ssh and then connect to the database from your workstation.

ssh -L 3307:remotehost:3306 you@remotehost

The -L option tells ssh to forward the local port 3307 to the remote servers port 3307 allowing you to now connect from your local system.

mysql -u user -p -P 3307 -h 127.0.0.1

Proxy Jump

What if your service is located behind a jump host? You could use the above trick, but that could become a bit hard to manage. Welcome the -J option. The -J option allows you to connect to services via a jump host. For example, the command ssh you@192.168.1.1 -J you@jumphost:1022, will first ssh to you@jumphost on port 1022 and then from that host it’ll ssh to you@192.168.1.1.

You can also do this with scp too, with the -o ProxyJump=you@jumphost:10222 option.

SSH_ORIGINAL_COMMAND

The SSH_ORIGINAL_COMMAND is a variable that contains the command that is executed. Meaning, if the client ran ssh you@host -t id then the SSH_ORIGINAL_COMMAND variable on the remote host will contain id as well as a few other defaults. This variable is particularly useful to verify that the client isn’t trying to do anything they should not be doing. For example, the following bash script, checks that the SSH_ORIGINAL_COMMAND matches against the REGEX variable, and if it does, its allowed to continue.

# regex to check the incoming SSH command
# assumes an ssh-ed25519 key
# checks for the 'echo' command
# checks for the authorized_keys file options 'command="exit",restrict,port-forwarding'
# checks for an ssh-ed25519 key with a comment format of $portnum_$hostname_$day-$monthabbrev-$year_$hour:$minute
REGEX="^echo command=\"exit\",restrict,port-forwarding ssh-ed25519 [a-zA-Z0-9\+\/]{68} [0-9]{5}_[0-9a-zA-Z\_\-]{1,15}_[0-9][0-9]-[ADFJMNOS][aceopu][bcglnprtvy]-[0-9][0-9][0-9][0-9]_[0-9][0-9]:[0-9][0-9]$"

# check the original command against the regex
if [[ $SSH_ORIGINAL_COMMAND =~ $REGEX ]]
then
    # the echo command matches the regex, add the key and options to the authorized_keys file
    $SSH_ORIGINAL_COMMAND | tee -a ~/.ssh/authorized_keys
fi

The Client Menu

Ever ran cat on a binary file? Or your session has timed out and you did not use a mosh session, so the shell is just “hanging”? Or did you forget to setup that port forward? Well ssh has a client menu, whenever your in a ssh session hit return (to clear the buffer) and then ~? to bring up the ssh client menu.

​ me@remotehost:~$
 me@remotehost:~$ ~?
Supported escape sequences:
 ~.   - terminate connection (and any multiplexed sessions)
 ~B   - send a BREAK to the remote system
 ~C   - open a command line
 ~R   - request rekey
 ~V/v - decrease/increase verbosity (LogLevel)
 ~^Z  - suspend ssh
 ~#   - list forwarded connections
 ~&   - background ssh (when waiting for connections to terminate)
 ~?   - this message
 ~~   - send the escape character by typing it twice
 (Note that escapes are only recognized immediately after newline).

Now when your session has timed out or you’ve accidentally ran cat on a binary, you can hit return then ~. to terminate that ssh connection. Or if you’ve forgotten to open up a port forward you can hit return then ~C and add the relevant options to setup your port forward.

Some Examples

Connect Script

Recently I needed to tunnel ssh from a VM back to myself, so I used the following bash script to first test that the VM could connect to me on port 22 and if it could, to then ssh to me, and tunnel port 2222 to its port 22. Meaning that if this script was successful, I’d be able to run the command ssh -p 2222 user@localhost and be presented a shell on the VM.

#!/bin/bash

HOSTIP=$1

if [ -z $HOSTIP ]; then
    echo -e "\$1 not set"
    exit
fi

timeout 5 bash -c "</dev/tcp/$HOSTIP/22"

if [ $? -eq 0 ]; then
    ssh -fN -R 2222:localhost:22 me@$1
else
    echo -e "!!! cant connect to $HOSTIP on port 22 !!!"
fi

Temporary Aliases

I work with VM’s a lot, and often need to ssh to them. But I also delete them to save on my hard drive space. So sometimes VM’s will be re-allocated the same IP from the DHCP server. This is fine from a networking point of view, but when I go to ssh to the VM, I’m greeted with an error stating that the host key conflicts. Or its already in the ~/.ssh/know_hosts file. Its easy enough to fix, you remove the entry in the know_hosts file, and then attempt to ssh again. But but but, this could be so much nicer.

If I know that the VM is temporary, and wont be staying around, do I really need to save its host ksy in the know_hosts file ? Thats a question for the reader, but for me, I don’t. So I have the following aliases set.

alias tssh="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
alias tscp="scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"