net-ssh 2.7.0 → 7.3.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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.dockerignore +6 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/config/rubocop_linter_action.yml +4 -0
- data/.github/workflows/ci-with-docker.yml +44 -0
- data/.github/workflows/ci.yml +94 -0
- data/.github/workflows/rubocop.yml +16 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +22 -0
- data/.rubocop_todo.yml +1081 -0
- data/CHANGES.txt +387 -0
- data/DEVELOPMENT.md +23 -0
- data/Dockerfile +29 -0
- data/Dockerfile.openssl3 +17 -0
- data/Gemfile +13 -0
- data/Gemfile.noed25519 +12 -0
- data/Gemfile.norbnacl +12 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/Manifest +4 -5
- data/README.md +303 -0
- data/Rakefile +174 -40
- data/SECURITY.md +4 -0
- data/THANKS.txt +25 -0
- data/appveyor.yml +58 -0
- data/docker-compose.yml +25 -0
- data/lib/net/ssh/authentication/agent.rb +279 -18
- data/lib/net/ssh/authentication/certificate.rb +183 -0
- data/lib/net/ssh/authentication/constants.rb +17 -15
- data/lib/net/ssh/authentication/ed25519.rb +184 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
- data/lib/net/ssh/authentication/key_manager.rb +125 -54
- data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
- data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +19 -12
- data/lib/net/ssh/authentication/methods/none.rb +16 -19
- data/lib/net/ssh/authentication/methods/password.rb +56 -19
- data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
- data/lib/net/ssh/authentication/pageant.rb +483 -246
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +138 -120
- data/lib/net/ssh/buffer.rb +399 -300
- data/lib/net/ssh/buffered_io.rb +154 -150
- data/lib/net/ssh/config.rb +361 -166
- data/lib/net/ssh/connection/channel.rb +640 -596
- data/lib/net/ssh/connection/constants.rb +29 -29
- data/lib/net/ssh/connection/event_loop.rb +123 -0
- data/lib/net/ssh/connection/keepalive.rb +59 -0
- data/lib/net/ssh/connection/session.rb +628 -548
- data/lib/net/ssh/connection/term.rb +125 -123
- data/lib/net/ssh/errors.rb +101 -95
- data/lib/net/ssh/key_factory.rb +198 -100
- data/lib/net/ssh/known_hosts.rb +221 -98
- data/lib/net/ssh/loggable.rb +50 -49
- data/lib/net/ssh/packet.rb +83 -79
- data/lib/net/ssh/prompt.rb +50 -81
- data/lib/net/ssh/proxy/command.rb +108 -60
- data/lib/net/ssh/proxy/errors.rb +12 -10
- data/lib/net/ssh/proxy/http.rb +82 -78
- data/lib/net/ssh/proxy/https.rb +50 -0
- data/lib/net/ssh/proxy/jump.rb +54 -0
- data/lib/net/ssh/proxy/socks4.rb +5 -8
- data/lib/net/ssh/proxy/socks5.rb +18 -20
- data/lib/net/ssh/service/forward.rb +383 -255
- data/lib/net/ssh/test/channel.rb +145 -136
- data/lib/net/ssh/test/extensions.rb +131 -110
- data/lib/net/ssh/test/kex.rb +34 -32
- data/lib/net/ssh/test/local_packet.rb +46 -44
- data/lib/net/ssh/test/packet.rb +89 -70
- data/lib/net/ssh/test/remote_packet.rb +32 -30
- data/lib/net/ssh/test/script.rb +156 -142
- data/lib/net/ssh/test/socket.rb +49 -48
- data/lib/net/ssh/test.rb +82 -77
- 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 +472 -348
- data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
- data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
- data/lib/net/ssh/transport/cipher_factory.rb +124 -100
- data/lib/net/ssh/transport/constants.rb +32 -24
- data/lib/net/ssh/transport/ctr.rb +42 -22
- data/lib/net/ssh/transport/gcm_cipher.rb +207 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +97 -63
- data/lib/net/ssh/transport/hmac/md5.rb +0 -2
- data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/none.rb +0 -2
- data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +14 -12
- data/lib/net/ssh/transport/identity_cipher.rb +54 -44
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
- data/lib/net/ssh/transport/kex.rb +15 -12
- data/lib/net/ssh/transport/key_expander.rb +24 -20
- data/lib/net/ssh/transport/openssl.rb +161 -124
- data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
- data/lib/net/ssh/transport/packet_stream.rb +246 -183
- data/lib/net/ssh/transport/server_version.rb +57 -51
- data/lib/net/ssh/transport/session.rb +307 -235
- data/lib/net/ssh/transport/state.rb +178 -176
- data/lib/net/ssh/verifiers/accept_new.rb +33 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
- data/lib/net/ssh/verifiers/always.rb +58 -0
- data/lib/net/ssh/verifiers/never.rb +19 -0
- data/lib/net/ssh/version.rb +57 -51
- data/lib/net/ssh.rb +140 -40
- data/net-ssh-public_cert.pem +21 -0
- data/net-ssh.gemspec +39 -184
- data/support/ssh_tunnel_bug.rb +5 -5
- data.tar.gz.sig +0 -0
- metadata +205 -99
- metadata.gz.sig +0 -0
- data/README.rdoc +0 -219
- data/Rudyfile +0 -96
- data/gem-public_cert.pem +0 -20
- data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
- data/lib/net/ssh/authentication/agent/socket.rb +0 -170
- data/lib/net/ssh/ruby_compat.rb +0 -51
- data/lib/net/ssh/verifiers/lenient.rb +0 -30
- data/lib/net/ssh/verifiers/null.rb +0 -12
- data/lib/net/ssh/verifiers/secure.rb +0 -54
- data/lib/net/ssh/verifiers/strict.rb +0 -24
- data/setup.rb +0 -1585
- data/support/arcfour_check.rb +0 -20
- data/test/README.txt +0 -47
- data/test/authentication/methods/common.rb +0 -28
- data/test/authentication/methods/test_abstract.rb +0 -51
- data/test/authentication/methods/test_hostbased.rb +0 -114
- data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
- data/test/authentication/methods/test_none.rb +0 -41
- data/test/authentication/methods/test_password.rb +0 -52
- data/test/authentication/methods/test_publickey.rb +0 -148
- data/test/authentication/test_agent.rb +0 -205
- data/test/authentication/test_key_manager.rb +0 -218
- data/test/authentication/test_session.rb +0 -108
- data/test/common.rb +0 -108
- data/test/configs/eqsign +0 -3
- data/test/configs/exact_match +0 -8
- data/test/configs/host_plus +0 -10
- data/test/configs/multihost +0 -4
- data/test/configs/nohost +0 -19
- data/test/configs/numeric_host +0 -4
- data/test/configs/send_env +0 -2
- data/test/configs/substitutes +0 -8
- data/test/configs/wild_cards +0 -14
- data/test/connection/test_channel.rb +0 -467
- data/test/connection/test_session.rb +0 -526
- data/test/known_hosts/github +0 -1
- data/test/manual/test_forward.rb +0 -223
- data/test/start/test_options.rb +0 -36
- data/test/start/test_transport.rb +0 -28
- data/test/test_all.rb +0 -11
- data/test/test_buffer.rb +0 -433
- data/test/test_buffered_io.rb +0 -63
- data/test/test_config.rb +0 -151
- data/test/test_key_factory.rb +0 -173
- data/test/test_known_hosts.rb +0 -13
- data/test/transport/hmac/test_md5.rb +0 -41
- data/test/transport/hmac/test_md5_96.rb +0 -27
- data/test/transport/hmac/test_none.rb +0 -34
- data/test/transport/hmac/test_ripemd160.rb +0 -36
- data/test/transport/hmac/test_sha1.rb +0 -36
- data/test/transport/hmac/test_sha1_96.rb +0 -27
- data/test/transport/hmac/test_sha2_256.rb +0 -37
- data/test/transport/hmac/test_sha2_256_96.rb +0 -27
- data/test/transport/hmac/test_sha2_512.rb +0 -37
- data/test/transport/hmac/test_sha2_512_96.rb +0 -27
- data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
- data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
- data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
- data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
- data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
- data/test/transport/test_algorithms.rb +0 -330
- data/test/transport/test_cipher_factory.rb +0 -443
- data/test/transport/test_hmac.rb +0 -34
- data/test/transport/test_identity_cipher.rb +0 -40
- data/test/transport/test_packet_stream.rb +0 -1755
- data/test/transport/test_server_version.rb +0 -78
- data/test/transport/test_session.rb +0 -319
- data/test/transport/test_state.rb +0 -181
data/lib/net/ssh/prompt.rb
CHANGED
@@ -1,93 +1,62 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# library will be used to disable keystroke echoing for the duration of
|
26
|
-
# this method.
|
27
|
-
def prompt(prompt, echo=true)
|
28
|
-
$stdout.print(prompt)
|
29
|
-
$stdout.flush
|
30
|
-
|
31
|
-
set_echo(false) unless echo
|
32
|
-
$stdin.gets.chomp
|
33
|
-
ensure
|
34
|
-
if !echo
|
35
|
-
set_echo(true)
|
36
|
-
$stdout.puts
|
37
|
-
end
|
1
|
+
require 'io/console'
|
2
|
+
|
3
|
+
module Net
|
4
|
+
module SSH
|
5
|
+
# Default prompt implementation, called for asking password from user.
|
6
|
+
# It will never be instantiated directly, but will instead be created for
|
7
|
+
# you automatically.
|
8
|
+
#
|
9
|
+
# A custom prompt objects can implement caching, or different UI. The prompt
|
10
|
+
# object should implemnted a start method, which should return something implementing
|
11
|
+
# ask and success. Net::SSH uses it like:
|
12
|
+
#
|
13
|
+
# prompter = options[:password_prompt].start({type:'password'})
|
14
|
+
# while !ok && max_retries < 3
|
15
|
+
# user = prompter.ask("user: ", true)
|
16
|
+
# password = prompter.ask("password: ", false)
|
17
|
+
# ok = send(user, password)
|
18
|
+
# prompter.sucess if ok
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
class Prompt
|
22
|
+
# factory
|
23
|
+
def self.default(options = {})
|
24
|
+
@default ||= new(options)
|
38
25
|
end
|
39
26
|
|
40
|
-
|
27
|
+
def initialize(options = {}); end
|
41
28
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
if
|
47
|
-
|
48
|
-
|
49
|
-
term.c_lflag &= ~::Termios::ECHO
|
29
|
+
# default prompt object implementation. More sophisticated implemenetations
|
30
|
+
# might implement caching.
|
31
|
+
class Prompter
|
32
|
+
def initialize(info)
|
33
|
+
if info[:type] == 'keyboard-interactive'
|
34
|
+
$stdout.puts(info[:name]) unless info[:name].empty?
|
35
|
+
$stdout.puts(info[:instruction]) unless info[:instruction].empty?
|
50
36
|
end
|
51
|
-
|
52
|
-
::Termios.setattr($stdin, ::Termios::TCSANOW, term)
|
53
37
|
end
|
54
|
-
end
|
55
38
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
def prompt(prompt, echo=true)
|
65
|
-
@seen_warning ||= false
|
66
|
-
if !echo && !@seen_warning
|
67
|
-
$stderr.puts "Text will be echoed in the clear. Please install the HighLine or Termios libraries to suppress echoed text."
|
68
|
-
@seen_warning = true
|
39
|
+
# ask input from user, a prompter might ask for multiple inputs
|
40
|
+
# (like user and password) in a single session.
|
41
|
+
def ask(prompt, echo = true)
|
42
|
+
$stdout.print(prompt)
|
43
|
+
$stdout.flush
|
44
|
+
ret = $stdin.noecho(&:gets).chomp
|
45
|
+
$stdout.print("\n")
|
46
|
+
ret
|
69
47
|
end
|
70
48
|
|
71
|
-
|
72
|
-
|
73
|
-
|
49
|
+
# success method will be called when the password was accepted
|
50
|
+
# It's a good time to save password asked to a cache.
|
51
|
+
def success; end
|
74
52
|
end
|
75
|
-
end
|
76
|
-
end
|
77
53
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
PromptMethods::Highline
|
84
|
-
rescue LoadError
|
85
|
-
begin
|
86
|
-
require 'termios'
|
87
|
-
PromptMethods::Termios
|
88
|
-
rescue LoadError
|
89
|
-
PromptMethods::Clear
|
54
|
+
# start password session. Multiple questions might be asked multiple times
|
55
|
+
# on the returned object. Info hash tries to uniquely identify the password
|
56
|
+
# session, so caching implementations can save passwords properly.
|
57
|
+
def start(info)
|
58
|
+
Prompter.new(info)
|
90
59
|
end
|
91
60
|
end
|
92
|
-
|
93
|
-
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,75 +1,123 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'rubygems'
|
2
3
|
require 'net/ssh/proxy/errors'
|
3
|
-
require 'net/ssh/ruby_compat'
|
4
4
|
|
5
|
-
module Net
|
5
|
+
module Net
|
6
|
+
module SSH
|
7
|
+
module Proxy
|
8
|
+
# An implementation of a command proxy. To use it, instantiate it,
|
9
|
+
# then pass the instantiated object via the :proxy key to
|
10
|
+
# Net::SSH.start:
|
11
|
+
#
|
12
|
+
# require 'net/ssh/proxy/command'
|
13
|
+
#
|
14
|
+
# proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p')
|
15
|
+
# Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
|
16
|
+
# ...
|
17
|
+
# end
|
18
|
+
class Command
|
19
|
+
# The command line template
|
20
|
+
attr_reader :command_line_template
|
6
21
|
|
7
|
-
|
8
|
-
|
9
|
-
# Net::SSH.start:
|
10
|
-
#
|
11
|
-
# require 'net/ssh/proxy/command'
|
12
|
-
#
|
13
|
-
# proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p')
|
14
|
-
# Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
|
15
|
-
# ...
|
16
|
-
# end
|
17
|
-
class Command
|
22
|
+
# The command line for the session
|
23
|
+
attr_reader :command_line
|
18
24
|
|
19
|
-
|
20
|
-
|
25
|
+
# Timeout in seconds in open, defaults to 60
|
26
|
+
attr_accessor :timeout
|
21
27
|
|
22
|
-
|
23
|
-
|
28
|
+
# Create a new socket factory that tunnels via a command executed
|
29
|
+
# with the user's shell, which is composed from the given command
|
30
|
+
# template. In the command template, `%h' will be substituted by
|
31
|
+
# the host name to connect and `%p' by the port.
|
32
|
+
def initialize(command_line_template)
|
33
|
+
@command_line_template = command_line_template
|
34
|
+
@command_line = nil
|
35
|
+
@timeout = 60
|
36
|
+
end
|
24
37
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
# Return a new socket connected to the given host and port via the
|
39
|
+
# proxy that was requested when the socket factory was instantiated.
|
40
|
+
def open(host, port, connection_options = nil)
|
41
|
+
command_line = @command_line_template.gsub(/%(.)/) {
|
42
|
+
case $1
|
43
|
+
when 'h'
|
44
|
+
host
|
45
|
+
when 'p'
|
46
|
+
port.to_s
|
47
|
+
when 'r'
|
48
|
+
remote_user = connection_options && connection_options[:remote_user]
|
49
|
+
if remote_user
|
50
|
+
remote_user
|
51
|
+
else
|
52
|
+
raise ArgumentError, "remote user name not available"
|
53
|
+
end
|
54
|
+
when '%'
|
55
|
+
'%'
|
56
|
+
else
|
57
|
+
raise ArgumentError, "unknown key: #{$1}"
|
58
|
+
end
|
59
|
+
}
|
60
|
+
begin
|
61
|
+
io = IO.popen(command_line, "r+")
|
62
|
+
begin
|
63
|
+
if result = IO.select([io], nil, [io], @timeout)
|
64
|
+
if result.last.any? || io.eof?
|
65
|
+
raise "command failed"
|
66
|
+
end
|
67
|
+
else
|
68
|
+
raise "command timed out"
|
69
|
+
end
|
70
|
+
rescue StandardError
|
71
|
+
close_on_error(io)
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
rescue StandardError => e
|
75
|
+
raise ConnectError, "#{e}: #{command_line}"
|
76
|
+
end
|
77
|
+
@command_line = command_line
|
78
|
+
if Gem.win_platform?
|
79
|
+
# read_nonblock and write_nonblock are not available on Windows
|
80
|
+
# pipe. Use sysread and syswrite as a replacement works.
|
81
|
+
def io.send(data, flag)
|
82
|
+
syswrite(data)
|
83
|
+
end
|
33
84
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
85
|
+
def io.recv(size)
|
86
|
+
sysread(size)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
def io.send(data, flag)
|
90
|
+
begin
|
91
|
+
result = write_nonblock(data)
|
92
|
+
rescue IO::WaitWritable, Errno::EINTR
|
93
|
+
IO.select(nil, [self])
|
94
|
+
retry
|
95
|
+
end
|
96
|
+
result
|
97
|
+
end
|
98
|
+
|
99
|
+
def io.recv(size)
|
100
|
+
begin
|
101
|
+
result = read_nonblock(size)
|
102
|
+
rescue IO::WaitReadable, Errno::EINTR
|
103
|
+
timeout_in_seconds = 20
|
104
|
+
if IO.select([self], nil, [self], timeout_in_seconds) == nil
|
105
|
+
raise "Unexpected spurious read wakeup"
|
106
|
+
end
|
107
|
+
|
108
|
+
retry
|
109
|
+
end
|
110
|
+
result
|
111
|
+
end
|
54
112
|
end
|
55
|
-
|
56
|
-
raise "command timed out"
|
57
|
-
end
|
58
|
-
rescue => e
|
59
|
-
raise ConnectError, "#{e}: #{command_line}"
|
60
|
-
end
|
61
|
-
@command_line = command_line
|
62
|
-
class << io
|
63
|
-
def send(data, flag)
|
64
|
-
write_nonblock(data)
|
113
|
+
io
|
65
114
|
end
|
66
115
|
|
67
|
-
def
|
68
|
-
|
116
|
+
def close_on_error(io)
|
117
|
+
Process.kill('TERM', io.pid)
|
118
|
+
Thread.new { io.close }
|
69
119
|
end
|
70
120
|
end
|
71
|
-
io
|
72
121
|
end
|
73
122
|
end
|
74
|
-
|
75
|
-
end; end; end
|
123
|
+
end
|
data/lib/net/ssh/proxy/errors.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
require 'net/ssh/errors'
|
2
2
|
|
3
|
-
module Net
|
3
|
+
module Net
|
4
|
+
module SSH
|
5
|
+
module Proxy
|
6
|
+
# A general exception class for all Proxy errors.
|
7
|
+
class Error < Net::SSH::Exception; end
|
4
8
|
|
5
|
-
|
6
|
-
|
9
|
+
# Used for reporting proxy connection errors.
|
10
|
+
class ConnectError < Error; end
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end; end; end
|
12
|
+
# Used when the server doesn't recognize the user's credentials.
|
13
|
+
class UnauthorizedError < Error; end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/net/ssh/proxy/http.rb
CHANGED
@@ -1,94 +1,98 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'net/ssh/proxy/errors'
|
3
3
|
|
4
|
-
module Net
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
4
|
+
module Net
|
5
|
+
module SSH
|
6
|
+
module Proxy
|
7
|
+
# An implementation of an HTTP proxy. To use it, instantiate it, then
|
8
|
+
# pass the instantiated object via the :proxy key to Net::SSH.start:
|
9
|
+
#
|
10
|
+
# require 'net/ssh/proxy/http'
|
11
|
+
#
|
12
|
+
# proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port)
|
13
|
+
# Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
|
14
|
+
# ...
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# If the proxy requires authentication, you can pass :user and :password
|
18
|
+
# to the proxy's constructor:
|
19
|
+
#
|
20
|
+
# proxy = Net::SSH::Proxy::HTTP.new('proxy_host', proxy_port,
|
21
|
+
# :user => "user", :password => "password")
|
22
|
+
#
|
23
|
+
# Note that HTTP digest authentication is not supported; Basic only at
|
24
|
+
# this point.
|
25
|
+
class HTTP
|
26
|
+
# The hostname or IP address of the HTTP proxy.
|
27
|
+
attr_reader :proxy_host
|
28
|
+
|
29
|
+
# The port number of the proxy.
|
30
|
+
attr_reader :proxy_port
|
31
|
+
|
32
|
+
# The map of additional options that were given to the object at
|
33
|
+
# initialization.
|
34
|
+
attr_reader :options
|
35
|
+
|
36
|
+
# Create a new socket factory that tunnels via the given host and
|
37
|
+
# port. The +options+ parameter is a hash of additional settings that
|
38
|
+
# can be used to tweak this proxy connection. Specifically, the following
|
39
|
+
# options are supported:
|
40
|
+
#
|
41
|
+
# * :user => the user name to use when authenticating to the proxy
|
42
|
+
# * :password => the password to use when authenticating
|
43
|
+
def initialize(proxy_host, proxy_port = 80, options = {})
|
44
|
+
@proxy_host = proxy_host
|
45
|
+
@proxy_port = proxy_port
|
46
|
+
@options = options
|
47
|
+
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
# Return a new socket connected to the given host and port via the
|
50
|
+
# proxy that was requested when the socket factory was instantiated.
|
51
|
+
def open(host, port, connection_options)
|
52
|
+
socket = establish_connection(connection_options[:timeout])
|
53
|
+
socket.write "CONNECT #{host}:#{port} HTTP/1.1\r\n"
|
54
|
+
socket.write "Host: #{host}:#{port}\r\n"
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
if options[:user]
|
57
|
+
credentials = ["#{options[:user]}:#{options[:password]}"].pack("m*").gsub(/\s/, "")
|
58
|
+
socket.write "Proxy-Authorization: Basic #{credentials}\r\n"
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
+
socket.write "\r\n"
|
61
62
|
|
62
|
-
|
63
|
+
resp = parse_response(socket)
|
63
64
|
|
64
|
-
|
65
|
+
return socket if resp[:code] == 200
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
private
|
67
|
+
socket.close
|
68
|
+
raise ConnectError, resp.inspect
|
69
|
+
end
|
71
70
|
|
72
|
-
|
73
|
-
version, code, reason = socket.gets.chomp.split(/ /, 3)
|
74
|
-
headers = {}
|
71
|
+
protected
|
75
72
|
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
def establish_connection(connect_timeout)
|
74
|
+
Socket.tcp(proxy_host, proxy_port, nil, nil,
|
75
|
+
connect_timeout: connect_timeout)
|
79
76
|
end
|
80
77
|
|
81
|
-
|
82
|
-
|
83
|
-
|
78
|
+
def parse_response(socket)
|
79
|
+
version, code, reason = socket.gets.chomp.split(/ /, 3)
|
80
|
+
headers = {}
|
84
81
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
:body => body }
|
90
|
-
end
|
82
|
+
while (line = socket.gets) && (line.chomp! != "")
|
83
|
+
name, value = line.split(/:/, 2)
|
84
|
+
headers[name.strip] = value.strip
|
85
|
+
end
|
91
86
|
|
92
|
-
|
87
|
+
body = socket.read(headers["Content-Length"].to_i) if headers["Content-Length"]
|
93
88
|
|
94
|
-
|
89
|
+
return { version: version,
|
90
|
+
code: code.to_i,
|
91
|
+
reason: reason,
|
92
|
+
headers: headers,
|
93
|
+
body: body }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'openssl'
|
3
|
+
require 'net/ssh/proxy/errors'
|
4
|
+
require 'net/ssh/proxy/http'
|
5
|
+
|
6
|
+
module Net
|
7
|
+
module SSH
|
8
|
+
module Proxy
|
9
|
+
# A specialization of the HTTP proxy which encrypts the whole connection
|
10
|
+
# using OpenSSL. This has the advantage that proxy authentication
|
11
|
+
# information is not sent in plaintext.
|
12
|
+
class HTTPS < HTTP
|
13
|
+
# Create a new socket factory that tunnels via the given host and
|
14
|
+
# port. The +options+ parameter is a hash of additional settings that
|
15
|
+
# can be used to tweak this proxy connection. In addition to the options
|
16
|
+
# taken by Net::SSH::Proxy::HTTP it supports:
|
17
|
+
#
|
18
|
+
# * :ssl_context => the SSL configuration to use for the connection
|
19
|
+
def initialize(proxy_host, proxy_port = 80, options = {})
|
20
|
+
@ssl_context = options.delete(:ssl_context) ||
|
21
|
+
OpenSSL::SSL::SSLContext.new
|
22
|
+
super(proxy_host, proxy_port, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
# Shim to make OpenSSL::SSL::SSLSocket behave like a regular TCPSocket
|
28
|
+
# for all intents and purposes of Net::SSH::BufferedIo
|
29
|
+
module SSLSocketCompatibility
|
30
|
+
def self.extended(object) # :nodoc:
|
31
|
+
object.define_singleton_method(:recv, object.method(:sysread))
|
32
|
+
object.sync_close = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def send(data, _opts)
|
36
|
+
syswrite(data)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def establish_connection(connect_timeout)
|
41
|
+
plain_socket = super(connect_timeout)
|
42
|
+
OpenSSL::SSL::SSLSocket.new(plain_socket, @ssl_context).tap do |socket|
|
43
|
+
socket.extend(SSLSocketCompatibility)
|
44
|
+
socket.connect
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/ssh/proxy/command'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module SSH
|
6
|
+
module Proxy
|
7
|
+
# An implementation of a jump proxy. To use it, instantiate it,
|
8
|
+
# then pass the instantiated object via the :proxy key to
|
9
|
+
# Net::SSH.start:
|
10
|
+
#
|
11
|
+
# require 'net/ssh/proxy/jump'
|
12
|
+
#
|
13
|
+
# proxy = Net::SSH::Proxy::Jump.new('user@proxy')
|
14
|
+
# Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
class Jump < Command
|
18
|
+
# The jump proxies
|
19
|
+
attr_reader :jump_proxies
|
20
|
+
|
21
|
+
# Create a new socket factory that tunnels via multiple jump proxes as
|
22
|
+
# [user@]host[:port].
|
23
|
+
def initialize(jump_proxies)
|
24
|
+
@jump_proxies = jump_proxies
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return a new socket connected to the given host and port via the jump
|
28
|
+
# proxy that was requested when the socket factory was instantiated.
|
29
|
+
def open(host, port, connection_options = nil)
|
30
|
+
build_proxy_command_equivalent(connection_options)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# We cannot build the ProxyCommand template until we know if the :config
|
35
|
+
# option was specified during `Net::SSH.start`.
|
36
|
+
def build_proxy_command_equivalent(connection_options = nil)
|
37
|
+
first_jump, extra_jumps = jump_proxies.split(",", 2)
|
38
|
+
config = connection_options && connection_options[:config]
|
39
|
+
uri = URI.parse("ssh://#{first_jump}")
|
40
|
+
|
41
|
+
template = "ssh".dup
|
42
|
+
template << " -l #{uri.user}" if uri.user
|
43
|
+
template << " -p #{uri.port}" if uri.port
|
44
|
+
template << " -J #{extra_jumps}" if extra_jumps
|
45
|
+
template << " -F #{config}" if config != true && config
|
46
|
+
template << " -W %h:%p "
|
47
|
+
template << uri.host
|
48
|
+
|
49
|
+
@command_line_template = template
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|