net-ssh 4.0.0.alpha3 → 4.0.0.alpha4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,42 @@
1
1
  language: ruby
2
- sudo: false
2
+ sudo: true
3
+ dist: trusty
4
+
3
5
  rvm:
4
6
  - 2.0
5
7
  - 2.1
6
8
  - 2.2
7
9
  - 2.3.0
8
- - jruby-head
9
- - rbx-3.20
10
+ - jruby-9.0.5.0
11
+ - rbx-3.25
10
12
  - ruby-head
13
+ env:
14
+ NET_SSH_RUN_INTEGRATION_TESTS=1
11
15
 
12
16
  matrix:
17
+ exclude:
18
+ - rvm: rbx-3.25
19
+ - rvm: jruby-9.0.5.0
20
+ include:
21
+ - rvm: rbx-3.25
22
+ env: NET_SSH_RUN_INTEGRATION_TESTS=
23
+ - rvm: jruby-9.0.5.0
24
+ env: JRUBY_OPTS='--client -J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -Xcext.enabled=false -J-Xss2m -Xcompile.invokedynamic=false' NET_SSH_RUN_INTEGRATION_TESTS=
13
25
  allow_failures:
14
26
  - rvm: ruby-head
15
- - rvm: rbx-3.20
27
+ - rvm: rbx-3.25
28
+ - rvm: jruby-9.0.5.0
16
29
 
17
30
  install:
31
+ - export JRUBY_OPTS='--client -J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -Xcext.enabled=false -J-Xss2m -Xcompile.invokedynamic=false'
32
+ - sudo pip install ansible
18
33
  - gem install bundler -v "= 1.11.2"
19
34
  - bundle _1.11.2_ install
35
+ - sudo ansible-galaxy install rvm_io.rvm1-ruby
36
+ - ansible-playbook ./test/integration/playbook.yml -i "localhost," --become -c local -e 'no_rvm=true' -e 'myuser=travis' -e 'mygroup=travis' -e 'homedir=/home/travis'
20
37
 
21
- script: rake test
38
+ script:
39
+ - bundle _1.11.2_ exec rake test
40
+ - BUNDLE_GEMFILE=./Gemfile.norbnacl bundle _1.11.2_ exec rake test
41
+ - bundle _1.11.2_ exec rake test_test
42
+ - bundle _1.11.2_ exec rubocop
@@ -1,3 +1,12 @@
1
+ === 4.0.0.alpha4
2
+
3
+ * Experimental event loop abstraction [Miklos Fazekas]
4
+ * RbNacl dependency is optional [Miklos Fazekas]
5
+ * agent_socket_factory option [Alon Goldboim]
6
+ * client sends KEXINIT, it doesn't have to wait for server [Miklos Fazekas]
7
+ * better error message when option is nil [Kane Morgan]
8
+ * prompting can be customized [Miklos Fazekas]
9
+
1
10
  === 4.0.0.alpha3
2
11
 
3
12
  * added max_select_wait_time [Eugene Kenny]
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ENV['NET_SSH_NO_RBNACL'] = 'true'
4
+ # Specify your gem's dependencies in mygem.gemspec
5
+ gemspec
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ net-ssh (4.0.0.alpha3)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.2.0)
10
+ metaclass (0.0.4)
11
+ minitest (5.8.4)
12
+ mocha (1.1.0)
13
+ metaclass (~> 0.0.1)
14
+ parser (2.3.1.0)
15
+ ast (~> 2.2)
16
+ powerpack (0.1.1)
17
+ rainbow (2.1.0)
18
+ rake (11.1.2)
19
+ rubocop (0.39.0)
20
+ parser (>= 2.3.0.7, < 3.0)
21
+ powerpack (~> 0.1)
22
+ rainbow (>= 1.99.1, < 3.0)
23
+ ruby-progressbar (~> 1.7)
24
+ unicode-display_width (~> 1.0, >= 1.0.1)
25
+ ruby-progressbar (1.8.0)
26
+ unicode-display_width (1.0.5)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler (~> 1.11.2)
33
+ minitest (~> 5.0)
34
+ mocha (>= 1.1.0)
35
+ net-ssh!
36
+ rake (~> 11.1)
37
+ rubocop (~> 0.39.0)
38
+
39
+ BUNDLED WITH
40
+ 1.11.2
@@ -79,8 +79,6 @@ The only requirement you might be missing is the OpenSSL bindings for Ruby. Thes
79
79
 
