net-ssh 7.2.0 → 8.0.0

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +3 -0
  4. data/.github/pull_request_template.md +1 -0
  5. data/.github/workflows/changelog.yml +43 -0
  6. data/.github/workflows/ci-with-docker.yml +20 -9
  7. data/.github/workflows/ci.yml +11 -20
  8. data/.github/workflows/rubocop.yml +3 -1
  9. data/.gitignore +2 -0
  10. data/.rubocop.yml +1 -1
  11. data/.rubocop_todo.yml +4 -1
  12. data/CHANGES.txt +114 -0
  13. data/CONTRIBUTING.md +35 -0
  14. data/Dockerfile +7 -2
  15. data/Dockerfile.openssl3 +1 -1
  16. data/Dockerfile.zlib_ng +29 -0
  17. data/README.md +76 -57
  18. data/Rakefile +117 -30
  19. data/appveyor.yml +4 -16
  20. data/changelog.d/README.md +1 -0
  21. data/docker-compose.yml +2 -8
  22. data/lib/net/ssh/authentication/agent.rb +35 -11
  23. data/lib/net/ssh/authentication/ed25519.rb +141 -40
  24. data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
  25. data/lib/net/ssh/authentication/key_manager.rb +61 -18
  26. data/lib/net/ssh/authentication/methods/abstract.rb +2 -8
  27. data/lib/net/ssh/authentication/methods/publickey.rb +3 -1
  28. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +1 -1
  29. data/lib/net/ssh/authentication/security_key.rb +71 -0
  30. data/lib/net/ssh/authentication/session.rb +34 -15
  31. data/lib/net/ssh/buffer.rb +6 -1
  32. data/lib/net/ssh/buffered_io.rb +41 -4
  33. data/lib/net/ssh/config.rb +30 -13
  34. data/lib/net/ssh/connection/channel.rb +49 -2
  35. data/lib/net/ssh/connection/session.rb +58 -8
  36. data/lib/net/ssh/errors.rb +24 -6
  37. data/lib/net/ssh/key_factory.rb +11 -4
  38. data/lib/net/ssh/known_hosts.rb +64 -11
  39. data/lib/net/ssh/multiplex/client.rb +162 -0
  40. data/lib/net/ssh/multiplex/control_path.rb +120 -0
  41. data/lib/net/ssh/multiplex/proxy_transport.rb +218 -0
  42. data/lib/net/ssh/multiplex.rb +130 -0
  43. data/lib/net/ssh/prompt.rb +3 -3
  44. data/lib/net/ssh/proxy/command.rb +28 -3
  45. data/lib/net/ssh/proxy/jump.rb +52 -4
  46. data/lib/net/ssh/service/forward.rb +38 -27
  47. data/lib/net/ssh/test/extensions.rb +3 -2
  48. data/lib/net/ssh/test/socket.rb +1 -1
  49. data/lib/net/ssh/test.rb +2 -1
  50. data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
  51. data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
  52. data/lib/net/ssh/transport/algorithms.rb +96 -34
  53. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +89 -16
  54. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +20 -1
  55. data/lib/net/ssh/transport/cipher_factory.rb +32 -8
  56. data/lib/net/ssh/transport/constants.rb +1 -0
  57. data/lib/net/ssh/transport/gcm_cipher.rb +228 -0
  58. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  59. data/lib/net/ssh/transport/hmac/sha1_etm.rb +12 -0
  60. data/lib/net/ssh/transport/hmac.rb +2 -0
  61. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +41 -9
  62. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +14 -6
  63. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +8 -0
  64. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +45 -15
  65. data/lib/net/ssh/transport/openssl.rb +2 -1
  66. data/lib/net/ssh/transport/packet_stream.rb +19 -3
  67. data/lib/net/ssh/transport/server_version.rb +33 -13
  68. data/lib/net/ssh/transport/session.rb +60 -46
  69. data/lib/net/ssh/transport/state.rb +1 -1
  70. data/lib/net/ssh/verifiers/always.rb +43 -8
  71. data/lib/net/ssh/version.rb +2 -2
  72. data/lib/net/ssh.rb +142 -62
  73. data/net-ssh-public_cert.pem +19 -18
  74. data/net-ssh.gemspec +7 -8
  75. data/rakelib/changelog.rb +229 -0
  76. data.tar.gz.sig +0 -0
  77. metadata +86 -53
  78. metadata.gz.sig +1 -1
  79. data/Gemfile.noed25519 +0 -12
  80. data/Gemfile.norbnacl +0 -12
  81. data/lib/net/ssh/version.rb.old +0 -68
@@ -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 @queue.shift if consume_queue && @queue.any? && algorithms.allow?(@queue.first)
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,16 @@ 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
+ raise Net::SSH::Exception, "unexpected packet type #{packet.type} during initial key exchange" unless algorithms.initialized?
214
232
 
215
233
  push(packet)
216
234
  end
@@ -234,6 +252,11 @@ module Net
234
252
  # #poll_message will return packets from the queue in the order they
235
253
  # were received.
236
254
  def push(packet)
255
+ @queued_bytes += packet.length
256
+ if @queue.size >= MAX_QUEUED_PACKETS || @queued_bytes > MAX_QUEUED_BYTES
257
+ raise Net::SSH::Exception, "too many packets queued during key exchange"
258
+ end
259
+
237
260
  @queue.push(packet)
238
261
  end
239
262
 
@@ -276,23 +299,31 @@ module Net
276
299
 
277
300
  private
278
301
 
279
- # Compatibility verifier which allows users to keep using
280
- # custom verifier code without adding new :verify_signature
281
- # method.
282
- class CompatibleVerifier
283
- def initialize(verifier)
284
- @verifier = verifier
285
- end
302
+ def shift_queue
303
+ packet = @queue.shift
304
+ @queued_bytes -= packet.length
305
+ packet
306
+ end
286
307
 
287
- def verify(arguments)
288
- @verifier.verify(arguments)
289
- end
308
+ # Turns Nagle's algorithm off on the socket, so that a packet written
309
+ # while the previous one is still unacknowledged goes out right away
310
+ # instead of waiting for the server's ACK. A ProxyCommand hands the
311
+ # transport a pipe rather than a socket, and the command on its other
312
+ # end owns the TCP connection, so there is nothing to set in that case.
313
+ def set_no_delay
314
+ return unless socket.respond_to?(:setsockopt)
290
315
 
291
- def verify_signature(&block)
292
- yield
293
- end
316
+ debug { "setting TCP_NODELAY" }
317
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
294
318
  end
295
319
 
320
+ REMOVED_VERIFY_HOST_KEY_VALUES = {
321
+ false => :never,
322
+ true => :accept_new_or_local_tunnel,
323
+ very: :accept_new,
324
+ secure: :always
325
+ }.freeze
326
+
296
327
  # Instantiates a new host-key verification class, based on the value of
297
328
  # the parameter.
298
329
  #
@@ -306,46 +337,29 @@ module Net
306
337
  #
307
338
  # If the argument happens to respond to :verify and :verify_signature,
308
339
  # 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
340
  def select_host_key_verifier(verifier)
313
341
  case verifier
314
- when false
315
- Kernel.warn('verify_host_key: false is deprecated, use :never')
316
- Net::SSH::Verifiers::Never.new
317
342
  when :never then
318
343
  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
344
  when :accept_new_or_local_tunnel, nil then
323
345
  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
346
  when :accept_new then
328
347
  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
348
  when :always then
333
349
  Net::SSH::Verifiers::Always.new
350
+ when *REMOVED_VERIFY_HOST_KEY_VALUES.keys
351
+ raise ArgumentError,
352
+ "verify_host_key: #{verifier.inspect} has been removed, use #{REMOVED_VERIFY_HOST_KEY_VALUES[verifier].inspect}"
334
353
  else
335
- if verifier.respond_to?(:verify)
336
- if verifier.respond_to?(:verify_signature)
337
- verifier
338
- else
339
- Kernel.warn("Warning: verifier without :verify_signature is deprecated")
340
- CompatibleVerifier.new(verifier)
341
- end
342
- else
343
- raise(
344
- ArgumentError,
345
- "Invalid argument to :verify_host_key (or deprecated " \
346
- ":paranoid): #{verifier.inspect}"
347
- )
354
+ raise ArgumentError, "Invalid argument to :verify_host_key: #{verifier.inspect}" unless verifier.respond_to?(:verify)
355
+
356
+ unless verifier.respond_to?(:verify_signature)
357
+ raise ArgumentError,
358
+ "verify_host_key verifier #{verifier.inspect} must implement verify_signature(&block): " \
359
+ "yield to the block, which checks the server's host key signature, and return its result"
348
360
  end
361
+
362
+ verifier
349
363
  end
350
364
  end
351
365
  end
@@ -125,7 +125,7 @@ module Net
125
125
  compressor.deflate(data, Zlib::SYNC_FLUSH)
126
126
  end
127
127
 
128
- # Deompresses the data. If no compression is in effect, this will just return
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
- # If we found any matches, check to see that the key type and
23
- # blob also match.
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
- # If a match was found, return true. Otherwise, raise an exception
34
- # indicating that the key was not recognized.
35
- process_cache_miss(host_keys, arguments, HostKeyMismatch, "does not match") unless found
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
- found
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}")
@@ -46,10 +46,10 @@ module Net
46
46
  end
47
47
 
48
48
  # The major component of this version of the Net::SSH library
49
- MAJOR = 7
49
+ MAJOR = 8
50
50
 
51
51
  # The minor component of this version of the Net::SSH library
52
- MINOR = 2
52
+ MINOR = 0
53
53
 
54
54
  # The tiny component of this version of the Net::SSH library
55
55
  TINY = 0
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 languages logger paranoid password port proxy
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
77
75
  ]
78
76
 
79
77
  # The standard means of starting a new SSH connection. When used with a
@@ -102,17 +100,54 @@ 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 ckeck IP address when connecting to remote host.
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 in a logger warning. 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 can't be combined with it, because the master
135
+ # serves them from its own agent or display only if it has ForwardAgent
136
+ # or ForwardX11 enabled itself, and rejects them otherwise. Keepalive and
137
+ # rekeying are left to the master. ssh_config's ControlMaster is
138
+ # ignored, only this option enables it. An invalid ControlPath raises
139
+ # ArgumentError. A missing or stale socket is logged at info level, a
140
+ # socket that can't be used (another owner, refused, wrong protocol,
141
+ # timed out) at warn level. Has no effect on Windows. Defaults to
142
+ # +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
@@ -141,11 +176,17 @@ module Net
141
176
  # Defaults to +300+ seconds.
142
177
  # * :keepalive_maxcount => the maximun number of keepalive packet miss allowed.
143
178
  # Defaults to 3
144
- # * :kex => the key exchange algorithm (or algorithms) to use
179
+ # * :kex => the key exchange algorithm (or algorithms) to use. Like
180
+ # KexAlgorithms, entries starting with +, - or ^ modify the default list.
181
+ # diffie-hellman-group14-sha1 is supported but not offered unless listed
182
+ # here (e.g. '+diffie-hellman-group14-sha1') or
183
+ # :append_all_supported_algorithms is set.
145
184
  # * :keys => an array of file names of private keys to use for publickey
146
185
  # and hostbased authentication
147
186
  # * :keycerts => an array of file names of key certificates to use
148
187
  # with publickey authentication
188
+ # * :keycert_data => an array of strings, which each element of the array
189
+ # being a key certificate to use with publickey authentication
149
190
  # * :key_data => an array of strings, with each element of the array being
150
191
  # a raw private key in PEM format.
151
192
  # * :keys_only => set to +true+ to use only private keys from +keys+ and
@@ -157,12 +198,24 @@ module Net
157
198
  # packet. Default is 0x8000 (32768 bytes). Increase to 0x10000 (65536 bytes)
158
199
  # for better performance if your SSH server supports it (most do).
159
200
  # * :max_win_size => maximum size we tell the other side that is supported for
160
- # the window.
201
+ # the window, i.e. how much the server may send on a channel before net-ssh
202
+ # reads it. Default is 0x200000 (2 MB), as in OpenSSH. Smaller values limit
203
+ # how much data is in flight; some servers (e.g. Mikrotik) truncate output
204
+ # when the window is small.
205
+ # * :minimum_dh_bits => the smallest group, in bits, accepted from the server
206
+ # in diffie-hellman-group-exchange key exchange. Defaults to 2048, as in
207
+ # OpenSSH; lower it (e.g. to 1024) only to reach legacy servers that offer
208
+ # nothing larger.
209
+ # * :no_delay => turns Nagle's algorithm off on the connection's socket
210
+ # (TCP_NODELAY), so a packet written while the previous one is still
211
+ # unacknowledged is sent right away instead of waiting for the server's
212
+ # ACK. OpenSSH does the same for every connection. Set to +false+ to keep
213
+ # Nagle's algorithm on. Has no effect when the socket comes from a
214
+ # ProxyCommand, whose command owns the TCP connection. Defaults to +true+.
161
215
  # * :non_interactive => set to true if your app is non interactive and prefers
162
216
  # authentication failure vs password prompt. Non-interactive applications
163
217
  # should set it to true to prefer failing a password/etc auth methods vs.
164
218
  # asking for password.
165
- # * :paranoid => deprecated alias for :verify_host_key
166
219
  # * :passphrase => the passphrase to use when loading a private key (default
167
220
  # is +nil+, for no passphrase)
168
221
  # * :password => the password to use to login
@@ -170,11 +223,13 @@ module Net
170
223
  # * :properties => a hash of key/value pairs to add to the new connection's
171
224
  # properties (see Net::SSH::Connection::Session#properties)
172
225
  # * :proxy => a proxy instance (see Proxy) to use when connecting
173
- # * :pubkey_algorithms => the public key authentication algorithms to use for
174
- # this connection. Valid values are 'rsa-sha2-256-cert-v01@openssh.com',
175
- # 'ssh-rsa-cert-v01@openssh.com', 'rsa-sha2-256', 'ssh-rsa'. Currently, this
176
- # option is only used for RSA public key authentication and ignored for other
177
- # types.
226
+ # * :pubkey_algorithms => the public key authentication algorithms to offer,
227
+ # in order of preference (e.g. %w[ssh-ed25519 rsa-sha2-512 rsa-sha2-256]).
228
+ # Keys whose type isn't listed are skipped. Like PubkeyAcceptedAlgorithms,
229
+ # entries starting with +, - or ^ modify the default list. ssh-rsa (SHA-1),
230
+ # ssh-rsa-cert-v01@openssh.com and ssh-dss are supported but not offered
231
+ # unless listed here (e.g. '+ssh-rsa') or :append_all_supported_algorithms
232
+ # is set.
178
233
  # * :rekey_blocks_limit => the max number of blocks to process before rekeying
179
234
  # * :rekey_limit => the max number of bytes to process before rekeying
180
235
  # * :rekey_packet_limit => the max number of packets to process before rekeying
@@ -186,7 +241,8 @@ module Net
186
241
  # * :user => the user name to log in as; this overrides the +user+
187
242
  # parameter, and is primarily only useful when provided via an SSH
188
243
  # configuration file.
189
- # * :remote_user => used for substitution into the '%r' part of a ProxyCommand
244
+ # * :remote_user => used for substitution into the '%r' part of a ProxyCommand;
245
+ # defaults to the user name the connection logs in as.
190
246
  # * :user_known_hosts_file => the location of the user known hosts file.
191
247
  # Set to an array to specify multiple user known hosts files.
192
248
  # Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2).
@@ -198,7 +254,9 @@ module Net
198
254
  # default. The symbols :debug, :info, :warn, :error, and :fatal are also
199
255
  # supported and are translated to the corresponding Logger constant.
200
256
  # * :append_all_supported_algorithms => set to +true+ to append all supported
201
- # algorithms by net-ssh. Was the default behaviour until 2.10
257
+ # algorithms by net-ssh, except the +none+ cipher and MAC, which must be
258
+ # named explicitly (e.g. encryption: '+none'). Was the default behaviour
259
+ # until 2.10
202
260
  # * :number_of_password_prompts => Number of prompts for the password
203
261
  # authentication method defaults to 3 set to 0 to disable prompt for
204
262
  # password auth method
@@ -213,21 +271,32 @@ module Net
213
271
  # * :accept_new_or_local_tunnel (insecure) ::Net::SSH::Verifiers::AcceptNewOrLocalTunnel
214
272
  # * :accept_new (insecure) ::Net::SSH::Verifiers::AcceptNew
215
273
  # * :always (secure) ::Net::SSH::Verifiers::Always
216
- # You can also provide an own Object which responds to +verify+. The argument
217
- # given to +verify+ is a hash consisting of the +:key+, the +:key_blob+,
218
- # the +:fingerprint+ and the +:session+. Returning true accepts the host key,
219
- # returning false declines it and closes the connection.
274
+ # You can also provide an own Object which responds to +verify+ and
275
+ # +verify_signature+. The argument given to +verify+ is a hash consisting
276
+ # of the +:key+, the +:key_blob+, the +:fingerprint+ and the +:session+.
277
+ # Returning true accepts the host key, returning false declines it and
278
+ # closes the connection. +verify_signature+ is given a block that checks
279
+ # the server's host key signature and must return the block's result.
220
280
  # * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256'
221
281
  # If +user+ parameter is nil it defaults to USER from ssh_config, or
222
282
  # local username
223
283
  def self.start(host, user = nil, options = {}, &block)
284
+ if options.key?(:paranoid)
285
+ raise ArgumentError, ":paranoid has been removed, use :verify_host_key (same values)"
286
+ end
287
+
224
288
  invalid_options = options.keys - VALID_OPTIONS
225
289
  if invalid_options.any?
226
290
  raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}"
227
291
  end
228
292
 
293
+ reject_nil_options(options)
294
+ Multiplex.validate_options!(options)
295
+ explicit_options = options.keys
296
+ host_arg = host
297
+
298
+ options = options.dup
229
299
  assign_defaults(options)
230
- _sanitize_options(options)
231
300
 
232
301
  options[:user] = user if user
233
302
  options = configuration_for(host, options.fetch(:config, true)).merge(options)
@@ -239,8 +308,6 @@ module Net
239
308
  options[:number_of_password_prompts] = 0
240
309
  end
241
310
 
242
- _support_deprecated_option_paranoid(options)
243
-
244
311
  if options[:verbose]
245
312
  options[:logger].level = case options[:verbose]
246
313
  when Integer then options[:verbose]
@@ -253,26 +320,35 @@ module Net
253
320
  end
254
321
  end
255
322
 
256
- transport = Transport::Session.new(host, options)
257
- auth = Authentication::Session.new(transport, options)
258
-
259
323
  user = options.fetch(:user, user) || Etc.getpwuid.name
260
- if auth.authenticate("ssh-connection", user, options[:password])
261
- connection = Connection::Session.new(transport, options)
262
- if block_given?
263
- begin
264
- yield connection
265
- ensure
266
- connection.close unless connection.closed?
267
- end
268
- else
269
- return connection
324
+ options[:remote_user] ||= user
325
+
326
+ connection = Multiplex.connect(host_arg, host, user, options, explicit: explicit_options) if options[:control_master]
327
+ connection ||= connect_directly(host, user, options)
328
+
329
+ if block_given?
330
+ begin
331
+ yield connection
332
+ ensure
333
+ connection.close unless connection.closed?
270
334
  end
271
335
  else
336
+ connection
337
+ end
338
+ end
339
+
340
+ def self.connect_directly(host, user, options)
341
+ transport = Transport::Session.new(host, options)
342
+ auth = Authentication::Session.new(transport, options)
343
+
344
+ unless auth.authenticate("ssh-connection", user, options[:password])
272
345
  transport.close
273
346
  raise AuthenticationFailed, "Authentication failed for user #{user}@#{host}"
274
347
  end
348
+
349
+ Connection::Session.new(transport, options)
275
350
  end
351
+ private_class_method :connect_directly
276
352
 
277
353
  # Returns a hash of the configuration options for the given host, as read
278
354
  # from the SSH configuration file(s). If +use_ssh_config+ is true (the
@@ -306,31 +382,35 @@ module Net
306
382
  end
307
383
  end
308
384
 
309
- def self._sanitize_options(options)
310
- invalid_option_values = [nil, [nil]]
311
- unless (options.values & invalid_option_values).empty?
312
- nil_options = options.select { |_k, v| invalid_option_values.include?(v) }.map(&:first)
313
- Kernel.warn "#{caller_locations(2, 1)[0]}: Passing nil, or [nil] to Net::SSH.start is deprecated for keys: #{nil_options.join(', ')}"
314
- end
315
- end
316
- private_class_method :_sanitize_options
385
+ NIL_ALLOWED_OPTIONS = %i[password passphrase logger password_prompt].freeze
317
386
 
318
- def self._support_deprecated_option_paranoid(options)
319
- if options.key?(:paranoid)
320
- Kernel.warn(
321
- ":paranoid is deprecated, please use :verify_host_key. Supported " \
322
- "values are exactly the same, only the name of the option has changed."
323
- )
324
- if options.key?(:verify_host_key)
325
- Kernel.warn(
326
- "Both :paranoid and :verify_host_key were specified. " \
327
- ":verify_host_key takes precedence, :paranoid will be ignored."
328
- )
329
- else
330
- options[:verify_host_key] = options.delete(:paranoid)
331
- end
332
- end
387
+ NIL_OPTION_HINTS = {
388
+ config: "config: false to skip ssh_config files",
389
+ proxy: "proxy: false to connect without a proxy",
390
+ keys: "keys: [] to use no key files",
391
+ use_agent: "use_agent: false to not use ssh-agent",
392
+ forward_agent: "forward_agent: false to not forward ssh-agent",
393
+ keepalive: "keepalive: false to send no keepalives",
394
+ compression: "compression: false to disable compression",
395
+ control_master: "control_master: false to not use a ControlMaster",
396
+ control_path: "control_path: 'none' to not use a ControlMaster socket",
397
+ send_env: "send_env: [] to send no environment variables",
398
+ set_env: "set_env: {} to set no environment variables"
399
+ }.freeze
400
+ private_constant :NIL_ALLOWED_OPTIONS, :NIL_OPTION_HINTS
401
+
402
+ def self.reject_nil_options(options)
403
+ nil_keys = options.select do |key, value|
404
+ !NIL_ALLOWED_OPTIONS.include?(key) && (value.nil? || (value.is_a?(Array) && value.include?(nil)))
405
+ end.keys
406
+ return if nil_keys.empty?
407
+
408
+ message = "nil is not allowed for #{nil_keys.map(&:inspect).join(', ')}; " \
409
+ "omit #{nil_keys.one? ? 'it' : 'them'} to use the default or ssh_config value"
410
+ hints = NIL_OPTION_HINTS.values_at(*nil_keys).compact
411
+ message += ", or pass #{hints.join(', or ')}" if hints.any?
412
+ raise ArgumentError, message
333
413
  end
334
- private_class_method :_support_deprecated_option_paranoid
414
+ private_class_method :reject_nil_options
335
415
  end
336
416
  end