Ruby Method to Run Shell Command on Private Host Through Bastion Host Over SSH Tunnel with Real-Time Console Output

From Bonus Bits
Jump to: navigation, search

Purpose

This article gives the steps to use Ruby Net SSH Gateway library to run a shell command on a private host through a public/accessible host such as a bastion host using SSH Tunneling with real-time console output. Also, some example Ruby methods for fetching the bastion and instance IP from Terraform outputs.


Prerequisites

  • Ruby 2.6.5+


Ruby Method for Fetching Bastion EIP IP Address from Terraform State

Reference: Ruby Method for Fetching a Bastion EIP IP Address from Terraform State File


Ruby Method for Fetching Instance IP from Terraform State

Reference: Ruby Method for Fetching Private Instance IP Address from Terraform State


Ruby Method

def ssh_command_tunnel(host, user, ssh_key, command)
  require 'net/ssh/gateway'

  key_path = "#{@project_vars['secrets_path']}/#{@project_vars['tf_workspace']}"

  bastion_ip = fetch_bastion_ip
  bastion_ssh_key = "#{key_path}/bastion"
  bastion_user = 'ubuntu'

  private_instance_ip = fetch_instance_ip(host)
  private_ssh_key = "#{key_path}/#{ssh_key}"

  retries = 0
  code = nil

  gateway = Net::SSH::Gateway.new(bastion_ip, bastion_user,
                                  forward_agent: true,
                                  keys: [bastion_ssh_key],
                                  host_key: 'ssh-rsa',
                                  port: 22,
                                  verify_host_key: :never)

  gateway.open(private_instance_ip, 22) do |_gateway_port|
    gateway.ssh(private_instance_ip, user,
                keys: private_ssh_key,
                host_key: 'ssh-rsa',
                port: 22,
                verify_host_key: :never) do |ssh|
      the_channel = ssh.open_channel do |channel|
        channel.exec command do |ch, success|
          raise 'could not execute command' unless success

          ch.on_data { |_c, data| print data }
          ch.on_extended_data { |_c, _type, data| print data }
          ch.on_request('exit-status') { |_ch, data| code = data.read_long }
        end
      end
      the_channel.wait
    end
  end
  gateway.shutdown!
  return "ERROR: Command (#{command}) Returned Code (#{code})!!" if code != 0
rescue Net::SSH::ConnectionTimeout
  puts 'Net::SSH::ConnectionTimeout'
  retry if (retries += 1) < 3
end


Usage Examples

ssh_command_tunnel('10.10.10.100', 'ubuntu', '/home/ubuntu/.ssh/id_rsa', 'tail -f /var/log/cloud-init-output.log')


Related Articles