80
80
  If that spits out something like "OpenSSL 0.9.8g 19 Oct 2007", then you're set. If you get an error, then you'll need to see about rebuilding ruby with OpenSSL support, or (if your platform supports it) installing the OpenSSL bindings separately.
81
81
 
82
- Additionally: if you are going to be having Net::SSH prompt you for things like passwords or certificate passphrases, you'll want to have either the Highline (recommended) or Termios (unix systems only) gem installed, so that the passwords don't echo in clear text.
83
-
84
82
  Lastly, if you want to run the tests or use any of the Rake tasks, you'll need Mocha and other dependencies listed in Gemfile
85
83
 
86
84
 
data/Rakefile CHANGED
@@ -75,5 +75,15 @@ Rake::TestTask.new do |t|
75
75
  t.libs << "test/integration" if ENV['NET_SSH_RUN_INTEGRATION_TESTS']
76
76
  test_files = FileList['test/**/test_*.rb']
77
77
  test_files -= FileList['test/integration/**/test_*.rb'] unless ENV['NET_SSH_RUN_INTEGRATION_TESTS']
78
+ test_files -= FileList['test/test/**/test_*.rb']
79
+ t.test_files = test_files
80
+ end
81
+
82
+ desc "Run tests of Net::SSH:Test"
83
+ Rake::TestTask.new do |t|
84
+ t.name = "test_test"
85
+ # we need to run test/test separatedly as it hacks io + other modules
86
+ t.libs = ["lib", "test"]
87
+ test_files = FileList['test/test/**/test_*.rb']
78
88
  t.test_files = test_files
79
89
  end
@@ -0,0 +1,18 @@
1
+ version: '{build}'
2
+
3
+ skip_tags: true
4
+
5
+ environment:
6
+ matrix:
7
+ - ruby_version: "21"
8
+ - ruby_version: "21-x64"
9
+
10
+ install:
11
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
12
+ - gem install bundler --no-document -v 1.11.2
13
+ - BUNDLE_GEMFILE=./Gemfile.norbnacl bundle _1.11.2_ install --retry=3
14
+
15
+ test_script:
16
+ - BUNDLE_GEMFILE=./Gemfile.norbnacl bundle _1.11.2_ exec rake test
17
+
18
+ build: off
@@ -3,6 +3,7 @@
3
3
  ENV['HOME'] ||= ENV['HOMEPATH'] ? "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" : Dir.pwd
4
4
 
5
5
  require 'logger'
6
+ require 'etc'
6
7
 
7
8
  require 'net/ssh/config'
8
9
  require 'net/ssh/errors'
@@ -10,6 +11,7 @@ require 'net/ssh/loggable'
10
11
  require 'net/ssh/transport/session'
11
12
  require 'net/ssh/authentication/session'
12
13
  require 'net/ssh/connection/session'
14
+ require 'net/ssh/prompt'
13
15
 
14
16
  module Net
15
17
 
@@ -69,7 +71,7 @@ module Net
69
71
  :known_hosts, :global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
70
72
  :host_name, :user, :properties, :passphrase, :keys_only, :max_pkt_size,
71
73
  :max_win_size, :send_env, :use_agent, :number_of_password_prompts,
72
- :append_supported_algorithms, :non_interactive
74
+ :append_supported_algorithms, :non_interactive, :password_prompt, :agent_socket_factory
73
75
  ]
74
76
 
75
77
  # The standard means of starting a new SSH connection. When used with a
@@ -176,7 +178,7 @@ module Net
176
178
  # * :user_known_hosts_file => the location of the user known hosts file.
