Multiple SSH proxying in Drush and Capistrano

Estimated time reading
2 m 11 sec

TL;DR in this article I show how to use SSH's ProxyCommand option in Drush's aliases and in Capistrano's host options in order to easily access hosts that are NAT-ed (multiple times).

What are Drush and Capistrano?

At Twinbit we use a customized version of Capistrano (V2) in order to deploy our projects. Furthermore, when the project is based on Drupal we also leverage the power of Drush. Drush is a very useful commandline tool that can operate on different Drupal installations.

The operations that Drush can do are many and varied, and 90% of the time those operations are run at development time against your local Drupal installation, but sometimes it's useful to easily leverage Drush's power on another (remote) installation, be it another dev environment, staging or production.

Drush lets you do this by using the concept of aliases. Simply stated, drush aliases are collection of options for a (possibly remote) Drupal installation; some of these options let you specify how exactly the host should be reached using SSH.

You can get all the information you need regarding aliases running drush aliases docs-aliases; from there you can see that drush is very customizable, to the point that each option that can be specified on the commandline can be used in an drush alias.

Furthermore drush aliases can be defined at the system level (under /etc/), at the user level (under ~/.drush/) and at the project level (under sites/all/drush/ from the main drupal directory) with a complex naming standard that can possibly let you group aliases together. From my experience it's very usefult to define aliases at the project level so that the entire team can benefit from it.

What problem are we solving?

We had an issue recently with one of our clients having to do with access to machines for deploy purposes.

More precisely we had to be able to access both the staging and production environment, but for security reasons the access was restricted: staging could only be reached via one of our cloud based machines (let's call it bastion from now on) and production could only be reached from staging.

+-------------------------------+ | | | local development environment | | | +---------------+---------------+ | v +---------+ | | | bastion | | | +----+----+ | v +-----------+ | | | staging | | | +-----+-----+ | v +------------+ | | | production | | | +------------+

The Solution

In this scenario SSH's ProxyCommand option by itself is enough to reach staging, but it never occured to me how to use it to reach production, i.e. when there is one extra host in the middle of the chain.
Drush actually forks a simple ssh process so it's easier to show it first:

php <?php $aliases['stage'] = array( 'remote-host' => 'stage_hostname_or_ip', 'remote-user' => 'your_ssh_username_stage', 'ssh-options' => '-o "ProxyCommand ssh -p 9848 [email protected]_hostname_or_ip nc %h %p"', ); $aliases['prod1'] = array( 'remote-host' => 'prod1_hostname_or_ip', 'remote-user' => 'your_ssh_username_prod', 'ssh-options' => '-o "ProxyCommand ssh -p 9848 [email protected]_hostname_or_ip nc %h %p" -o ForwardAgent=yes -tt [email protected]_hostname_or_ip ssh -o ForwardAgent=yes -tt', ); $aliases['prod2'] = $aliases['prod1']; $aliases['prod2']['remote-host'] = 'prod2_hostname_or_ip';

I obviously changed ports, usernames, and hostnames/IPs. It's also worth mentioning that a real Drush alias will contain other Drupal specific options like uri and root.

php 'uri' => '', 'root' => '/path/on/filesystem/to/drupal/root',

As you can see our setup was actually more involved in that production was behind a load balancer: so there are actually 2 hosts, and using the setup shown above one can actually run something like drush @prod1 ssh or drush @prod2 memcache-storage-clear-cache on a specific host.

For Capistrano (again, V2) the easiest solution I found was to use the gateway option

```ruby set :gateway, 'stage_with_proxycommand'

Define an entry in ~/.ssh/config with the following:

Host stage_with_proxycommand

HostName stage_hostname_or_ip

User your_ssh_username_stage

ProxyCommand ssh -p 9848 bastion_hostname_or_ip nc %h %p

the following is handy but not required

ssh_options[:forward_agent] = true ```

This is because even if capistrano's dependency gem net-ssh is a pure-Ruby implementation of the SSH2 client protocol, the SSH.connection_strategy method actually reads your ~/.ssh/config.

Using these configurations now it's possible to deploy or execute drush against staging or production from your local development environment, since the proxying through the bastion and, if required, through staging, is performed transparently behind the scenes.

Latest posts from Giuseppe Rota

Keep in touch

Enter your email address below to receive all our latest articles and announcements via email.