Question

We're using Capistrano to automate pushing new versions of a PHP application to a production server. The production server (we'll call it production) is public, while our repository server (we'll call it repo) sits behind our corporate firewall, along with our own machines.

Capistrano, as configured by default, won't work, as production can't talk to repo.

I was wondering if there was someway I could setup capistrano to SSH to repo first, then SSH to production opening a tunnel on a port that I can then use to SSH from production back to repo to pull the changes from SCM.

I just can't figure out how to set this up or figure out a better solution. Ideas?

Edit:

I've tried this:

role :web, "deploy.com"

namespace :deploy do
    task :remote_tunnel do
        run 'Creating SSH tunnel...' do |channel, stream, data|
            ssh = channel.connection
            ssh.forward.remote(22, 'server.com', 10000, '127.0.0.1')
            ssh.loop {!ssh.forward.active_remotes.include?([10000, '127.0.0.1'])}
        end
    end
end

before "deploy:update_code", "deploy:remote_tunnel"

But I keep getting this error:

failed: "sh -c 'Creating SSH tunnel...'" on deploy.com
Was it helpful?

Solution

Here's are 2 ways to accomplish it.

1st way

not sure if you've seen this thread?

It makes use of the net-ssh-gateway library, but creates copies of the local forwarding methods but they're geared for remote access.

class Net::SSH::Gateway 
  # Opens a SSH tunnel from a port on a remote host to a given host and port 
  # on the local side 
  # (equivalent to openssh -R parameter) 
  def open_remote(port, host, remote_port, remote_host = "127.0.0.1") 
    ensure_open! 

    @session_mutex.synchronize do 
      @session.forward.remote(port, host, remote_port, remote_host) 
    end 

    if block_given? 
      begin 
        yield [remote_port, remote_host] 
      ensure 
        close_remote(remote_port, remote_host) 
      end 
    else 
      return [remote_port, remote_host] 
    end 
  rescue Errno::EADDRINUSE 
    retry 
  end 


  # Cancels port-forwarding over an open port that was previously opened via 
  # open_remote. 
  def close_remote(port, host = "127.0.0.1") 
    ensure_open! 

    @session_mutex.synchronize do 
      @session.forward.cancel_remote(port, host) 
    end 
  end 
end

2nd way

Outlined in an answer to this SO question:

This technique is very similar to the 1st way. First you need to create 2 paths to the repository:

# deploy.rb
set :local_repository, "ssh://git@serverbehindfirewall/path/to/project.git"
set :repository,  "ssh://git@localhost:9000/path/to/project.git"

Then before you deploy you'll need to setup the remote forward:

% ssh -R 9000:serverbehindfirewall:22 deploybot@deployserver.com
# CTRL + C + A (Screen) or ⌘ + T (Terminal.app) to open new tab

Followed by your deploy:

% cap HOSTFILTER=deployserver.com deploy # HOSTFILTER reduces set to specified host. Only useful if you have multiple servers.

See this answer to that SO question for more details:

OTHER TIPS

Using Capistrano 3.x, the following works for me:

namespace :deploy do
  desc "Open SSH Tunnel to GitLab"
  task :open_tunnel do
    on roles(:app) do
      info "Opening SSH Remote Tunnel..."
      self.send(:with_ssh) do |ssh|
        # ssh -R 9000:192.168.1.123:22
        ssh.forward.remote(22, "192.168.1.123", 9000)
      end
    end
  end
  before "deploy:check", "deploy:open_tunnel"
end

Please note that ssh.forward.remote expects parameters in a different order than ssh -R, the above is equivalent to ssh -R 9000:192.168.1.123:22

This task calls a private method, if anyone knows an official way to get the access Capistrano's ssh connection, please comment or edit.

Edit: Also see the section Tunneling and other related SSH themes of SSHKit's README

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top