summaryrefslogtreecommitdiff
path: root/test/integration/test_proxy.rb
blob: 6572c429f1c96dab20a4774c42e8336239f55830 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
require_relative 'common'
require 'net/ssh/buffer'
require 'net/ssh'
require 'timeout'
require 'tempfile'
require 'fileutils'
require 'net/ssh/proxy/command'
require 'net/ssh/proxy/jump'

class TestProxy < NetSSHTest
  include IntegrationTestHelpers

  def localhost
    'localhost'
  end

  def user
    'net_ssh_1'
  end

  def ssh_start_params(options)
    [localhost, user, { keys: @key_id_rsa }.merge(options)]
  end

  def setup_ssh_env(&block)
    tmpdir do |dir|
      @key_id_rsa = "#{dir}/id_rsa"
      sh "rm -rf #{@key_id_rsa} #{@key_id_rsa}.pub"
      sh "ssh-keygen -q -f #{@key_id_rsa} -t rsa -N ''"
      set_authorized_key(user, "#{@key_id_rsa}.pub")
      yield
    end
  end

  def setup_gateway(&block)
    gwhost = "gateway.netssh"
    gwuser = 'net_ssh_2'
    tmpdir do |dir|
      @gwkey_id_rsa = "#{dir}/id_rsa"
      sh "rm -rf #{@gwkey_id_rsa} #{@gwkey_id_rsa}.pub"
      sh "ssh-keygen -q -f #{@gwkey_id_rsa} -t rsa -N ''"
      set_authorized_key(gwuser, "#{@gwkey_id_rsa}.pub")
      config = "Host #{gwhost}
                  IdentityFile #{@gwkey_id_rsa}
                  StrictHostKeyChecking no
               "
      FileUtils.mkdir_p File.expand_path("~/.ssh")
      my_config = File.expand_path("~/.ssh/config")
      File.open(my_config, 'w') { |file| file.write(config) }
      begin
        FileUtils.chmod(0o600, my_config)
        yield gwuser, gwhost
      ensure
        FileUtils.rm(my_config)
      end
    end
  end

  def test_smoke
    setup_ssh_env do
      proxy = Net::SSH::Proxy::Command.new("/bin/nc localhost 22")
      msg = 'echo123'
      ret = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh|
        ssh.exec! "echo \"$USER:#{msg}\""
      end
      assert_equal "net_ssh_1:#{msg}\n", ret
    end
  end

  def with_spurious_write_wakeup_emulate(rate = 99, &block)
    orig_io_select = IO.method(:select)
    count = 0

    IO.singleton_class.send(:define_method, :select) do |*params|
      count += 1
      if (count % rate != 0)
        if params && params[1] && !params[1].empty?
          return [[], params[1], []]
        end
      end
      orig_io_select.call(*params)
    end

    begin
      yield
    ensure
      IO.singleton_class.send(:define_method, :select, &orig_io_select)
    end
  end

  def test_with_rate_limit_and_spurious_wakeup
    system("sudo sh -c 'echo 4096 > /proc/sys/fs/pipe-max-size'")
    begin
      setup_ssh_env do
        proxy = Net::SSH::Proxy::Command.new("/usr/bin/pv --rate-limit 100k | /bin/nc localhost 22")

        large_msg = 'echo123' * 30000

        ok = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh|
          with_spurious_write_wakeup_emulate do
            ret = ssh.exec! "echo \"$USER:#{large_msg}\""
            assert_match(%r{/bin/.*sh: Argument list too long\n}, ret)
            hello_count = 1000
            ret = ssh.exec! "ruby -e 'puts \"Hello\"*#{hello_count}'"
            assert_equal "Hello" * hello_count + "\n", ret
          end
          :ok
        end

        assert_equal :ok, ok
      end
    ensure
      system("sudo sh -c 'echo 1048576 > /proc/sys/fs/pipe-max-size'")
    end
  end

  def test_proxy_jump_through_localhost
    setup_ssh_env do
      setup_gateway do |gwuser, gwhost|
        proxy = Net::SSH::Proxy::Jump.new("#{gwuser}@#{gwhost}")

        output = Net::SSH.start(*ssh_start_params(proxy: proxy)) do |ssh|
          ssh.exec! "echo \"$USER:echo123\""
        end
        assert_equal "net_ssh_1:echo123\n", output
      end
    end
  end

  class DbgProxy
    attr_reader :io

    def initialize(origin)
      @origin = origin
    end

    def open(*args)
      @io = @origin.open(*args)
      @io
    end
  end

  def test_does_close_proxy_on_proxy_failure
    setup_ssh_env do
      proxy = DbgProxy.new(Net::SSH::Proxy::Command.new('sleep 2 && ssh -W %h:%p -o "PreferredAuthentications none" user@localhost'))
      msg = 'echo123'
      assert_raises Errno::EPIPE, Net::SSH::Proxy::ConnectError do
        Net::SSH.start(*ssh_start_params(proxy: proxy, password: 'bad', non_interactive: true, auth_methods: ['password'], verbose: :debug)) do |ssh|
          ssh.exec! "echo \"$USER:#{msg}\""
        end
      end
      assert proxy.io.nil? || proxy.io.closed?, "Process #proxy.io.pid not closed"
    end
  end
end