net-ssh 3.0.1 → 3.0.2.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -1
- data.tar.gz.sig +0 -0
- data/CHANGES.txt +4 -0
- data/README.rdoc +1 -1
- data/Rakefile +5 -1
- data/lib/net/ssh.rb +5 -2
- data/lib/net/ssh/authentication/key_manager.rb +1 -4
- data/lib/net/ssh/connection/channel.rb +26 -7
- data/lib/net/ssh/connection/session.rb +11 -2
- data/lib/net/ssh/proxy/command.rb +17 -2
- data/lib/net/ssh/version.rb +2 -2
- data/net-ssh-public_cert.pem +15 -15
- data/net-ssh.gemspec +7 -5
- data/test/README.txt +0 -22
- data/test/connection/test_channel.rb +3 -0
- data/test/connection/test_session.rb +14 -5
- data/test/integration/README.txt +2 -4
- data/test/integration/common.rb +6 -0
- data/test/integration/playbook.yml +8 -0
- data/test/integration/test_forward.rb +435 -0
- data/test/integration/test_id_rsa_keys.rb +18 -6
- data/test/integration/test_proxy.rb +93 -0
- data/test/start/test_user_nil.rb +27 -0
- data/test/test_all.rb +1 -1
- metadata +22 -20
- metadata.gz.sig +0 -0
- data/test/manual/test_forward.rb +0 -285
@@ -0,0 +1,435 @@
|
|
1
|
+
# $ ruby -Ilib -Itest -rrubygems test/manual/test_forward.rb
|
2
|
+
|
3
|
+
# Tests for the following patch:
|
4
|
+
#
|
5
|
+
# http://github.com/net-ssh/net-ssh/tree/portfwfix
|
6
|
+
#
|
7
|
+
# It fixes 3 issues, regarding closing forwarded ports:
|
8
|
+
#
|
9
|
+
# 1.) if client closes a forwarded connection, but the server is reading, net-ssh terminates with IOError socket closed.
|
10
|
+
# 2.) if client force closes (RST) a forwarded connection, but server is reading, net-ssh terminates with
|
11
|
+
# 3.) if server closes the sending side, the on_eof is not handled.
|
12
|
+
#
|
13
|
+
# More info:
|
14
|
+
#
|
15
|
+
# http://net-ssh.lighthouseapp.com/projects/36253/tickets/7
|
16
|
+
|
17
|
+
require_relative './common'
|
18
|
+
require 'net/ssh/buffer'
|
19
|
+
require 'net/ssh'
|
20
|
+
require 'timeout'
|
21
|
+
require 'tempfile'
|
22
|
+
|
23
|
+
class TestForward < Test::Unit::TestCase
|
24
|
+
include IntegrationTestHelpers
|
25
|
+
|
26
|
+
def localhost
|
27
|
+
'localhost'
|
28
|
+
end
|
29
|
+
|
30
|
+
def user
|
31
|
+
'net_ssh_1'
|
32
|
+
end
|
33
|
+
|
34
|
+
def ssh_start_params
|
35
|
+
[localhost ,user , {:keys => @key_id_rsa, :verbose => :debug}]
|
36
|
+
end
|
37
|
+
|
38
|
+
def setup_ssh_env(&block)
|
39
|
+
tmpdir do |dir|
|
40
|
+
@key_id_rsa = "#{dir}/id_rsa"
|
41
|
+
sh "rm -rf #{@key_id_rsa} #{@key_id_rsa}.pub"
|
42
|
+
sh "ssh-keygen -f #{@key_id_rsa} -t rsa -N ''"
|
43
|
+
set_authorized_key(user,"#{@key_id_rsa}.pub")
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def start_server_sending_lot_of_data(exceptions)
|
49
|
+
server = TCPServer.open(0)
|
50
|
+
Thread.start do
|
51
|
+
loop do
|
52
|
+
Thread.start(server.accept) do |client|
|
53
|
+
begin
|
54
|
+
10000.times do |i|
|
55
|
+
client.puts "item#{i}"
|
56
|
+
end
|
57
|
+
client.close
|
58
|
+
rescue
|
59
|
+
exceptions << $!
|
60
|
+
raise
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
return server
|
66
|
+
end
|
67
|
+
|
68
|
+
def start_server_closing_soon(exceptions=nil)
|
69
|
+
server = TCPServer.open(0)
|
70
|
+
Thread.start do
|
71
|
+
loop do
|
72
|
+
Thread.start(server.accept) do |client|
|
73
|
+
begin
|
74
|
+
client.recv(1024)
|
75
|
+
client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii"))
|
76
|
+
client.close
|
77
|
+
rescue
|
78
|
+
exceptions << $!
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
return server
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_in_file_no_password
|
88
|
+
setup_ssh_env do
|
89
|
+
ret = Net::SSH.start(*ssh_start_params) do |ssh|
|
90
|
+
#ret = Net::SSH.start("localhost", "net_ssh_1", {keys: @key_id_rsa}) do |ssh|
|
91
|
+
ssh.exec! 'echo "hello from:$USER"'
|
92
|
+
end
|
93
|
+
assert_equal "hello from:net_ssh_1\n", ret
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_local_ephemeral_port_should_work_correctly
|
98
|
+
setup_ssh_env do
|
99
|
+
session = Net::SSH.start(*ssh_start_params)
|
100
|
+
|
101
|
+
assert_nothing_raised do
|
102
|
+
assigned_port = session.forward.local(0, localhost, 22)
|
103
|
+
assert_not_nil assigned_port
|
104
|
+
assert_operator assigned_port, :>, 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_remote_ephemeral_port_should_work_correctly
|
110
|
+
setup_ssh_env do
|
111
|
+
session = Net::SSH.start(*ssh_start_params)
|
112
|
+
|
113
|
+
assert_nothing_raised do
|
114
|
+
session.forward.remote(22, localhost, 0, localhost)
|
115
|
+
session.loop { !(session.forward.active_remotes.length > 0) }
|
116
|
+
assigned_port = session.forward.active_remotes.first[0]
|
117
|
+
assert_not_nil assigned_port
|
118
|
+
assert_operator assigned_port, :>, 0
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_remote_callback_should_fire
|
124
|
+
setup_ssh_env do
|
125
|
+
session = Net::SSH.start(*ssh_start_params)
|
126
|
+
|
127
|
+
assert_nothing_raised do
|
128
|
+
got_port = nil
|
129
|
+
session.forward.remote(22, localhost, 0, localhost) do |port|
|
130
|
+
got_port = port
|
131
|
+
end
|
132
|
+
session.loop { !(session.forward.active_remotes.length > 0) }
|
133
|
+
assert_operator session.forward.active_remote_destinations.length, :==, 1
|
134
|
+
assert_operator session.forward.active_remote_destinations.keys.first, :==, [ 22, localhost ]
|
135
|
+
assert_operator session.forward.active_remote_destinations.values.first, :==, [ got_port, localhost ]
|
136
|
+
assert_operator session.forward.active_remotes.first, :==, [ got_port, localhost ]
|
137
|
+
assigned_port = session.forward.active_remotes.first[0]
|
138
|
+
assert_operator got_port, :==, assigned_port
|
139
|
+
assert_not_nil assigned_port
|
140
|
+
assert_operator assigned_port, :>, 0
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_remote_callback_should_fire_on_error_and_still_throw_exception
|
146
|
+
setup_ssh_env do
|
147
|
+
session = Net::SSH.start(*ssh_start_params)
|
148
|
+
|
149
|
+
assert_nothing_raised do
|
150
|
+
session.forward.remote(22, localhost, 22, localhost) do |port|
|
151
|
+
assert_operator port, :==, :error
|
152
|
+
end
|
153
|
+
end
|
154
|
+
assert_raises(Net::SSH::Exception) do
|
155
|
+
session.loop { true }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_remote_callback_should_fire_on_error_but_not_throw_exception_if_asked_not_to
|
161
|
+
setup_ssh_env do
|
162
|
+
session = Net::SSH.start(*ssh_start_params)
|
163
|
+
|
164
|
+
assert_nothing_raised do
|
165
|
+
got_port = nil
|
166
|
+
session.forward.remote(22, localhost, 22, localhost) do |port|
|
167
|
+
assert_operator port, :==, :error
|
168
|
+
got_port = port
|
169
|
+
:no_exception
|
170
|
+
end
|
171
|
+
session.loop { !got_port }
|
172
|
+
assert_operator got_port, :==, :error
|
173
|
+
assert_operator session.forward.active_remotes.length, :==, 0
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_loop_should_not_abort_when_local_side_of_forward_is_closed
|
179
|
+
setup_ssh_env do
|
180
|
+
session = Net::SSH.start(*ssh_start_params)
|
181
|
+
server_exc = Queue.new
|
182
|
+
server = start_server_sending_lot_of_data(server_exc)
|
183
|
+
remote_port = server.addr[1]
|
184
|
+
local_port = 0 # request ephemeral port
|
185
|
+
session.forward.local(local_port, localhost, remote_port)
|
186
|
+
client_done = Queue.new
|
187
|
+
Thread.start do
|
188
|
+
begin
|
189
|
+
client = TCPSocket.new(localhost, local_port)
|
190
|
+
client.recv(1024)
|
191
|
+
client.close
|
192
|
+
sleep(0.2)
|
193
|
+
ensure
|
194
|
+
client_done << true
|
195
|
+
end
|
196
|
+
end
|
197
|
+
session.loop(0.1) { client_done.empty? }
|
198
|
+
assert_equal "Broken pipe", "#{server_exc.pop}" unless server_exc.empty?
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_loop_should_not_abort_when_local_side_of_forward_is_reset
|
203
|
+
setup_ssh_env do
|
204
|
+
session = Net::SSH.start(*ssh_start_params)
|
205
|
+
server_exc = Queue.new
|
206
|
+
server = start_server_sending_lot_of_data(server_exc)
|
207
|
+
remote_port = server.addr[1]
|
208
|
+
local_port = 0 # request ephemeral port
|
209
|
+
session.forward.local(local_port, localhost, remote_port)
|
210
|
+
client_done = Queue.new
|
211
|
+
Thread.start do
|
212
|
+
begin
|
213
|
+
client = TCPSocket.new(localhost, local_port)
|
214
|
+
client.recv(1024)
|
215
|
+
client.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack("ii"))
|
216
|
+
client.close
|
217
|
+
sleep(0.1)
|
218
|
+
ensure
|
219
|
+
client_done << true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
session.loop(0.1) { client_done.empty? }
|
223
|
+
assert_equal "Broken pipe", "#{server_exc.pop}" unless server_exc.empty?
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def create_local_socket(&blk)
|
228
|
+
tempfile = Tempfile.new("net_ssh_forward_test")
|
229
|
+
path = tempfile.path
|
230
|
+
tempfile.delete
|
231
|
+
yield UNIXServer.open(path)
|
232
|
+
File.delete(path)
|
233
|
+
end if defined?(UNIXServer)
|
234
|
+
|
235
|
+
def test_forward_local_unix_socket_to_remote_port
|
236
|
+
setup_ssh_env do
|
237
|
+
session = Net::SSH.start(*ssh_start_params)
|
238
|
+
server_exc = Queue.new
|
239
|
+
server = start_server_sending_lot_of_data(server_exc)
|
240
|
+
remote_port = server.addr[1]
|
241
|
+
client_data = nil
|
242
|
+
|
243
|
+
create_local_socket do |local_socket|
|
244
|
+
session.forward.local(local_socket, localhost, remote_port)
|
245
|
+
client_done = Queue.new
|
246
|
+
|
247
|
+
Thread.start do
|
248
|
+
begin
|
249
|
+
client = UNIXSocket.new(local_socket.path)
|
250
|
+
client_data = client.recv(1024)
|
251
|
+
client.close
|
252
|
+
sleep(0.2)
|
253
|
+
ensure
|
254
|
+
client_done << true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
session.loop(0.1) { client_done.empty? }
|
259
|
+
end
|
260
|
+
|
261
|
+
assert_not_nil(client_data, "client should have received data")
|
262
|
+
assert(client_data.match(/item\d/), 'client should have received the string item')
|
263
|
+
end
|
264
|
+
end if defined?(UNIXSocket)
|
265
|
+
|
266
|
+
def test_loop_should_not_abort_when_server_side_of_forward_is_closed
|
267
|
+
setup_ssh_env do
|
268
|
+
session = Net::SSH.start(*ssh_start_params)
|
269
|
+
server = start_server_closing_soon
|
270
|
+
remote_port = server.addr[1]
|
271
|
+
local_port = 0 # request ephemeral port
|
272
|
+
session.forward.local(local_port, localhost, remote_port)
|
273
|
+
client_done = Queue.new
|
274
|
+
Thread.start do
|
275
|
+
begin
|
276
|
+
client = TCPSocket.new(localhost, local_port)
|
277
|
+
1.times do |i|
|
278
|
+
client.puts "item#{i}"
|
279
|
+
end
|
280
|
+
client.close
|
281
|
+
sleep(0.1)
|
282
|
+
ensure
|
283
|
+
client_done << true
|
284
|
+
end
|
285
|
+
end
|
286
|
+
session.loop(0.1) { client_done.empty? }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def start_server
|
291
|
+
server = TCPServer.open(0)
|
292
|
+
Thread.start do
|
293
|
+
loop do
|
294
|
+
Thread.start(server.accept) do |client|
|
295
|
+
yield(client)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
return server
|
300
|
+
end
|
301
|
+
|
302
|
+
def test_client_close_should_be_handled_remote
|
303
|
+
setup_ssh_env do
|
304
|
+
message = "This is a small message!"*1000
|
305
|
+
session = Net::SSH.start(*ssh_start_params)
|
306
|
+
server_done = Queue.new
|
307
|
+
server = start_server do |client|
|
308
|
+
begin
|
309
|
+
data = client.read message.size
|
310
|
+
server_done << data
|
311
|
+
client.close
|
312
|
+
rescue
|
313
|
+
server_done << $!
|
314
|
+
end
|
315
|
+
end
|
316
|
+
client_done = Queue.new
|
317
|
+
got_remote_port = Queue.new
|
318
|
+
local_port = server.addr[1]
|
319
|
+
session.forward.remote(0, localhost, local_port) do |actual_remote_port|
|
320
|
+
got_remote_port << actual_remote_port
|
321
|
+
end
|
322
|
+
session.loop(0.1) { got_remote_port.empty? }
|
323
|
+
remote_port = got_remote_port.pop
|
324
|
+
Thread.start do
|
325
|
+
begin
|
326
|
+
client = TCPSocket.new(localhost, remote_port)
|
327
|
+
client.write(message)
|
328
|
+
client.close
|
329
|
+
client_done << true
|
330
|
+
rescue
|
331
|
+
client_done << $!
|
332
|
+
end
|
333
|
+
end
|
334
|
+
timeout(5) do
|
335
|
+
session.loop(0.1) { server_done.empty? }
|
336
|
+
assert_equal message, server_done.pop
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_client_close_should_be_handled
|
342
|
+
setup_ssh_env do
|
343
|
+
message = "This is a small message!"*1000
|
344
|
+
session = Net::SSH.start(*ssh_start_params)
|
345
|
+
server_done = Queue.new
|
346
|
+
server = start_server do |client|
|
347
|
+
begin
|
348
|
+
data = client.read message.size
|
349
|
+
server_done << data
|
350
|
+
client.close
|
351
|
+
rescue
|
352
|
+
server_done << $!
|
353
|
+
end
|
354
|
+
end
|
355
|
+
client_done = Queue.new
|
356
|
+
remote_port = server.addr[1]
|
357
|
+
local_port = session.forward.local(0, localhost, remote_port)
|
358
|
+
Thread.start do
|
359
|
+
begin
|
360
|
+
client = TCPSocket.new(localhost, local_port)
|
361
|
+
client.write(message)
|
362
|
+
client.close
|
363
|
+
client_done << true
|
364
|
+
rescue
|
365
|
+
client_done << $!
|
366
|
+
end
|
367
|
+
end
|
368
|
+
timeout(5) do
|
369
|
+
session.loop(0.1) { server_done.empty? }
|
370
|
+
assert_equal message, server_done.pop
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def test_server_eof_should_be_handled_remote
|
376
|
+
setup_ssh_env do
|
377
|
+
message = "This is a small message!"
|
378
|
+
session = Net::SSH.start(*ssh_start_params)
|
379
|
+
server = start_server do |client|
|
380
|
+
client.write message
|
381
|
+
client.close
|
382
|
+
end
|
383
|
+
client_done = Queue.new
|
384
|
+
got_remote_port = Queue.new
|
385
|
+
local_port = server.addr[1]
|
386
|
+
session.forward.remote(0, localhost, local_port) do |actual_remote_port|
|
387
|
+
got_remote_port << actual_remote_port
|
388
|
+
end
|
389
|
+
session.loop(0.1) { got_remote_port.empty? }
|
390
|
+
remote_port = got_remote_port.pop
|
391
|
+
Thread.start do
|
392
|
+
begin
|
393
|
+
client = TCPSocket.new(localhost, remote_port)
|
394
|
+
data = client.read(4096)
|
395
|
+
client.close
|
396
|
+
client_done << data
|
397
|
+
rescue
|
398
|
+
client_done << $!
|
399
|
+
end
|
400
|
+
end
|
401
|
+
timeout(5) do
|
402
|
+
session.loop(0.1) { client_done.empty? }
|
403
|
+
assert_equal message, client_done.pop
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def test_server_eof_should_be_handled
|
409
|
+
setup_ssh_env do
|
410
|
+
message = "This is a small message!"
|
411
|
+
session = Net::SSH.start(*ssh_start_params)
|
412
|
+
server = start_server do |client|
|
413
|
+
client.write message
|
414
|
+
client.close
|
415
|
+
end
|
416
|
+
client_done = Queue.new
|
417
|
+
remote_port = server.addr[1]
|
418
|
+
local_port = session.forward.local(0, localhost, remote_port)
|
419
|
+
Thread.start do
|
420
|
+
begin
|
421
|
+
client = TCPSocket.new(localhost, local_port)
|
422
|
+
data = client.read(4096)
|
423
|
+
client.close
|
424
|
+
client_done << data
|
425
|
+
rescue
|
426
|
+
client_done << $!
|
427
|
+
end
|
428
|
+
end
|
429
|
+
timeout(5) do
|
430
|
+
session.loop(0.1) { client_done.empty? }
|
431
|
+
assert_equal message, client_done.pop
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
@@ -10,12 +10,6 @@ require 'net/ssh'
|
|
10
10
|
class TestIDRSAPKeys < Test::Unit::TestCase
|
11
11
|
include IntegrationTestHelpers
|
12
12
|
|
13
|
-
def tmpdir(&block)
|
14
|
-
Dir.mktmpdir do |dir|
|
15
|
-
yield(dir)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
13
|
def test_in_file_no_password
|
20
14
|
tmpdir do |dir|
|
21
15
|
sh "rm -rf #{dir}/id_rsa #{dir}/id_rsa.pub"
|
@@ -81,4 +75,22 @@ class TestIDRSAPKeys < Test::Unit::TestCase
|
|
81
75
|
assert_equal "hello from:net_ssh_1\n", ret
|
82
76
|
end
|
83
77
|
end
|
78
|
+
|
79
|
+
def test_asks_for_passwords_when_read_from_memory
|
80
|
+
tmpdir do |dir|
|
81
|
+
sh "rm -rf #{dir}/id_rsa #{dir}/id_rsa.pub"
|
82
|
+
sh "ssh-keygen -f #{dir}/id_rsa -t rsa -N 'pwd12'"
|
83
|
+
set_authorized_key('net_ssh_1',"#{dir}/id_rsa.pub")
|
84
|
+
private_key = File.read("#{dir}/id_rsa")
|
85
|
+
|
86
|
+
options = {keys: [], key_data: [private_key]}
|
87
|
+
|
88
|
+
key_manager = Net::SSH::Authentication::KeyManager.new(nil, options)
|
89
|
+
|
90
|
+
Net::SSH::KeyFactory.expects(:prompt).with('Enter passphrase for :', false).returns('pwd12')
|
91
|
+
Net::SSH.start("localhost", "net_ssh_1", options) do |ssh|
|
92
|
+
ssh.exec! 'whoami'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
84
96
|
end
|