Vpn-less Persistent Ssh Sessions
For a long time, my daily workflow has been slowed down by having to reopen my SSH connections when transitioning from workplace to home and reciprocally: once network connection drops, every process launched from the starting shell just terminates.
Things started to improve when I realized I could stay inside tmux. Much like GNU screen, tmux is a terminal multiplexer that allows the user to detach and reattach terminal sessions. When network connection is interrupted, everything keeps running inside the multiplexer.
From that point, losing network when leaving the office meant I would have to reconnect to my servers once arrived at home, then reattach to still running tmux sessions before being operational again.
Better. Yet not transparent enough.
The ideal solution should achieve:
- close the laptop lid
- commute
- open the laptop lid; nothing to do, everything is still there
The requirements above mean two things:
- being able to connect to any work server from everywhere, by hostname
- automatically reattach to tmux sessions when network goes up again
Vpn Less SSH Sessions
To reach any work machine from home without a VPN tunnel, you need a bastion host at your workplace. Configure it to accept SSH connections coming from the WAN interface (that is, the internet). For maximum security, only allow public-key authentication.
We will refer to this bastion host as deathstar.somewhereinspace.com
.
Workstations we want to reach are called mustafar
, kamino
, and tatooine
. These don’t need to be visible from outside of the company LAN (that’s the whole point of the article).
The idea is the following: when connecting to one of those workstations, always tunnel everything through the bastion host.
This is achieved with the help of OpenSSH 5.4 ‘netcat mode’ (ssh -W
) in conjunction with a ProxyCommand
directive.
In your ~/.ssh/config
, place the following lines:
Host bastion
HostName deathstar.somewhereinspace.com
Host deathstar.somewhereinspace.com
ProxyCommand none
Compression yes
Host mustafar kamino tatooine
ProxyCommand ssh bastion -W %h:%p
By now, every time you do:
$ ssh kamino
The connection will automatigically be tunneled through the bastion host.
Obviously, the bastion host must also be reachable with its public hostname from inside your company LAN.
You can even make this configuration a bit more generic by adding:
Host *.somewhereinspace.com
ProxyCommand ssh bastion -W `echo %h |sed s/.\somewhereinspace\.com//g`:%p
Persistent Connections
To achieve persistent SSH connections, we will use both OpenSSH 5.6 ControlPersist
directive and autossh.
At the end of your ~/.ssh/config
, place the following lines:
Host *
ControlMaster auto
ControlPersist 1h
ControlPath /tmp/ssh_mux_%h_%p_%r
ServerAliveInterval 30
ServerAliveCountMax 4
ControlMaster
and ControlPersist
directives tell OpenSSH to share multiple sessions over a single master connection. The master connection will remain open 1 hour after the initial client connection has been closed.
ServerAliveInterval
and ServerAliveCountMax
control how OpenSSH decides to disconnect and terminate the session when the remote server does not answer server alive messages.
autossh is the last piece of our puzzle: autossh is a program to start a copy of ssh and monitor it, restarting it as necessary should it die or stop passing traffic.
We use autossh along with tmux to automagically restart closed SSH connections and reattach to tmux sessions.
Place the following shell function in your ~/.bash_profile
or ~/.bashrc
:
function rtmux {
case "$2" in
"") autossh -M 0 $1 -t "if tmux -qu has; then tmux -qu attach; else EDITOR=vim tmux -qu new; fi";;
*) autossh -M 0 $1 -t "if tmux -qu has -t $2; then tmux -qu attach -t $2; else EDITOR=vim tmux -qu new -s $2; fi";;
esac
}
We disable autossh monitoring with -M 0
because ServerAliveInterval
and ServerAliveCountMax
OpenSSH directives already control when the ssh client exits.
The “EDITOR=vim
” bits are here to instruct tmux we want Vi like key-bindings. You can drop that part.
To persistently reattach to the current tmux session on kamino, we do:
$ rtmux kamino
If we want to use a specific tmux session, we do:
$ rtmux kamino <tmux session name>