177
179
  # Set to an array to specify multiple user known hosts files.
178
180
  # Defaults to %w(~/.ssh/known_hosts ~/.ssh/known_hosts2).
179
- # * :use_agent => Set false to disable the use of ssh-agent. Defaults to
181
+ # * :use_agent => Set false to disable the use of ssh-agent. Defaults to
180
182
  # true
181
183
  # * :non_interactive => set to true if your app is non interactive and prefers
182
184
  # authentication failure vs password prompt
@@ -191,7 +193,11 @@ module Net
191
193
  # password auth method
192
194
  # * :non_interactive => non interactive applications should set it to true
193
195
  # to prefer failing a password/etc auth methods vs asking for password
196
+ # * :password_prompt => a custom prompt object with ask method. See Net::SSH::Prompt
194
197
  #
198
+ # * :agent_socket_factory => enables the user to pass a lambda/block that will serve as the socket factory
199
+ # Net::SSH::start(user,host,agent_socket_factory: ->{ UNIXSocket.open('/foo/bar') })
200
+ # example: ->{ UNIXSocket.open('/foo/bar')}
195
201
  # If +user+ parameter is nil it defaults to USER from ssh_config, or
196
202
  # local username
197
203
  def self.start(host, user=nil, options={}, &block)
@@ -200,6 +206,11 @@ module Net
200
206
  raise ArgumentError, "invalid option(s): #{invalid_options.join(', ')}"
201
207
  end
202
208
 
209
+ if options.values.include? nil
210
+ nil_options = options.keys.select { |k| options[k].nil? }
211
+ raise ArgumentError, "Value(s) have been set to nil: #{nil_options.join(', ')}"
212
+ end
213
+
203
214
  options[:user] = user if user
204
215
  options = configuration_for(host, options.fetch(:config, true)).merge(options)
205
216
  host = options.fetch(:host_name, host)
@@ -213,6 +224,8 @@ module Net
213
224
  options[:number_of_password_prompts] = 0
214
225
  end
215
226
 
227
+ options[:password_prompt] ||= Prompt.default(options)
228
+
216
229
  if options[:verbose]
217
230
  options[:logger].level = case options[:verbose]
218
231
  when Fixnum then options[:verbose]
@@ -19,7 +19,7 @@ module Net; module SSH; module Authentication
19
19
 
20
20
  # Instantiates a new agent object, connects to a running SSH agent,
21
21
  # negotiates the agent protocol version, and returns the agent object.
22
- def self.connect(logger=nil)
22
+ def self.connect(logger=nil, agent_socket_factory)
23
23
  agent = new(logger)
24
24
  agent.connect!
25
25
  agent
@@ -42,9 +42,9 @@ module Net; module SSH; module Authentication
42
42
 
43
43
  # Instantiates a new agent object, connects to a running SSH agent,
44
44
  # negotiates the agent protocol version, and returns the agent object.
45
- def self.connect(logger=nil)
45
+ def self.connect(logger=nil, agent_socket_factory = nil)
46
46
  agent = new(logger)
47
- agent.connect!
47
+ agent.connect!(agent_socket_factory)
48
48
  agent.negotiate!
49
49
  agent
50
50
  end
@@ -59,10 +59,10 @@ module Net; module SSH; module Authentication
59
59
  # given by the attribute writers. If the agent on the other end of the
60
60
  # socket reports that it is an SSH2-compatible agent, this will fail
61
61
  # (it only supports the ssh-agent distributed by OpenSSH).
62
- def connect!
62
+ def connect!(agent_socket_factory = nil)
63
63
  begin
64
64
  debug { "connecting to ssh-agent" }
65
- @socket = agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])
65
+ @socket = agent_socket_factory.nil? ? socket_class.open(ENV['SSH_AUTH_SOCK']) : agent_socket_factory.call
66
66
  rescue
67
67
  error { "could not connect to ssh-agent" }
68
68
  raise AgentNotAvailable, $!.message
