net-ssh 7.3.6 → 8.1.2
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/CHANGES.txt +106 -5
- data/README.md +71 -57
- data/lib/net/ssh/authentication/agent.rb +35 -11
- data/lib/net/ssh/authentication/certificate.rb +2 -2
- data/lib/net/ssh/authentication/ed25519.rb +137 -44
- data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
- data/lib/net/ssh/authentication/key_manager.rb +22 -11
- data/lib/net/ssh/authentication/methods/abstract.rb +6 -8
- data/lib/net/ssh/authentication/methods/hostbased.rb +3 -3
- data/lib/net/ssh/authentication/methods/publickey.rb +52 -38
- data/lib/net/ssh/authentication/security_key.rb +71 -0
- data/lib/net/ssh/authentication/session.rb +30 -17
- data/lib/net/ssh/buffer.rb +8 -3
- data/lib/net/ssh/buffered_io.rb +40 -3
- data/lib/net/ssh/config.rb +123 -50
- data/lib/net/ssh/connection/channel.rb +49 -2
- data/lib/net/ssh/connection/session.rb +151 -13
- data/lib/net/ssh/errors.rb +34 -6
- data/lib/net/ssh/key_factory.rb +7 -1
- data/lib/net/ssh/known_hosts.rb +103 -38
- 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 +127 -0
- data/lib/net/ssh/prompt.rb +43 -5
- data/lib/net/ssh/proxy/https.rb +28 -4
- data/lib/net/ssh/proxy/jump.rb +61 -8
- data/lib/net/ssh/service/forward.rb +35 -24
- data/lib/net/ssh/test.rb +2 -1
- data/lib/net/ssh/transport/algorithms.rb +92 -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 +23 -1
- data/lib/net/ssh/transport/constants.rb +1 -0
- data/lib/net/ssh/transport/gcm_cipher.rb +27 -6
- 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 +12 -0
- data/lib/net/ssh/transport/session.rb +61 -45
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +3 -1
- data/lib/net/ssh/verifiers/always.rb +16 -4
- data/lib/net/ssh/verifiers/never.rb +2 -1
- data/lib/net/ssh/version.rb +3 -3
- data/lib/net/ssh.rb +164 -63
- data.tar.gz.sig +0 -0
- metadata +33 -61
- metadata.gz.sig +0 -0
- data/.dockerignore +0 -9
- data/.github/FUNDING.yml +0 -1
- data/.github/config/rubocop_linter_action.yml +0 -4
- data/.github/workflows/ci-with-docker.yml +0 -58
- data/.github/workflows/ci.yml +0 -99
- data/.github/workflows/rubocop.yml +0 -16
- data/.gitignore +0 -15
- data/.rubocop.yml +0 -22
- data/.rubocop_todo.yml +0 -1083
- data/DEVELOPMENT.md +0 -23
- data/Dockerfile +0 -32
- data/Dockerfile.openssl3 +0 -17
- data/Dockerfile.zlib_ng +0 -29
- data/Gemfile +0 -13
- data/Gemfile.noed25519 +0 -12
- data/Gemfile.norbnacl +0 -12
- data/ISSUE_TEMPLATE.md +0 -30
- data/Manifest +0 -132
- data/Rakefile +0 -196
- data/SECURITY.md +0 -4
- data/THANKS.txt +0 -110
- data/appveyor.yml +0 -58
- data/docker-compose.yml +0 -23
- data/net-ssh-public_cert.pem +0 -21
- data/net-ssh.gemspec +0 -48
- data/support/ssh_tunnel_bug.rb +0 -65
|
@@ -56,6 +56,11 @@ module Net
|
|
|
56
56
|
# The hash of options that were given to the object at initialization.
|
|
57
57
|
attr_reader :options
|
|
58
58
|
|
|
59
|
+
# The signature algorithms the server accepts for publickey
|
|
60
|
+
# authentication, from its RFC 8308 server-sig-algs extension, or nil if
|
|
61
|
+
# it did not send one.
|
|
62
|
+
attr_reader :server_sig_algs
|
|
63
|
+
|
|
59
64
|
# Instantiates a new transport layer abstraction. This will block until
|
|
60
65
|
# the initial key exchange completes, leaving you with a ready-to-use
|
|
61
66
|
# transport session.
|
|
@@ -66,6 +71,7 @@ module Net
|
|
|
66
71
|
@port = options[:port] || DEFAULT_PORT
|
|
67
72
|
@bind_address = options[:bind_address] || nil
|
|
68
73
|
@options = options
|
|
74
|
+
@host_key_verifier = select_host_key_verifier(options[:verify_host_key])
|
|
69
75
|
|
|
70
76
|
@socket =
|
|
71
77
|
if (factory = options[:proxy])
|
|
@@ -79,21 +85,24 @@ module Net
|
|
|
79
85
|
|
|
80
86
|
@socket.extend(PacketStream)
|
|
81
87
|
@socket.logger = @logger
|
|
88
|
+
set_no_delay unless options[:no_delay] == false
|
|
82
89
|
|
|
83
90
|
debug { "connection established" }
|
|
84
91
|
|
|
85
92
|
@queue = []
|
|
86
93
|
@queued_bytes = 0
|
|
87
94
|
|
|
88
|
-
@host_key_verifier = select_host_key_verifier(options[:verify_host_key])
|
|
89
|
-
|
|
90
95
|
@server_version = ServerVersion.new(socket, logger, options[:timeout])
|
|
91
96
|
|
|
92
97
|
@algorithms = Algorithms.new(self, options)
|
|
93
98
|
@algorithms.start
|
|
94
99
|
wait { algorithms.initialized? }
|
|
95
100
|
rescue Errno::ETIMEDOUT
|
|
101
|
+
@socket&.close
|
|
96
102
|
raise Net::SSH::ConnectionTimeout
|
|
103
|
+
rescue StandardError
|
|
104
|
+
@socket&.close
|
|
105
|
+
raise
|
|
97
106
|
end
|
|
98
107
|
|
|
99
108
|
def host_keys
|
|
@@ -103,6 +112,10 @@ module Net
|
|
|
103
112
|
end
|
|
104
113
|
end
|
|
105
114
|
|
|
115
|
+
def pubkey_algorithms
|
|
116
|
+
algorithms[:pubkey_algorithms]
|
|
117
|
+
end
|
|
118
|
+
|
|
106
119
|
# Returns the host (and possibly IP address) in a format compatible with
|
|
107
120
|
# SSH known-host files.
|
|
108
121
|
def host_as_string
|
|
@@ -211,12 +224,16 @@ module Net
|
|
|
211
224
|
when DEBUG
|
|
212
225
|
send(packet[:always_display] ? :fatal : :debug) { packet[:message] }
|
|
213
226
|
|
|
227
|
+
when EXT_INFO
|
|
228
|
+
accept_ext_info(packet)
|
|
229
|
+
|
|
214
230
|
when KEXINIT
|
|
215
231
|
algorithms.accept_kexinit(packet)
|
|
216
232
|
|
|
217
233
|
else
|
|
218
234
|
return packet if algorithms.allow?(packet)
|
|
219
|
-
|
|
235
|
+
# Algorithms#rekey! clears initialized?, so the session id is what marks the first key exchange as done.
|
|
236
|
+
raise Net::SSH::Exception, "unexpected packet type #{packet.type} during initial key exchange" unless algorithms.session_id
|
|
220
237
|
|
|
221
238
|
push(packet)
|
|
222
239
|
end
|
|
@@ -287,29 +304,45 @@ module Net
|
|
|
287
304
|
|
|
288
305
|
private
|
|
289
306
|
|
|
307
|
+
# Parses an RFC 8308 EXT_INFO packet, which servers send after the first
|
|
308
|
+
# NEWKEYS and possibly again just before USERAUTH_SUCCESS.
|
|
309
|
+
def accept_ext_info(packet)
|
|
310
|
+
count = packet.read_long or raise Net::SSH::Exception, "malformed EXT_INFO packet from server"
|
|
311
|
+
count.times do
|
|
312
|
+
name = packet.read_string
|
|
313
|
+
value = packet.read_string
|
|
314
|
+
raise Net::SSH::Exception, "malformed EXT_INFO packet from server" if name.nil? || value.nil?
|
|
315
|
+
|
|
316
|
+
debug { "EXT_INFO #{name}: #{value}" }
|
|
317
|
+
@server_sig_algs = value.split(",") if name == "server-sig-algs"
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
290
321
|
def shift_queue
|
|
291
322
|
packet = @queue.shift
|
|
292
323
|
@queued_bytes -= packet.length
|
|
293
324
|
packet
|
|
294
325
|
end
|
|
295
326
|
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
327
|
+
# Turns Nagle's algorithm off on the socket, so that a packet written
|
|
328
|
+
# while the previous one is still unacknowledged goes out right away
|
|
329
|
+
# instead of waiting for the server's ACK. A ProxyCommand hands the
|
|
330
|
+
# transport a pipe rather than a socket, and the command on its other
|
|
331
|
+
# end owns the TCP connection, so there is nothing to set in that case.
|
|
332
|
+
def set_no_delay
|
|
333
|
+
return unless socket.respond_to?(:setsockopt)
|
|
303
334
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def verify_signature(&block)
|
|
309
|
-
yield
|
|
310
|
-
end
|
|
335
|
+
debug { "setting TCP_NODELAY" }
|
|
336
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
311
337
|
end
|
|
312
338
|
|
|
339
|
+
REMOVED_VERIFY_HOST_KEY_VALUES = {
|
|
340
|
+
false => :never,
|
|
341
|
+
true => :accept_new_or_local_tunnel,
|
|
342
|
+
very: :accept_new,
|
|
343
|
+
secure: :always
|
|
344
|
+
}.freeze
|
|
345
|
+
|
|
313
346
|
# Instantiates a new host-key verification class, based on the value of
|
|
314
347
|
# the parameter.
|
|
315
348
|
#
|
|
@@ -323,46 +356,29 @@ module Net
|
|
|
323
356
|
#
|
|
324
357
|
# If the argument happens to respond to :verify and :verify_signature,
|
|
325
358
|
# it is returned directly. Otherwise, an exception is raised.
|
|
326
|
-
#
|
|
327
|
-
# Values false, true, and :very were deprecated in
|
|
328
|
-
# [#595](https://github.com/net-ssh/net-ssh/pull/595)
|
|
329
359
|
def select_host_key_verifier(verifier)
|
|
330
360
|
case verifier
|
|
331
|
-
when false
|
|
332
|
-
Kernel.warn('verify_host_key: false is deprecated, use :never')
|
|
333
|
-
Net::SSH::Verifiers::Never.new
|
|
334
361
|
when :never then
|
|
335
362
|
Net::SSH::Verifiers::Never.new
|
|
336
|
-
when true
|
|
337
|
-
Kernel.warn('verify_host_key: true is deprecated, use :accept_new_or_local_tunnel')
|
|
338
|
-
Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
|
|
339
363
|
when :accept_new_or_local_tunnel, nil then
|
|
340
364
|
Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
|
|
341
|
-
when :very
|
|
342
|
-
Kernel.warn('verify_host_key: :very is deprecated, use :accept_new')
|
|
343
|
-
Net::SSH::Verifiers::AcceptNew.new
|
|
344
365
|
when :accept_new then
|
|
345
366
|
Net::SSH::Verifiers::AcceptNew.new
|
|
346
|
-
when :secure then
|
|
347
|
-
Kernel.warn('verify_host_key: :secure is deprecated, use :always')
|
|
348
|
-
Net::SSH::Verifiers::Always.new
|
|
349
367
|
when :always then
|
|
350
368
|
Net::SSH::Verifiers::Always.new
|
|
369
|
+
when *REMOVED_VERIFY_HOST_KEY_VALUES.keys
|
|
370
|
+
raise ArgumentError,
|
|
371
|
+
"verify_host_key: #{verifier.inspect} has been removed, use #{REMOVED_VERIFY_HOST_KEY_VALUES[verifier].inspect}"
|
|
351
372
|
else
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
end
|
|
359
|
-
else
|
|
360
|
-
raise(
|
|
361
|
-
ArgumentError,
|
|
362
|
-
"Invalid argument to :verify_host_key (or deprecated " \
|
|
363
|
-
":paranoid): #{verifier.inspect}"
|
|
364
|
-
)
|
|
373
|
+
raise ArgumentError, "Invalid argument to :verify_host_key: #{verifier.inspect}" unless verifier.respond_to?(:verify)
|
|
374
|
+
|
|
375
|
+
unless verifier.respond_to?(:verify_signature)
|
|
376
|
+
raise ArgumentError,
|
|
377
|
+
"verify_host_key verifier #{verifier.inspect} must implement verify_signature(&block): " \
|
|
378
|
+
"yield to the block, which checks the server's host key signature, and return its result"
|
|
365
379
|
end
|
|
380
|
+
|
|
381
|
+
verifier
|
|
366
382
|
end
|
|
367
383
|
end
|
|
368
384
|
end
|
|
@@ -7,11 +7,13 @@ module Net
|
|
|
7
7
|
# verify a connection if the server is the localhost and the port is a
|
|
8
8
|
# nonstandard port number. Those two conditions will typically mean the
|
|
9
9
|
# connection is being tunnelled through a forwarded port, so the known-hosts
|
|
10
|
-
# file will not be helpful (in general).
|
|
10
|
+
# file will not be helpful (in general). A key marked @revoked is refused
|
|
11
|
+
# either way.
|
|
11
12
|
class AcceptNewOrLocalTunnel < AcceptNew
|
|
12
13
|
# Tries to determine if the connection is being tunnelled, and if so,
|
|
13
14
|
# returns true. Otherwise, performs the standard strict verification.
|
|
14
15
|
def verify(arguments)
|
|
16
|
+
refuse_revoked!(arguments)
|
|
15
17
|
return true if tunnelled?(arguments)
|
|
16
18
|
|
|
17
19
|
super
|
|
@@ -10,10 +10,13 @@ module Net
|
|
|
10
10
|
# (HostKeyUnknown). This is in contrast to the "Strict" class, which will
|
|
11
11
|
# silently add the key to your known_hosts file. If the server does appear at
|
|
12
12
|
# least once, but the key given does not match any known for the server, an
|
|
13
|
-
# exception will be raised (HostKeyMismatch).
|
|
13
|
+
# exception will be raised (HostKeyMismatch). A key marked @revoked in a
|
|
14
|
+
# host file is always refused (HostKeyRevoked).
|
|
14
15
|
# Otherwise, this returns true.
|
|
15
16
|
class Always
|
|
16
17
|
def verify(arguments)
|
|
18
|
+
refuse_revoked!(arguments)
|
|
19
|
+
|
|
17
20
|
host_keys = arguments[:session].host_keys
|
|
18
21
|
|
|
19
22
|
# We've never seen this host before, so raise an exception.
|
|
@@ -47,13 +50,22 @@ module Net
|
|
|
47
50
|
|
|
48
51
|
private
|
|
49
52
|
|
|
53
|
+
# Custom :known_hosts objects may return a host_keys collection with no
|
|
54
|
+
# revocation support.
|
|
55
|
+
def refuse_revoked!(arguments)
|
|
56
|
+
host_keys = arguments[:session].host_keys
|
|
57
|
+
return unless host_keys.respond_to?(:revoked?) && host_keys.revoked?(arguments[:key])
|
|
58
|
+
|
|
59
|
+
exception = HostKeyRevoked.new("fingerprint #{arguments[:fingerprint]} " +
|
|
60
|
+
"is revoked for #{host_keys.host.inspect}")
|
|
61
|
+
exception.data = arguments
|
|
62
|
+
raise exception
|
|
63
|
+
end
|
|
64
|
+
|
|
50
65
|
# Enforces the @cert-authority certificate constraints for entries that
|
|
51
66
|
# implement them (Net::SSH::HostKeyEntries::CertAuthority). Non-certificate
|
|
52
67
|
# entries do not respond to these predicates and are left untouched.
|
|
53
68
|
def verify_certificate!(entry, host_keys, arguments)
|
|
54
|
-
# Plain keys carry no certificate constraints, and respond_to? on this delegator prints a deprecation warning (#1026).
|
|
55
|
-
return if entry.is_a?(Net::SSH::HostKeyEntries::PubKey)
|
|
56
|
-
|
|
57
69
|
cert = arguments[:key]
|
|
58
70
|
|
|
59
71
|
if entry.respond_to?(:matches_validity?) && !entry.matches_validity?(cert)
|
|
@@ -3,7 +3,8 @@ module Net
|
|
|
3
3
|
module Verifiers
|
|
4
4
|
# This host key verifier simply allows every key it sees, without
|
|
5
5
|
# any verification. This is simple, but very insecure because it
|
|
6
|
-
# exposes you to MiTM attacks.
|
|
6
|
+
# exposes you to MiTM attacks. It does not read the known hosts files,
|
|
7
|
+
# so it accepts keys marked @revoked as well.
|
|
7
8
|
class Never
|
|
8
9
|
# Returns true.
|
|
9
10
|
def verify(arguments)
|
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 = 1
|
|
53
53
|
|
|
54
54
|
# The tiny component of this version of the Net::SSH library
|
|
55
|
-
TINY =
|
|
55
|
+
TINY = 2
|
|
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
|
@@ -8,6 +8,7 @@ require 'net/ssh/loggable'
|
|
|
8
8
|
require 'net/ssh/transport/session'
|
|
9
9
|
require 'net/ssh/authentication/session'
|
|
10
10
|
require 'net/ssh/connection/session'
|
|
11
|
+
require 'net/ssh/multiplex'
|
|
11
12
|
require 'net/ssh/prompt'
|
|
12
13
|
|
|
13
14
|
module Net
|
|
@@ -62,14 +63,15 @@ module Net
|
|
|
62
63
|
auth_methods bind_address compression compression_level config
|
|
63
64
|
encryption forward_agent hmac host_key identity_agent remote_user
|
|
64
65
|
keepalive keepalive_interval keepalive_maxcount kex keys key_data
|
|
65
|
-
keycerts keycert_data languages logger
|
|
66
|
+
keycerts keycert_data languages logger password port proxy
|
|
66
67
|
rekey_blocks_limit rekey_limit rekey_packet_limit timeout verbose
|
|
67
68
|
known_hosts global_known_hosts_file user_known_hosts_file host_key_alias
|
|
68
69
|
host_name user properties passphrase keys_only max_pkt_size
|
|
69
70
|
max_win_size send_env set_env use_agent number_of_password_prompts
|
|
70
71
|
append_all_supported_algorithms non_interactive password_prompt
|
|
71
|
-
agent_socket_factory minimum_dh_bits verify_host_key
|
|
72
|
-
fingerprint_hash check_host_ip pubkey_algorithms
|
|
72
|
+
prompt_without_tty agent_socket_factory minimum_dh_bits verify_host_key
|
|
73
|
+
fingerprint_hash check_host_ip pubkey_algorithms no_delay
|
|
74
|
+
control_master control_path preopen_channels
|
|
73
75
|
]
|
|
74
76
|
|
|
75
77
|
# The standard means of starting a new SSH connection. When used with a
|
|
@@ -98,7 +100,10 @@ module Net
|
|
|
98
100
|
# ssh.loop
|
|
99
101
|
# end
|
|
100
102
|
#
|
|
101
|
-
# 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.
|
|
102
107
|
#
|
|
103
108
|
# * :auth_methods => an array of authentication methods to try
|
|
104
109
|
# * :bind_address => the IP address on the connecting machine to use in
|
|
@@ -109,13 +114,48 @@ module Net
|
|
|
109
114
|
# * :compression => the compression algorithm to use, or +true+ to use
|
|
110
115
|
# whatever is supported.
|
|
111
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.
|
|
112
151
|
# * :config => set to +true+ to load the default OpenSSH config files
|
|
113
152
|
# (~/.ssh/config, /etc/ssh_config), or to +false+ to not load them, or to
|
|
114
153
|
# a file-name (or array of file-names) to load those specific configuration
|
|
115
154
|
# files. Defaults to +true+.
|
|
116
155
|
# * :encryption => the encryption cipher (or ciphers) to use
|
|
117
156
|
# * :forward_agent => set to true if you want the SSH agent connection to
|
|
118
|
-
# be forwarded
|
|
157
|
+
# be forwarded. Ignored for a session through a ControlMaster (see
|
|
158
|
+
# :control_master).
|
|
119
159
|
# * :known_hosts => a custom object holding known hosts records.
|
|
120
160
|
# It must implement #search_for and `add` in a similiar manner as KnownHosts.
|
|
121
161
|
# * :global_known_hosts_file => the location of the global known hosts
|
|
@@ -137,7 +177,11 @@ module Net
|
|
|
137
177
|
# Defaults to +300+ seconds.
|
|
138
178
|
# * :keepalive_maxcount => the maximun number of keepalive packet miss allowed.
|
|
139
179
|
# Defaults to 3
|
|
140
|
-
# * :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.
|
|
141
185
|
# * :keys => an array of file names of private keys to use for publickey
|
|
142
186
|
# and hostbased authentication
|
|
143
187
|
# * :keycerts => an array of file names of key certificates to use
|
|
@@ -155,24 +199,51 @@ module Net
|
|
|
155
199
|
# packet. Default is 0x8000 (32768 bytes). Increase to 0x10000 (65536 bytes)
|
|
156
200
|
# for better performance if your SSH server supports it (most do).
|
|
157
201
|
# * :max_win_size => maximum size we tell the other side that is supported for
|
|
158
|
-
# 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+.
|
|
159
216
|
# * :non_interactive => set to true if your app is non interactive and prefers
|
|
160
217
|
# authentication failure vs password prompt. Non-interactive applications
|
|
161
218
|
# should set it to true to prefer failing a password/etc auth methods vs.
|
|
162
|
-
# asking for password.
|
|
163
|
-
#
|
|
219
|
+
# asking for password. Like OpenSSH, Net::SSH never prompts when $stdin is
|
|
220
|
+
# not a TTY (e.g. in a background job), so it defaults to +true+ then,
|
|
221
|
+
# unless :password_prompt or :prompt_without_tty is given or an
|
|
222
|
+
# SSH_ASKPASS program can be used (see Net::SSH::Prompt.askpass).
|
|
164
223
|
# * :passphrase => the passphrase to use when loading a private key (default
|
|
165
224
|
# is +nil+, for no passphrase)
|
|
166
225
|
# * :password => the password to use to login
|
|
167
226
|
# * :port => the port to use when connecting to the remote host
|
|
227
|
+
# * :preopen_channels => set to +true+ to keep one spare "session" channel
|
|
228
|
+
# open, so that Net::SSH::Connection::Session#open_channel can hand it
|
|
229
|
+
# out at once and a command that waits for the previous one to finish
|
|
230
|
+
# costs one round trip instead of two. The spare counts against the
|
|
231
|
+
# server's MaxSessions; if the server refuses it, pre-opening stops for
|
|
232
|
+
# the session. Defaults to +false+.
|
|
233
|
+
# * :prompt_without_tty => set to +true+ to prompt for passwords, key
|
|
234
|
+
# passphrases and keyboard-interactive responses on $stdout/$stdin even
|
|
235
|
+
# when $stdin is not a TTY, e.g. to read a password piped into the
|
|
236
|
+
# process. Defaults to +false+.
|
|
168
237
|
# * :properties => a hash of key/value pairs to add to the new connection's
|
|
169
238
|
# properties (see Net::SSH::Connection::Session#properties)
|
|
170
239
|
# * :proxy => a proxy instance (see Proxy) to use when connecting
|
|
171
|
-
# * :pubkey_algorithms => the public key authentication algorithms to
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
240
|
+
# * :pubkey_algorithms => the public key authentication algorithms to offer,
|
|
241
|
+
# in order of preference (e.g. %w[ssh-ed25519 rsa-sha2-512 rsa-sha2-256]).
|
|
242
|
+
# Keys whose type isn't listed are skipped. Like PubkeyAcceptedAlgorithms,
|
|
243
|
+
# entries starting with +, - or ^ modify the default list. ssh-rsa (SHA-1),
|
|
244
|
+
# ssh-rsa-cert-v01@openssh.com and ssh-dss are supported but not offered
|
|
245
|
+
# unless listed here (e.g. '+ssh-rsa') or :append_all_supported_algorithms
|
|
246
|
+
# is set.
|
|
176
247
|
# * :rekey_blocks_limit => the max number of blocks to process before rekeying
|
|
177
248
|
# * :rekey_limit => the max number of bytes to process before rekeying
|
|
178
249
|
# * :rekey_packet_limit => the max number of packets to process before rekeying
|
|
@@ -184,7 +255,8 @@ module Net
|
|
|
184
255
|
# * :user => the user name to log in as; this overrides the +user+
|
|
185
256
|
# parameter, and is primarily only useful when provided via an SSH
|
|
186
257
|
# configuration file.
|
|
187
|
-
# * :remote_user => used for substitution into the '%r' part of a ProxyCommand
|
|
258
|
+
# * :remote_user => used for substitution into the '%r' part of a ProxyCommand;
|
|
259
|
+
# defaults to the user name the connection logs in as.
|
|
188
260
|
# * :user_known_hosts_file => the location of the user known hosts file.
|
|
189
261
|
# Set to an array to specify multiple user known hosts files.
|
|
190
262
|
# Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2).
|
|
@@ -196,7 +268,9 @@ module Net
|
|
|
196
268
|
# default. The symbols :debug, :info, :warn, :error, and :fatal are also
|
|
197
269
|
# supported and are translated to the corresponding Logger constant.
|
|
198
270
|
# * :append_all_supported_algorithms => set to +true+ to append all supported
|
|
199
|
-
# algorithms by net-ssh
|
|
271
|
+
# algorithms by net-ssh, except the +none+ cipher and MAC, which must be
|
|
272
|
+
# named explicitly (e.g. encryption: '+none'). Was the default behaviour
|
|
273
|
+
# until 2.10
|
|
200
274
|
# * :number_of_password_prompts => Number of prompts for the password
|
|
201
275
|
# authentication method defaults to 3 set to 0 to disable prompt for
|
|
202
276
|
# password auth method
|
|
@@ -211,24 +285,35 @@ module Net
|
|
|
211
285
|
# * :accept_new_or_local_tunnel (insecure) ::Net::SSH::Verifiers::AcceptNewOrLocalTunnel
|
|
212
286
|
# * :accept_new (insecure) ::Net::SSH::Verifiers::AcceptNew
|
|
213
287
|
# * :always (secure) ::Net::SSH::Verifiers::Always
|
|
214
|
-
# You can also provide an own Object which responds to +verify
|
|
215
|
-
# given to +verify+ is a hash consisting
|
|
216
|
-
# the +:fingerprint+ and the +:session+.
|
|
217
|
-
# returning false declines it and
|
|
288
|
+
# You can also provide an own Object which responds to +verify+ and
|
|
289
|
+
# +verify_signature+. The argument given to +verify+ is a hash consisting
|
|
290
|
+
# of the +:key+, the +:key_blob+, the +:fingerprint+ and the +:session+.
|
|
291
|
+
# Returning true accepts the host key, returning false declines it and
|
|
292
|
+
# closes the connection. +verify_signature+ is given a block that checks
|
|
293
|
+
# the server's host key signature and must return the block's result.
|
|
218
294
|
# * :fingerprint_hash => 'MD5' or 'SHA256', defaults to 'SHA256'
|
|
219
295
|
# If +user+ parameter is nil it defaults to USER from ssh_config, or
|
|
220
296
|
# local username
|
|
221
297
|
def self.start(host, user = nil, options = {}, &block)
|
|
298
|
+
if options.key?(:paranoid)
|
|
299
|
+
raise ArgumentError, ":paranoid has been removed, use :verify_host_key (same values)"
|
|
300
|
+
end
|
|
301
|
+
|
|
222
302
|
invalid_options = options.keys - VALID_OPTIONS
|
|
223
303
|
if invalid_options.any?
|
|
224
304
|
raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}"
|
|
225
305
|
end
|
|
226
306
|
|
|
307
|
+
reject_nil_options(options)
|
|
308
|
+
Multiplex.validate_options!(options)
|
|
309
|
+
explicit_options = options.keys
|
|
310
|
+
host_arg = host
|
|
311
|
+
|
|
312
|
+
options = options.dup
|
|
227
313
|
assign_defaults(options)
|
|
228
|
-
_sanitize_options(options)
|
|
229
314
|
|
|
230
315
|
options[:user] = user if user
|
|
231
|
-
options = configuration_for(host, options.fetch(:config, true)).merge(options)
|
|
316
|
+
options = configuration_for(host, options.fetch(:config, true), user: options[:user]).merge(options)
|
|
232
317
|
host = options.fetch(:host_name, host)
|
|
233
318
|
|
|
234
319
|
options[:check_host_ip] = true unless options.key?(:check_host_ip)
|
|
@@ -237,8 +322,6 @@ module Net
|
|
|
237
322
|
options[:number_of_password_prompts] = 0
|
|
238
323
|
end
|
|
239
324
|
|
|
240
|
-
_support_deprecated_option_paranoid(options)
|
|
241
|
-
|
|
242
325
|
if options[:verbose]
|
|
243
326
|
options[:logger].level = case options[:verbose]
|
|
244
327
|
when Integer then options[:verbose]
|
|
@@ -251,26 +334,35 @@ module Net
|
|
|
251
334
|
end
|
|
252
335
|
end
|
|
253
336
|
|
|
254
|
-
transport = Transport::Session.new(host, options)
|
|
255
|
-
auth = Authentication::Session.new(transport, options)
|
|
256
|
-
|
|
257
337
|
user = options.fetch(:user, user) || Etc.getpwuid.name
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
338
|
+
options[:remote_user] ||= user
|
|
339
|
+
|
|
340
|
+
connection = Multiplex.connect(host_arg, host, user, options, explicit: explicit_options) if options[:control_master]
|
|
341
|
+
connection ||= connect_directly(host, user, options)
|
|
342
|
+
|
|
343
|
+
if block_given?
|
|
344
|
+
begin
|
|
345
|
+
yield connection
|
|
346
|
+
ensure
|
|
347
|
+
connection.close unless connection.closed?
|
|
268
348
|
end
|
|
269
349
|
else
|
|
350
|
+
connection
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def self.connect_directly(host, user, options)
|
|
355
|
+
transport = Transport::Session.new(host, options)
|
|
356
|
+
auth = Authentication::Session.new(transport, options)
|
|
357
|
+
|
|
358
|
+
unless auth.authenticate("ssh-connection", user, options[:password])
|
|
270
359
|
transport.close
|
|
271
360
|
raise AuthenticationFailed, "Authentication failed for user #{user}@#{host}"
|
|
272
361
|
end
|
|
362
|
+
|
|
363
|
+
Connection::Session.new(transport, options)
|
|
273
364
|
end
|
|
365
|
+
private_class_method :connect_directly
|
|
274
366
|
|
|
275
367
|
# Returns a hash of the configuration options for the given host, as read
|
|
276
368
|
# from the SSH configuration file(s). If +use_ssh_config+ is true (the
|
|
@@ -280,15 +372,17 @@ module Net
|
|
|
280
372
|
# be a file name (or array of file names) of SSH configuration file(s)
|
|
281
373
|
# to read.
|
|
282
374
|
#
|
|
375
|
+
# +user+ is the explicitly requested remote user, matched by <tt>Match User</tt>.
|
|
376
|
+
#
|
|
283
377
|
# See Net::SSH::Config for the full description of all supported options.
|
|
284
|
-
def self.configuration_for(host, use_ssh_config)
|
|
378
|
+
def self.configuration_for(host, use_ssh_config, user: nil)
|
|
285
379
|
files = case use_ssh_config
|
|
286
380
|
when true then Net::SSH::Config.expandable_default_files
|
|
287
381
|
when false, nil then return {}
|
|
288
382
|
else Array(use_ssh_config)
|
|
289
383
|
end
|
|
290
384
|
|
|
291
|
-
Net::SSH::Config.for(host, files)
|
|
385
|
+
Net::SSH::Config.for(host, files, user: user)
|
|
292
386
|
end
|
|
293
387
|
|
|
294
388
|
def self.assign_defaults(options)
|
|
@@ -297,6 +391,9 @@ module Net
|
|
|
297
391
|
options[:logger].level = Logger::FATAL
|
|
298
392
|
end
|
|
299
393
|
|
|
394
|
+
unless options[:password_prompt] || options[:prompt_without_tty] || Prompt.can_ask?
|
|
395
|
+
options[:non_interactive] = true
|
|
396
|
+
end
|
|
300
397
|
options[:password_prompt] ||= Prompt.default(options)
|
|
301
398
|
|
|
302
399
|
%i[password passphrase].each do |key|
|
|
@@ -304,31 +401,35 @@ module Net
|
|
|
304
401
|
end
|
|
305
402
|
end
|
|
306
403
|
|
|
307
|
-
|
|
308
|
-
invalid_option_values = [nil, [nil]]
|
|
309
|
-
unless (options.values & invalid_option_values).empty?
|
|
310
|
-
nil_options = options.select { |_k, v| invalid_option_values.include?(v) }.map(&:first)
|
|
311
|
-
Kernel.warn "#{caller_locations(2, 1)[0]}: Passing nil, or [nil] to Net::SSH.start is deprecated for keys: #{nil_options.join(', ')}"
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
private_class_method :_sanitize_options
|
|
404
|
+
NIL_ALLOWED_OPTIONS = %i[password passphrase logger password_prompt].freeze
|
|
315
405
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
406
|
+
NIL_OPTION_HINTS = {
|
|
407
|
+
config: "config: false to skip ssh_config files",
|
|
408
|
+
proxy: "proxy: false to connect without a proxy",
|
|
409
|
+
keys: "keys: [] to use no key files",
|
|
410
|
+
use_agent: "use_agent: false to not use ssh-agent",
|
|
411
|
+
forward_agent: "forward_agent: false to not forward ssh-agent",
|
|
412
|
+
keepalive: "keepalive: false to send no keepalives",
|
|
413
|
+
compression: "compression: false to disable compression",
|
|
414
|
+
control_master: "control_master: false to not use a ControlMaster",
|
|
415
|
+
control_path: "control_path: 'none' to not use a ControlMaster socket",
|
|
416
|
+
send_env: "send_env: [] to send no environment variables",
|
|
417
|
+
set_env: "set_env: {} to set no environment variables"
|
|
418
|
+
}.freeze
|
|
419
|
+
private_constant :NIL_ALLOWED_OPTIONS, :NIL_OPTION_HINTS
|
|
420
|
+
|
|
421
|
+
def self.reject_nil_options(options)
|
|
422
|
+
nil_keys = options.select do |key, value|
|
|
423
|
+
!NIL_ALLOWED_OPTIONS.include?(key) && (value.nil? || (value.is_a?(Array) && value.include?(nil)))
|
|
424
|
+
end.keys
|
|
425
|
+
return if nil_keys.empty?
|
|
426
|
+
|
|
427
|
+
message = "nil is not allowed for #{nil_keys.map(&:inspect).join(', ')}; " \
|
|
428
|
+
"omit #{nil_keys.one? ? 'it' : 'them'} to use the default or ssh_config value"
|
|
429
|
+
hints = NIL_OPTION_HINTS.values_at(*nil_keys).compact
|
|
430
|
+
message += ", or pass #{hints.join(', or ')}" if hints.any?
|
|
431
|
+
raise ArgumentError, message
|
|
331
432
|
end
|
|
332
|
-
private_class_method :
|
|
433
|
+
private_class_method :reject_nil_options
|
|
333
434
|
end
|
|
334
435
|
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|