net-ssh 7.2.3 → 8.0.4
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 +0 -0
- data/.dockerignore +3 -0
- data/.github/pull_request_template.md +1 -0
- data/.github/workflows/changelog.yml +18 -0
- data/.github/workflows/ci-with-docker.yml +20 -9
- data/.github/workflows/ci.yml +10 -20
- data/.github/workflows/rubocop.yml +3 -1
- data/.rubocop.yml +5 -1
- data/.rubocop_todo.yml +4 -8
- data/CHANGES.txt +122 -0
- data/CONTRIBUTING.md +34 -0
- data/Dockerfile +4 -1
- data/Dockerfile.openssl3 +1 -1
- data/Dockerfile.zlib_ng +29 -0
- data/Gemfile +2 -0
- data/README.md +74 -57
- data/Rakefile +30 -12
- data/appveyor.yml +4 -16
- data/changelog.d/README.md +1 -0
- data/docker-compose.yml +1 -9
- data/lib/net/ssh/authentication/agent.rb +35 -11
- data/lib/net/ssh/authentication/ed25519.rb +140 -37
- data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
- data/lib/net/ssh/authentication/key_manager.rb +42 -16
- data/lib/net/ssh/authentication/methods/abstract.rb +2 -8
- data/lib/net/ssh/authentication/methods/publickey.rb +3 -1
- data/lib/net/ssh/authentication/security_key.rb +71 -0
- data/lib/net/ssh/authentication/session.rb +29 -17
- data/lib/net/ssh/buffer.rb +6 -1
- data/lib/net/ssh/buffered_io.rb +40 -3
- data/lib/net/ssh/config.rb +30 -13
- data/lib/net/ssh/connection/channel.rb +49 -2
- data/lib/net/ssh/connection/session.rb +151 -13
- data/lib/net/ssh/errors.rb +24 -6
- data/lib/net/ssh/key_factory.rb +11 -4
- data/lib/net/ssh/known_hosts.rb +62 -8
- data/lib/net/ssh/multiplex/client.rb +162 -0
- data/lib/net/ssh/multiplex/control_path.rb +120 -0
- data/lib/net/ssh/multiplex/proxy_transport.rb +218 -0
- data/lib/net/ssh/multiplex.rb +126 -0
- data/lib/net/ssh/prompt.rb +3 -3
- data/lib/net/ssh/proxy/command.rb +28 -3
- data/lib/net/ssh/proxy/jump.rb +71 -8
- data/lib/net/ssh/service/forward.rb +44 -27
- data/lib/net/ssh/test/extensions.rb +3 -2
- data/lib/net/ssh/test/socket.rb +1 -1
- data/lib/net/ssh/test.rb +2 -1
- data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
- data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
- data/lib/net/ssh/transport/algorithms.rb +96 -34
- data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +89 -16
- data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +20 -1
- data/lib/net/ssh/transport/cipher_factory.rb +32 -8
- data/lib/net/ssh/transport/constants.rb +1 -0
- data/lib/net/ssh/transport/gcm_cipher.rb +228 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
- data/lib/net/ssh/transport/hmac/sha1_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +2 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +41 -9
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +14 -6
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +8 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +45 -15
- data/lib/net/ssh/transport/openssl.rb +2 -1
- data/lib/net/ssh/transport/packet_stream.rb +19 -3
- data/lib/net/ssh/transport/server_version.rb +33 -13
- data/lib/net/ssh/transport/session.rb +61 -46
- data/lib/net/ssh/transport/state.rb +1 -1
- data/lib/net/ssh/verifiers/always.rb +43 -8
- data/lib/net/ssh/version.rb +3 -3
- data/lib/net/ssh.rb +148 -63
- data/net-ssh-public_cert.pem +7 -7
- data/net-ssh.gemspec +6 -8
- data.tar.gz.sig +0 -0
- metadata +64 -46
- metadata.gz.sig +0 -0
- data/Gemfile.noed25519 +0 -12
- data/Gemfile.norbnacl +0 -12
|
@@ -27,6 +27,10 @@ module Net
|
|
|
27
27
|
# The standard port for the SSH protocol.
|
|
28
28
|
DEFAULT_PORT = 22
|
|
29
29
|
|
|
30
|
+
# Packets held back during a re-key are bounded so a server can't exhaust memory (GHSA-ggxh-cfwq-5xqc).
|
|
31
|
+
MAX_QUEUED_PACKETS = 16_384
|
|
32
|
+
MAX_QUEUED_BYTES = 32 * 1024 * 1024
|
|
33
|
+
|
|
30
34
|
# The host to connect to, as given to the constructor.
|
|
31
35
|
attr_reader :host
|
|
32
36
|
|
|
@@ -62,6 +66,7 @@ module Net
|
|
|
62
66
|
@port = options[:port] || DEFAULT_PORT
|
|
63
67
|
@bind_address = options[:bind_address] || nil
|
|
64
68
|
@options = options
|
|
69
|
+
@host_key_verifier = select_host_key_verifier(options[:verify_host_key])
|
|
65
70
|
|
|
66
71
|
@socket =
|
|
67
72
|
if (factory = options[:proxy])
|
|
@@ -75,12 +80,12 @@ module Net
|
|
|
75
80
|
|
|
76
81
|
@socket.extend(PacketStream)
|
|
77
82
|
@socket.logger = @logger
|
|
83
|
+
set_no_delay unless options[:no_delay] == false
|
|
78
84
|
|
|
79
85
|
debug { "connection established" }
|
|
80
86
|
|
|
81
87
|
@queue = []
|
|
82
|
-
|
|
83
|
-
@host_key_verifier = select_host_key_verifier(options[:verify_host_key])
|
|
88
|
+
@queued_bytes = 0
|
|
84
89
|
|
|
85
90
|
@server_version = ServerVersion.new(socket, logger, options[:timeout])
|
|
86
91
|
|
|
@@ -88,7 +93,11 @@ module Net
|
|
|
88
93
|
@algorithms.start
|
|
89
94
|
wait { algorithms.initialized? }
|
|
90
95
|
rescue Errno::ETIMEDOUT
|
|
96
|
+
@socket&.close
|
|
91
97
|
raise Net::SSH::ConnectionTimeout
|
|
98
|
+
rescue StandardError
|
|
99
|
+
@socket&.close
|
|
100
|
+
raise
|
|
92
101
|
end
|
|
93
102
|
|
|
94
103
|
def host_keys
|
|
@@ -98,12 +107,16 @@ module Net
|
|
|
98
107
|
end
|
|
99
108
|
end
|
|
100
109
|
|
|
110
|
+
def pubkey_algorithms
|
|
111
|
+
algorithms[:pubkey_algorithms]
|
|
112
|
+
end
|
|
113
|
+
|
|
101
114
|
# Returns the host (and possibly IP address) in a format compatible with
|
|
102
115
|
# SSH known-host files.
|
|
103
116
|
def host_as_string
|
|
104
117
|
@host_as_string ||= begin
|
|
105
|
-
string = "#{host}"
|
|
106
|
-
string = "[#{string}]:#{port}" if port != DEFAULT_PORT
|
|
118
|
+
string = +"#{host}"
|
|
119
|
+
string = +"[#{string}]:#{port}" if port != DEFAULT_PORT
|
|
107
120
|
|
|
108
121
|
peer_ip = socket.peer_ip
|
|
109
122
|
|
|
@@ -188,7 +201,7 @@ module Net
|
|
|
188
201
|
# read from the queue before the socket is queried.
|
|
189
202
|
def poll_message(mode = :nonblock, consume_queue = true)
|
|
190
203
|
loop do
|
|
191
|
-
return
|
|
204
|
+
return shift_queue if consume_queue && @queue.any? && algorithms.allow?(@queue.first)
|
|
192
205
|
|
|
193
206
|
packet = socket.next_packet(mode, options[:timeout])
|
|
194
207
|
return nil if packet.nil?
|
|
@@ -206,11 +219,17 @@ module Net
|
|
|
206
219
|
when DEBUG
|
|
207
220
|
send(packet[:always_display] ? :fatal : :debug) { packet[:message] }
|
|
208
221
|
|
|
222
|
+
# We don't advertise ext-info-c, but some servers send EXT_INFO anyway (#955).
|
|
223
|
+
when EXT_INFO
|
|
224
|
+
debug { "ignoring EXT_INFO packet" }
|
|
225
|
+
|
|
209
226
|
when KEXINIT
|
|
210
227
|
algorithms.accept_kexinit(packet)
|
|
211
228
|
|
|
212
229
|
else
|
|
213
230
|
return packet if algorithms.allow?(packet)
|
|
231
|
+
# Algorithms#rekey! clears initialized?, so the session id is what marks the first key exchange as done.
|
|
232
|
+
raise Net::SSH::Exception, "unexpected packet type #{packet.type} during initial key exchange" unless algorithms.session_id
|
|
214
233
|
|
|
215
234
|
push(packet)
|
|
216
235
|
end
|
|
@@ -234,6 +253,11 @@ module Net
|
|
|
234
253
|
# #poll_message will return packets from the queue in the order they
|
|
235
254
|
# were received.
|
|
236
255
|
def push(packet)
|
|
256
|
+
@queued_bytes += packet.length
|
|
257
|
+
if @queue.size >= MAX_QUEUED_PACKETS || @queued_bytes > MAX_QUEUED_BYTES
|
|
258
|
+
raise Net::SSH::Exception, "too many packets queued during key exchange"
|
|
259
|
+
end
|
|
260
|
+
|
|
237
261
|
@queue.push(packet)
|
|
238
262
|
end
|
|
239
263
|
|
|
@@ -276,23 +300,31 @@ module Net
|
|
|
276
300
|
|
|
277
301
|
private
|
|
278
302
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
@verifier = verifier
|
|
285
|
-
end
|
|
303
|
+
def shift_queue
|
|
304
|
+
packet = @queue.shift
|
|
305
|
+
@queued_bytes -= packet.length
|
|
306
|
+
packet
|
|
307
|
+
end
|
|
286
308
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
309
|
+
# Turns Nagle's algorithm off on the socket, so that a packet written
|
|
310
|
+
# while the previous one is still unacknowledged goes out right away
|
|
311
|
+
# instead of waiting for the server's ACK. A ProxyCommand hands the
|
|
312
|
+
# transport a pipe rather than a socket, and the command on its other
|
|
313
|
+
# end owns the TCP connection, so there is nothing to set in that case.
|
|
314
|
+
def set_no_delay
|
|
315
|
+
return unless socket.respond_to?(:setsockopt)
|
|
290
316
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
end
|
|
317
|
+
debug { "setting TCP_NODELAY" }
|
|
318
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
294
319
|
end
|
|
295
320
|
|
|
321
|
+
REMOVED_VERIFY_HOST_KEY_VALUES = {
|
|
322
|
+
false => :never,
|
|
323
|
+
true => :accept_new_or_local_tunnel,
|
|
324
|
+
very: :accept_new,
|
|
325
|
+
secure: :always
|
|
326
|
+
}.freeze
|
|
327
|
+
|
|
296
328
|
# Instantiates a new host-key verification class, based on the value of
|
|
297
329
|
# the parameter.
|
|
298
330
|
#
|
|
@@ -306,46 +338,29 @@ module Net
|
|
|
306
338
|
#
|
|
307
339
|
# If the argument happens to respond to :verify and :verify_signature,
|
|
308
340
|
# it is returned directly. Otherwise, an exception is raised.
|
|
309
|
-
#
|
|
310
|
-
# Values false, true, and :very were deprecated in
|
|
311
|
-
# [#595](https://github.com/net-ssh/net-ssh/pull/595)
|
|
312
341
|
def select_host_key_verifier(verifier)
|
|
313
342
|
case verifier
|
|
314
|
-
when false
|
|
315
|
-
Kernel.warn('verify_host_key: false is deprecated, use :never')
|
|
316
|
-
Net::SSH::Verifiers::Never.new
|
|
317
343
|
when :never then
|
|
318
344
|
Net::SSH::Verifiers::Never.new
|
|
319
|
-
when true
|
|
320
|
-
Kernel.warn('verify_host_key: true is deprecated, use :accept_new_or_local_tunnel')
|
|
321
|
-
Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
|
|
322
345
|
when :accept_new_or_local_tunnel, nil then
|
|
323
346
|
Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
|
|
324
|
-
when :very
|
|
325
|
-
Kernel.warn('verify_host_key: :very is deprecated, use :accept_new')
|
|
326
|
-
Net::SSH::Verifiers::AcceptNew.new
|
|
327
347
|
when :accept_new then
|
|
328
348
|
Net::SSH::Verifiers::AcceptNew.new
|
|
329
|
-
when :secure then
|
|
330
|
-
Kernel.warn('verify_host_key: :secure is deprecated, use :always')
|
|
331
|
-
Net::SSH::Verifiers::Always.new
|
|
332
349
|
when :always then
|
|
333
350
|
Net::SSH::Verifiers::Always.new
|
|
351
|
+
when *REMOVED_VERIFY_HOST_KEY_VALUES.keys
|
|
352
|
+
raise ArgumentError,
|
|
353
|
+
"verify_host_key: #{verifier.inspect} has been removed, use #{REMOVED_VERIFY_HOST_KEY_VALUES[verifier].inspect}"
|
|
334
354
|
else
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
end
|
|
342
|
-
else
|
|
343
|
-
raise(
|
|
344
|
-
ArgumentError,
|
|
345
|
-
"Invalid argument to :verify_host_key (or deprecated " \
|
|
346
|
-
":paranoid): #{verifier.inspect}"
|
|
347
|
-
)
|
|
355
|
+
raise ArgumentError, "Invalid argument to :verify_host_key: #{verifier.inspect}" unless verifier.respond_to?(:verify)
|
|
356
|
+
|
|
357
|
+
unless verifier.respond_to?(:verify_signature)
|
|
358
|
+
raise ArgumentError,
|
|
359
|
+
"verify_host_key verifier #{verifier.inspect} must implement verify_signature(&block): " \
|
|
360
|
+
"yield to the block, which checks the server's host key signature, and return its result"
|
|
348
361
|
end
|
|
362
|
+
|
|
363
|
+
verifier
|
|
349
364
|
end
|
|
350
365
|
end
|
|
351
366
|
end
|
|
@@ -125,7 +125,7 @@ module Net
|
|
|
125
125
|
compressor.deflate(data, Zlib::SYNC_FLUSH)
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
#
|
|
128
|
+
# Decompresses the data. If no compression is in effect, this will just return
|
|
129
129
|
# the data unmodified, otherwise it uses #decompressor to decompress the data.
|
|
130
130
|
def decompress(data)
|
|
131
131
|
data = data.to_s
|
|
@@ -19,10 +19,8 @@ module Net
|
|
|
19
19
|
# We've never seen this host before, so raise an exception.
|
|
20
20
|
process_cache_miss(host_keys, arguments, HostKeyUnknown, "is unknown") if host_keys.empty?
|
|
21
21
|
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
found = host_keys.any? do |key|
|
|
22
|
+
# Find the known-hosts entry whose key matches the presented certificate.
|
|
23
|
+
found_key = host_keys.find do |key|
|
|
26
24
|
if key.respond_to?(:matches_key?)
|
|
27
25
|
key.matches_key?(arguments[:key])
|
|
28
26
|
else
|
|
@@ -30,11 +28,17 @@ module Net
|
|
|
30
28
|
end
|
|
31
29
|
end
|
|
32
30
|
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
# No matching entry found — key is not recognized.
|
|
32
|
+
process_cache_miss(host_keys, arguments, HostKeyMismatch, "does not match") unless found_key
|
|
33
|
+
|
|
34
|
+
# For @cert-authority entries, enforce the certificate constraints
|
|
35
|
+
# OpenSSH checks in sshkey_cert_check_authority. A certificate signed by
|
|
36
|
+
# the trusted CA but failing any of these is rejected with
|
|
37
|
+
# HostKeyMismatch (not HostKeyUnknown) so AcceptNew does not swallow the
|
|
38
|
+
# failure and remember the rejected certificate.
|
|
39
|
+
verify_certificate!(found_key, host_keys, arguments)
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
true
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def verify_signature(&block)
|
|
@@ -43,6 +47,37 @@ module Net
|
|
|
43
47
|
|
|
44
48
|
private
|
|
45
49
|
|
|
50
|
+
# Enforces the @cert-authority certificate constraints for entries that
|
|
51
|
+
# implement them (Net::SSH::HostKeyEntries::CertAuthority). Non-certificate
|
|
52
|
+
# entries do not respond to these predicates and are left untouched.
|
|
53
|
+
def verify_certificate!(entry, host_keys, arguments)
|
|
54
|
+
cert = arguments[:key]
|
|
55
|
+
|
|
56
|
+
if entry.respond_to?(:matches_validity?) && !entry.matches_validity?(cert)
|
|
57
|
+
reason = if cert.valid_before && cert.valid_before < Time.now
|
|
58
|
+
"Certificate has expired"
|
|
59
|
+
else
|
|
60
|
+
"Certificate is not yet valid"
|
|
61
|
+
end
|
|
62
|
+
process_cache_miss(host_keys, arguments, HostKeyMismatch, reason)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if entry.respond_to?(:matches_principal?) && !entry.matches_principal?(cert, host_keys.hostname)
|
|
66
|
+
process_cache_miss(host_keys, arguments, HostKeyMismatch,
|
|
67
|
+
"Certificate invalid: name is not a listed principal")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if entry.respond_to?(:matches_type?) && !entry.matches_type?(cert)
|
|
71
|
+
process_cache_miss(host_keys, arguments, HostKeyMismatch,
|
|
72
|
+
"Certificate invalid: not a host certificate")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return unless entry.respond_to?(:critical_options_supported?) && !entry.critical_options_supported?(cert)
|
|
76
|
+
|
|
77
|
+
process_cache_miss(host_keys, arguments, HostKeyMismatch,
|
|
78
|
+
"Certificate invalid: unsupported critical option")
|
|
79
|
+
end
|
|
80
|
+
|
|
46
81
|
def process_cache_miss(host_keys, args, exc_class, message)
|
|
47
82
|
exception = exc_class.new("fingerprint #{args[:fingerprint]} " +
|
|
48
83
|
"#{message} for #{host_keys.host.inspect}")
|
data/lib/net/ssh/version.rb
CHANGED
|
@@ -46,13 +46,13 @@ module Net
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# The major component of this version of the Net::SSH library
|
|
49
|
-
MAJOR =
|
|
49
|
+
MAJOR = 8
|
|
50
50
|
|
|
51
51
|
# The minor component of this version of the Net::SSH library
|
|
52
|
-
MINOR =
|
|
52
|
+
MINOR = 0
|
|
53
53
|
|
|
54
54
|
# The tiny component of this version of the Net::SSH library
|
|
55
|
-
TINY =
|
|
55
|
+
TINY = 4
|
|
56
56
|
|
|
57
57
|
# The prerelease component of this version of the Net::SSH library
|
|
58
58
|
# nil allowed
|
data/lib/net/ssh.rb
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
# Make sure HOME is set, regardless of OS, so that File.expand_path works
|
|
2
|
-
# as expected with tilde characters.
|
|
3
|
-
ENV['HOME'] ||= ENV['HOMEPATH'] ? "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" : Dir.pwd
|
|
4
|
-
|
|
5
1
|
require 'logger'
|
|
6
2
|
require 'etc'
|
|
7
3
|
require 'shellwords'
|
|
@@ -12,6 +8,7 @@ require 'net/ssh/loggable'
|
|
|
12
8
|
require 'net/ssh/transport/session'
|
|
13
9
|
require 'net/ssh/authentication/session'
|
|
14
10
|
require 'net/ssh/connection/session'
|
|
11
|
+
require 'net/ssh/multiplex'
|
|
15
12
|
require 'net/ssh/prompt'
|
|
16
13
|
|
|
17
14
|
module Net
|
|
@@ -66,14 +63,15 @@ module Net
|
|
|
66
63
|
auth_methods bind_address compression compression_level config
|
|
67
64
|
encryption forward_agent hmac host_key identity_agent remote_user
|
|
68
65
|
keepalive keepalive_interval keepalive_maxcount kex keys key_data
|
|
69
|
-
keycerts keycert_data languages logger
|
|
66
|
+
keycerts keycert_data languages logger password port proxy
|
|
70
67
|
rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose
|
|
71
68
|
known_hosts global_known_hosts_file user_known_hosts_file host_key_alias
|
|
72
69
|
host_name user properties passphrase keys_only max_pkt_size
|
|
73
70
|
max_win_size send_env set_env use_agent number_of_password_prompts
|
|
74
71
|
append_all_supported_algorithms non_interactive password_prompt
|
|
75
72
|
agent_socket_factory minimum_dh_bits verify_host_key
|
|
76
|
-
fingerprint_hash check_host_ip pubkey_algorithms
|
|
73
|
+
fingerprint_hash check_host_ip pubkey_algorithms no_delay
|
|
74
|
+
control_master control_path preopen_channels
|
|
77
75
|
]
|
|
78
76
|
|
|
79
77
|
# The standard means of starting a new SSH connection. When used with a
|
|
@@ -102,24 +100,62 @@ module Net
|
|
|
102
100
|
# ssh.loop
|
|
103
101
|
# end
|
|
104
102
|
#
|
|
105
|
-
# This method accepts the following options (all are optional)
|
|
103
|
+
# This method accepts the following options (all are optional). Omit an
|
|
104
|
+
# option to use its default or ssh_config value; passing +nil+ (or an array
|
|
105
|
+
# containing +nil+) raises ArgumentError, except for :password,
|
|
106
|
+
# :passphrase, :logger and :password_prompt, where +nil+ means the default.
|
|
106
107
|
#
|
|
107
108
|
# * :auth_methods => an array of authentication methods to try
|
|
108
109
|
# * :bind_address => the IP address on the connecting machine to use in
|
|
109
110
|
# establishing connection. (:bind_address is discarded if :proxy
|
|
110
111
|
# is set.)
|
|
111
|
-
# * :check_host_ip => Also
|
|
112
|
+
# * :check_host_ip => Also check IP address when connecting to remote host.
|
|
112
113
|
# Defaults to +true+.
|
|
113
114
|
# * :compression => the compression algorithm to use, or +true+ to use
|
|
114
115
|
# whatever is supported.
|
|
115
116
|
# * :compression_level => the compression level to use when sending data
|
|
117
|
+
# * :control_master => set to +:auto+ to run the session through an
|
|
118
|
+
# already running OpenSSH ControlMaster for the same host, port and user
|
|
119
|
+
# (started e.g. with <tt>ssh -M</tt> or <tt>ControlMaster auto</tt>),
|
|
120
|
+
# using its proxy mode (OpenSSH 7.4 or later). This skips the TCP
|
|
121
|
+
# connection, key exchange and authentication. If there is no usable
|
|
122
|
+
# master (no ControlPath, no socket, refused, timed out, owned by another
|
|
123
|
+
# user) Net::SSH logs why and connects as usual; it never starts a master
|
|
124
|
+
# itself. The master's own settings then apply instead of the options
|
|
125
|
+
# for setting up a connection (:keys, :key_data, :password, :passphrase,
|
|
126
|
+
# :auth_methods, :verify_host_key, :known_hosts, :host_key, :kex,
|
|
127
|
+
# :encryption, :hmac, :compression, :rekey_*, :proxy, :keepalive and the
|
|
128
|
+
# like); explicitly passed ones are named at info level. Over the
|
|
129
|
+
# master, remote port forwarding (Service::Forward#remote and
|
|
130
|
+
# #cancel_remote) raises Net::SSH::NotSupportedOverControlMaster, as the
|
|
131
|
+
# master consumes the global request reply, so success, failure and the
|
|
132
|
+
# port allocated for port 0 can't be observed; other global requests
|
|
133
|
+
# raise it too. Agent and X11 forwarding requests raise it
|
|
134
|
+
# too, and :forward_agent is ignored (logged at info level), because the
|
|
135
|
+
# master serves them from its own agent or display only if it has
|
|
136
|
+
# ForwardAgent or ForwardX11 enabled itself, and rejects them otherwise.
|
|
137
|
+
# Keepalive and rekeying are left to the master. ssh_config's
|
|
138
|
+
# ControlMaster is ignored, only this option enables it. An invalid
|
|
139
|
+
# ControlPath raises ArgumentError. A missing or stale socket is logged
|
|
140
|
+
# at info level, a socket that can't be used (another owner, refused,
|
|
141
|
+
# wrong protocol, timed out) at warn level. Has no effect on Windows.
|
|
142
|
+
# Defaults to +false+.
|
|
143
|
+
# * :control_path => the ControlMaster socket to use with
|
|
144
|
+
# :control_master, with the same ~, ${ENV} and % tokens as ssh_config's
|
|
145
|
+
# ControlPath (%C, %d, %h, %i, %j, %k, %L, %l, %n, %p, %r, %u, %%).
|
|
146
|
+
# The master at that path decides which host and user the session is
|
|
147
|
+
# connected to, so a fixed path without %h, %p, %r or %C sends every
|
|
148
|
+
# host through the same master and the explicitly passed host key and
|
|
149
|
+
# authentication options are ignored. Defaults to ControlPath from
|
|
150
|
+
# ssh_config; +"none"+ disables the master.
|
|
116
151
|
# * :config => set to +true+ to load the default OpenSSH config files
|
|
117
152
|
# (~/.ssh/config, /etc/ssh_config), or to +false+ to not load them, or to
|
|
118
153
|
# a file-name (or array of file-names) to load those specific configuration
|
|
119
154
|
# files. Defaults to +true+.
|
|
120
155
|
# * :encryption => the encryption cipher (or ciphers) to use
|
|
121
156
|
# * :forward_agent => set to true if you want the SSH agent connection to
|
|
122
|
-
# be forwarded
|
|
157
|
+
# be forwarded. Ignored for a session through a ControlMaster (see
|
|
158
|
+
# :control_master).
|
|
123
159
|
# * :known_hosts => a custom object holding known hosts records.
|
|
124
160
|
# It must implement #search_for and `add` in a similiar manner as KnownHosts.
|
|
125
161
|
# * :global_known_hosts_file => the location of the global known hosts
|
|
@@ -141,7 +177,11 @@ module Net
|
|
|
141
177
|
# Defaults to +300+ seconds.
|
|
142
178
|
# * :keepalive_maxcount => the maximun number of keepalive packet miss allowed.
|
|
143
179
|
# Defaults to 3
|
|
144
|
-
# * :kex => the key exchange algorithm (or algorithms) to use
|
|
180
|
+
# * :kex => the key exchange algorithm (or algorithms) to use. Like
|
|
181
|
+
# KexAlgorithms, entries starting with +, - or ^ modify the default list.
|
|
182
|
+
# diffie-hellman-group14-sha1 is supported but not offered unless listed
|
|
183
|
+
# here (e.g. '+diffie-hellman-group14-sha1') or
|
|
184
|
+
# :append_all_supported_algorithms is set.
|
|
145
185
|
# * :keys => an array of file names of private keys to use for publickey
|
|
146
186
|
# and hostbased authentication
|
|
147
187
|
# * :keycerts => an array of file names of key certificates to use
|
|
@@ -159,24 +199,44 @@ module Net
|
|
|
159
199
|
# packet. Default is 0x8000 (32768 bytes). Increase to 0x10000 (65536 bytes)
|
|
160
200
|
# for better performance if your SSH server supports it (most do).
|
|
161
201
|
# * :max_win_size => maximum size we tell the other side that is supported for
|
|
162
|
-
# the window.
|
|
202
|
+
# the window, i.e. how much the server may send on a channel before net-ssh
|
|
203
|
+
# reads it. Default is 0x200000 (2 MB), as in OpenSSH. Smaller values limit
|
|
204
|
+
# how much data is in flight; some servers (e.g. Mikrotik) truncate output
|
|
205
|
+
# when the window is small.
|
|
206
|
+
# * :minimum_dh_bits => the smallest group, in bits, accepted from the server
|
|
207
|
+
# in diffie-hellman-group-exchange key exchange. Defaults to 2048, as in
|
|
208
|
+
# OpenSSH; lower it (e.g. to 1024) only to reach legacy servers that offer
|
|
209
|
+
# nothing larger.
|
|
210
|
+
# * :no_delay => turns Nagle's algorithm off on the connection's socket
|
|
211
|
+
# (TCP_NODELAY), so a packet written while the previous one is still
|
|
212
|
+
# unacknowledged is sent right away instead of waiting for the server's
|
|
213
|
+
# ACK. OpenSSH does the same for every connection. Set to +false+ to keep
|
|
214
|
+
# Nagle's algorithm on. Has no effect when the socket comes from a
|
|
215
|
+
# ProxyCommand, whose command owns the TCP connection. Defaults to +true+.
|
|
163
216
|
# * :non_interactive => set to true if your app is non interactive and prefers
|
|
164
217
|
# authentication failure vs password prompt. Non-interactive applications
|
|
165
218
|
# should set it to true to prefer failing a password/etc auth methods vs.
|
|
166
219
|
# asking for password.
|
|
167
|
-
# * :paranoid => deprecated alias for :verify_host_key
|
|
168
220
|
# * :passphrase => the passphrase to use when loading a private key (default
|
|
169
221
|
# is +nil+, for no passphrase)
|
|
170
222
|
# * :password => the password to use to login
|
|
171
223
|
# * :port => the port to use when connecting to the remote host
|
|
224
|
+
# * :preopen_channels => set to +true+ to keep one spare "session" channel
|
|
225
|
+
# open, so that Net::SSH::Connection::Session#open_channel can hand it
|
|
226
|
+
# out at once and a command that waits for the previous one to finish
|
|
227
|
+
# costs one round trip instead of two. The spare counts against the
|
|
228
|
+
# server's MaxSessions; if the server refuses it, pre-opening stops for
|
|
229
|
+
# the session. Defaults to +false+.
|
|
172
230
|
# * :properties => a hash of key/value pairs to add to the new connection's
|
|
173
231
|
# properties (see Net::SSH::Connection::Session#properties)
|
|
174
232
|
# * :proxy => a proxy instance (see Proxy) to use when connecting
|
|
175
|
-
# * :pubkey_algorithms => the public key authentication algorithms to
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
233
|
+
# * :pubkey_algorithms => the public key authentication algorithms to offer,
|
|
234
|
+
# in order of preference (e.g. %w[ssh-ed25519 rsa-sha2-512 rsa-sha2-256]).
|
|
235
|
+
# Keys whose type isn't listed are skipped. Like PubkeyAcceptedAlgorithms,
|
|
236
|
+
# entries starting with +, - or ^ modify the default list. ssh-rsa (SHA-1),
|
|
237
|
+
# ssh-rsa-cert-v01@openssh.com and ssh-dss are supported but not offered
|
|
238
|
+
# unless listed here (e.g. '+ssh-rsa') or :append_all_supported_algorithms
|
|
239
|
+
# is set.
|
|
180
240
|
# * :rekey_blocks_limit => the max number of blocks to process before rekeying
|
|
181
241
|
# * :rekey_limit => the max number of bytes to process before rekeying
|
|
182
242
|
# * :rekey_packet_limit => the max number of packets to process before rekeying
|
|
@@ -188,7 +248,8 @@ module Net
|
|
|
188
248
|
# * :user => the user name to log in as; this overrides the +user+
|
|
189
249
|
# parameter, and is primarily only useful when provided via an SSH
|
|
190
250
|
# configuration file.
|
|
191
|
-
# * :remote_user => used for substitution into the '%r' part of a ProxyCommand
|
|
251
|
+
# * :remote_user => used for substitution into the '%r' part of a ProxyCommand;
|
|
252
|
+
# defaults to the user name the connection logs in as.
|
|
192
253
|
# * :user_known_hosts_file => the location of the user known hosts file.
|
|
193
254
|
# Set to an array to specify multiple user known hosts files.
|
|
194
255
|
# Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2).
|
|
@@ -200,7 +261,9 @@ module Net
|
|
|
200
261
|
# default. The symbols :debug, :info, :warn, :error, and :fatal are also
|
|
201
262
|
# supported and are translated to the corresponding Logger constant.
|
|
202
263
|
# * :append_all_supported_algorithms => set to +true+ to append all supported
|
|
203
|
-
# algorithms by net-ssh
|
|
264
|
+
# algorithms by net-ssh, except the +none+ cipher and MAC, which must be
|
|
265
|
+
# named explicitly (e.g. encryption: '+none'). Was the default behaviour
|
|
266
|
+
# until 2.10
|
|
204
267
|
# * :number_of_password_prompts => Number of prompts for the password
|
|
205
268
|
# authentication method defaults to 3 set to 0 to disable prompt for
|
|
206
269
|
# password auth method
|
|
@@ -215,21 +278,32 @@ module Net
|
|
|
215
278
|
# * :accept_new_or_local_tunnel (insecure) ::Net::SSH::Verifiers::AcceptNewOrLocalTunnel
|
|
216
279
|
# * :accept_new (insecure) ::Net::SSH::Verifiers::AcceptNew
|
|
217
280
|
# * :always (secure) ::Net::SSH::Verifiers::Always
|
|
218
|
-
# You can also provide an own Object which responds to +verify
|
|
219
|
-
# given to +verify+ is a hash consisting
|
|
220
|
-
# the +:fingerprint+ and the +:session+.
|
|
221
|
-
# returning false declines it and
|
|
281
|
+
# You can also provide an own Object which responds to +verify+ and
|
|
282
|
+
# +verify_signature+. The argument given to +verify+ is a hash consisting
|
|
283
|
+
# of the +:key+, the +:key_blob+, the +:fingerprint+ and the +:session+.
|
|
284
|
+
# Returning true accepts the host key, returning false declines it and
|
|
285
|
+
# closes the connection. +verify_signature+ is given a block that checks
|
|
286
|
+
# the server's host key signature and must return the block's result.
|
|
222
287
|
# * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256'
|
|
223
288
|
# If +user+ parameter is nil it defaults to USER from ssh_config, or
|
|
224
289
|
# local username
|
|
225
290
|
def self.start(host, user = nil, options = {}, &block)
|
|
291
|
+
if options.key?(:paranoid)
|
|
292
|
+
raise ArgumentError, ":paranoid has been removed, use :verify_host_key (same values)"
|
|
293
|
+
end
|
|
294
|
+
|
|
226
295
|
invalid_options = options.keys - VALID_OPTIONS
|
|
227
296
|
if invalid_options.any?
|
|
228
297
|
raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}"
|
|
229
298
|
end
|
|
230
299
|
|
|
300
|
+
reject_nil_options(options)
|
|
301
|
+
Multiplex.validate_options!(options)
|
|
302
|
+
explicit_options = options.keys
|
|
303
|
+
host_arg = host
|
|
304
|
+
|
|
305
|
+
options = options.dup
|
|
231
306
|
assign_defaults(options)
|
|
232
|
-
_sanitize_options(options)
|
|
233
307
|
|
|
234
308
|
options[:user] = user if user
|
|
235
309
|
options = configuration_for(host, options.fetch(:config, true)).merge(options)
|
|
@@ -241,8 +315,6 @@ module Net
|
|
|
241
315
|
options[:number_of_password_prompts] = 0
|
|
242
316
|
end
|
|
243
317
|
|
|
244
|
-
_support_deprecated_option_paranoid(options)
|
|
245
|
-
|
|
246
318
|
if options[:verbose]
|
|
247
319
|
options[:logger].level = case options[:verbose]
|
|
248
320
|
when Integer then options[:verbose]
|
|
@@ -255,26 +327,35 @@ module Net
|
|
|
255
327
|
end
|
|
256
328
|
end
|
|
257
329
|
|
|
258
|
-
transport = Transport::Session.new(host, options)
|
|
259
|
-
auth = Authentication::Session.new(transport, options)
|
|
260
|
-
|
|
261
330
|
user = options.fetch(:user, user) || Etc.getpwuid.name
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
331
|
+
options[:remote_user] ||= user
|
|
332
|
+
|
|
333
|
+
connection = Multiplex.connect(host_arg, host, user, options, explicit: explicit_options) if options[:control_master]
|
|
334
|
+
connection ||= connect_directly(host, user, options)
|
|
335
|
+
|
|
336
|
+
if block_given?
|
|
337
|
+
begin
|
|
338
|
+
yield connection
|
|
339
|
+
ensure
|
|
340
|
+
connection.close unless connection.closed?
|
|
272
341
|
end
|
|
273
342
|
else
|
|
343
|
+
connection
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def self.connect_directly(host, user, options)
|
|
348
|
+
transport = Transport::Session.new(host, options)
|
|
349
|
+
auth = Authentication::Session.new(transport, options)
|
|
350
|
+
|
|
351
|
+
unless auth.authenticate("ssh-connection", user, options[:password])
|
|
274
352
|
transport.close
|
|
275
353
|
raise AuthenticationFailed, "Authentication failed for user #{user}@#{host}"
|
|
276
354
|
end
|
|
355
|
+
|
|
356
|
+
Connection::Session.new(transport, options)
|
|
277
357
|
end
|
|
358
|
+
private_class_method :connect_directly
|
|
278
359
|
|
|
279
360
|
# Returns a hash of the configuration options for the given host, as read
|
|
280
361
|
# from the SSH configuration file(s). If +use_ssh_config+ is true (the
|
|
@@ -308,31 +389,35 @@ module Net
|
|
|
308
389
|
end
|
|
309
390
|
end
|
|
310
391
|
|
|
311
|
-
|
|
312
|
-
invalid_option_values = [nil, [nil]]
|
|
313
|
-
unless (options.values & invalid_option_values).empty?
|
|
314
|
-
nil_options = options.select { |_k, v| invalid_option_values.include?(v) }.map(&:first)
|
|
315
|
-
Kernel.warn "#{caller_locations(2, 1)[0]}: Passing nil, or [nil] to Net::SSH.start is deprecated for keys: #{nil_options.join(', ')}"
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
private_class_method :_sanitize_options
|
|
392
|
+
NIL_ALLOWED_OPTIONS = %i[password passphrase logger password_prompt].freeze
|
|
319
393
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
394
|
+
NIL_OPTION_HINTS = {
|
|
395
|
+
config: "config: false to skip ssh_config files",
|
|
396
|
+
proxy: "proxy: false to connect without a proxy",
|
|
397
|
+
keys: "keys: [] to use no key files",
|
|
398
|
+
use_agent: "use_agent: false to not use ssh-agent",
|
|
399
|
+
forward_agent: "forward_agent: false to not forward ssh-agent",
|
|
400
|
+
keepalive: "keepalive: false to send no keepalives",
|
|
401
|
+
compression: "compression: false to disable compression",
|
|
402
|
+
control_master: "control_master: false to not use a ControlMaster",
|
|
403
|
+
control_path: "control_path: 'none' to not use a ControlMaster socket",
|
|
404
|
+
send_env: "send_env: [] to send no environment variables",
|
|
405
|
+
set_env: "set_env: {} to set no environment variables"
|
|
406
|
+
}.freeze
|
|
407
|
+
private_constant :NIL_ALLOWED_OPTIONS, :NIL_OPTION_HINTS
|
|
408
|
+
|
|
409
|
+
def self.reject_nil_options(options)
|
|
410
|
+
nil_keys = options.select do |key, value|
|
|
411
|
+
!NIL_ALLOWED_OPTIONS.include?(key) && (value.nil? || (value.is_a?(Array) && value.include?(nil)))
|
|
412
|
+
end.keys
|
|
413
|
+
return if nil_keys.empty?
|
|
414
|
+
|
|
415
|
+
message = "nil is not allowed for #{nil_keys.map(&:inspect).join(', ')}; " \
|
|
416
|
+
"omit #{nil_keys.one? ? 'it' : 'them'} to use the default or ssh_config value"
|
|
417
|
+
hints = NIL_OPTION_HINTS.values_at(*nil_keys).compact
|
|
418
|
+
message += ", or pass #{hints.join(', or ')}" if hints.any?
|
|
419
|
+
raise ArgumentError, message
|
|
335
420
|
end
|
|
336
|
-
private_class_method :
|
|
421
|
+
private_class_method :reject_nil_options
|
|
337
422
|
end
|
|
338
423
|
end
|