@@ -132,7 +132,7 @@ module Net; module SSH; module Authentication
132
132
  private
133
133
 
134
134
  # Returns the agent socket factory to use.
135
- def agent_socket_factory
135
+ def socket_class
136
136
  if Net::SSH::Authentication::PLATFORM == :win32
137
137
  Pageant::Socket
138
138
  else
@@ -1,5 +1,6 @@
1
- gem 'rbnacl-libsodium'
2
- gem 'rbnacl'
1
+ gem 'rbnacl-libsodium', '~> 1.0.10'
2
+ gem 'rbnacl', '~> 3.3.0'
3
+ gem 'bcrypt_pbkdf', '~> 1.0.0.alpha1' unless RUBY_PLATFORM == "java"
3
4
 
4
5
  require 'rbnacl/libsodium'
5
6
  require 'rbnacl'
@@ -13,20 +14,14 @@ require 'base64'
13
14
  require 'net/ssh/transport/cipher_factory'
14
15
  require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
15
16
 
16
- module RbNaCl
17
- module Signatures
18
- module Ed25519
19
- class SigningKeyFromFile < SigningKey
20
- def initialize(pk,sk)
21
- @signing_key = sk
22
- @verify_key = VerifyKey.new(pk)
23
- end
24
- end
17
+ module ED25519
18
+ class SigningKeyFromFile < RbNaCl::Signatures::Ed25519::SigningKey
19
+ def initialize(pk,sk)
20
+ @signing_key = sk
21
+ @verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(pk)
25
22
  end
26
23
  end
27
- end
28
24
 
29
- module ED25519
30
25
  class PubKey
31
26
  def initialize(data)
32
27
  @verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(data)
@@ -118,7 +113,7 @@ module ED25519
118
113
  _comment = decoded.read_string
119
114
 
120
115
  @pk = pk
121
- @sign_key = RbNaCl::Signatures::Ed25519::SigningKeyFromFile.new(pk,sk)
116
+ @sign_key = SigningKeyFromFile.new(pk,sk)
122
117
  end
123
118
 
124
119
  def public_key
@@ -176,7 +176,7 @@ module Net
176
176
  # or if the agent is otherwise not available.
177
177
  def agent
178
178
  return unless use_agent?
179
- @agent ||= Agent.connect(logger)
179
+ @agent ||= Agent.connect(logger, options[:agent_socket_factory])
180
180
  rescue AgentNotAvailable
181
181
  @use_agent = false
182
182
  nil
@@ -221,11 +221,11 @@ module Net
221
221
  key = KeyFactory.load_public_key(identity[:pubkey_file])
222
222
  { :public_key => key, :from => :file, :file => identity[:privkey_file] }
223
223
  when :privkey_file
224
- private_key = KeyFactory.load_private_key(identity[:privkey_file], options[:passphrase], ask_passphrase)
224
+ private_key = KeyFactory.load_private_key(identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt])
225
225
  key = private_key.send(:public_key)
226
226
  { :public_key => key, :from => :file, :file => identity[:privkey_file], :key => private_key }
227
227
  when :data
228
- private_key = KeyFactory.load_data_private_key(identity[:data], options[:passphrase], ask_passphrase)
228
+ private_key = KeyFactory.load_data_private_key(identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt])
229
229
  key = private_key.send(:public_key)
230
230
  { :public_key => key, :from => :key_data, :data => identity[:data], :key => private_key }
231
231
  else
@@ -22,6 +22,7 @@ module Net; module SSH; module Authentication; module Methods
22
22
  @session = session
23
23
  @key_manager = options[:key_manager]
24
24
  @options = options
25
+ @prompt = options[:password_prompt]
25
26
  self.logger = session.logger
26
27
  end
27
28
 
@@ -55,6 +56,9 @@ module Net; module SSH; module Authentication; module Methods
55
56
  buffer
56
57
  end
57
58
 
59
+ private
60
+
61
+ attr_reader :prompt
58
62
  end
59
63
 
60
64
  end; end; end; end
