Search code examples
rubysshcapistranofirewallssh-tunnel

Capistrano, Firewalls and Tunnel


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

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 [email protected]
    # 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: