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.
- 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 +43 -0
- data/.github/workflows/ci-with-docker.yml +20 -9
- data/.github/workflows/ci.yml +11 -20
- data/.github/workflows/rubocop.yml +3 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +4 -1
- data/CHANGES.txt +114 -0
- data/CONTRIBUTING.md +35 -0
- data/Dockerfile +7 -2
- data/Dockerfile.openssl3 +1 -1
- data/Dockerfile.zlib_ng +29 -0
- data/README.md +76 -57
- data/Rakefile +117 -30
- data/appveyor.yml +4 -16
- data/changelog.d/README.md +1 -0
- data/docker-compose.yml +2 -8
- data/lib/net/ssh/authentication/agent.rb +35 -11
- data/lib/net/ssh/authentication/ed25519.rb +141 -40
- data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
- data/lib/net/ssh/authentication/key_manager.rb +61 -18
- 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/pub_key_fingerprint.rb +1 -1
- data/lib/net/ssh/authentication/security_key.rb +71 -0
- data/lib/net/ssh/authentication/session.rb +34 -15
- data/lib/net/ssh/buffer.rb +6 -1
- data/lib/net/ssh/buffered_io.rb +41 -4
- data/lib/net/ssh/config.rb +30 -13
- data/lib/net/ssh/connection/channel.rb +49 -2
- data/lib/net/ssh/connection/session.rb +58 -8
- data/lib/net/ssh/errors.rb +24 -6
- data/lib/net/ssh/key_factory.rb +11 -4
- data/lib/net/ssh/known_hosts.rb +64 -11
- 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 +130 -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 +52 -4
- data/lib/net/ssh/service/forward.rb +38 -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 +60 -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 +2 -2
- data/lib/net/ssh.rb +142 -62
- data/net-ssh-public_cert.pem +19 -18
- data/net-ssh.gemspec +7 -8
- data/rakelib/changelog.rb +229 -0
- data.tar.gz.sig +0 -0
- metadata +86 -53
- metadata.gz.sig +1 -1
- data/Gemfile.noed25519 +0 -12
- data/Gemfile.norbnacl +0 -12
- 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
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
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
|
-
#
|
|
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,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 =
|
|
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
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
|
|
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
|
|
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
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
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
|
|
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
|
|
217
|
-
# given to +verify+ is a hash consisting
|
|
218
|
-
# the +:fingerprint+ and the +:session+.
|
|
219
|
-
# returning false declines it and
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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 :
|
|
414
|
+
private_class_method :reject_nil_options
|
|
335
415
|
end
|
|
336
416
|
end
|