@@ -8,8 +8,6 @@ module Net
8
8
 
9
9
  # Implements the "keyboard-interactive" SSH authentication method.
10
10
  class KeyboardInteractive < Abstract
11
- include Prompt
12
-
13
11
  USERAUTH_INFO_REQUEST = 60
14
12
  USERAUTH_INFO_RESPONSE = 61
15
13
 
@@ -18,12 +16,14 @@ module Net
18
16
  debug { "trying keyboard-interactive" }
19
17
  send_message(userauth_request(username, next_service, "keyboard-interactive", "", ""))
20
18
 
19
+ prompter = nil
21
20
  loop do
22
21
  message = session.next_message
23
22
 
24
23
  case message.type
25
24
  when USERAUTH_SUCCESS
26
25
  debug { "keyboard-interactive succeeded" }
26
+ prompter.success if prompter
27
27
  return true
28
28
  when USERAUTH_FAILURE
29
29
  debug { "keyboard-interactive failed" }
@@ -31,26 +31,26 @@ module Net
31
31
  raise Net::SSH::Authentication::DisallowedMethod unless
32
32
  message[:authentications].split(/,/).include? 'keyboard-interactive'
33
33
 
34
- return false
34
+ return false unless interactive?
35
+ password = nil
36
+ debug { "retrying keyboard-interactive" }
37
+ send_message(userauth_request(username, next_service, "keyboard-interactive", "", ""))
35
38
  when USERAUTH_INFO_REQUEST
36
39
  name = message.read_string
37
40
  instruction = message.read_string
38
41
  debug { "keyboard-interactive info request" }
39
42
 
40
- unless password
41
- if interactive?
42
- puts(name) unless name.empty?
43
- puts(instruction) unless instruction.empty?
44
- end
43
+ if password.nil? && interactive? && prompter.nil?
44
+ prompter = prompt.start(type: 'keyboard-interactive', name: name, instruction: instruction)
45
45
  end
46
46
 
47
47
  _ = message.read_string # lang_tag
48
48
  responses =[]
49
-
49
+
50
50
  message.read_long.times do
51
51
  text = message.read_string
52
52
  echo = message.read_bool
53
- password_to_send = password || (interactive? ? prompt(text, echo) : nil)
53
+ password_to_send = password || (prompter && prompter.ask(text, echo))
54
54
  responses << password_to_send
55
55
  end
56
56
 
@@ -9,11 +9,10 @@ module Net
9
9
 
10
10
  # Implements the "password" SSH authentication method.
11
11
  class Password < Abstract
12
- include Prompt
13
-
14
12
  # Attempt to authenticate the given user for the given service. If
15
13
  # the password parameter is nil, this will ask for password
16
14
  def authenticate(next_service, username, password=nil)
15
+ clear_prompter!
17
16
  retries = 0
18
17
  max_retries = get_max_retries
19
18
  return false if !password && max_retries == 0
@@ -37,6 +36,7 @@ module Net
37
36
  case message.type
38
37
  when USERAUTH_SUCCESS
39
38
  debug { "password succeeded" }
39
+ @prompter.success if @prompter
40
40
  return true
41
41
  when USERAUTH_FAILURE
42
42
  return false
@@ -52,9 +52,20 @@ module Net
52
52
 
53
53
  NUMBER_OF_PASSWORD_PROMPTS = 3
54
54
 
55
+ def clear_prompter!
56
+ @prompt_info = nil
57
+ @prompter = nil
58
+ end
59
+
55
60
  def ask_password(username)
61
+ host = session.transport.host
62
+ prompt_info = {type: 'password', user: username, host: host}
63
+ if @prompt_info != prompt_info
64
+ @prompt_info = prompt_info
65
+ @prompter = prompt.start(prompt_info)
66
+ end
56
67
  echo = false
57
- prompt("#{username}@#{session.transport.host}'s password:", echo)
68
+ @prompter.ask("#{username}@#{host}'s password:", echo)
58
69
  end
59
70
 
60
71
  def get_max_retries