sshkit 1.9.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1b6bcfd27e36ebe075f80d336e753443e1367aed
4
- data.tar.gz: c41d529fd3c79e9b89506e20b95778672385f636
3
+ metadata.gz: 9da07f57b3af779a599eacb874eb170a75c70013
4
+ data.tar.gz: 547bea80a8bacc021f25188ddb2d031078077a4d
5
5
  SHA512:
6
- metadata.gz: 8cc8f62186668eae9e473e7b72c073d31c008af7104ee893e5a5761bdc6c3159b9ce2ecd6063a0e4cbc4173eeb986451dffa5d6b69588e04ba577bd499cad8c7
7
- data.tar.gz: be5c5e5686439d393a498d9084219eecb706192518bfc57aaeec18ae7c0b6c4803b344ef51690c0ab7ac9987725220caf2fbcd7eeac24f4ce3c5873760fda69d
6
+ metadata.gz: df5aaafd725f7142083d2d9a8a393561a03456230a01bdfda61d2341e3f7debc39f76a82dd373813ee67837fffeb6add8def675e48eb3139b3874061b8115543
7
+ data.tar.gz: f793b26a4fe0bae6c30eda6bea38484fc6faf834a7d3dc0b674f7c04c4b10bcf505fa38dda91a73ae37fa6f4f3dfca92bd1f658fee901eb387296bc44e84207e
data/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ appear at the top.
8
8
  * Add your entries below here, remember to credit yourself however you want
9
9
  to be credited!
10
10
 
11
+ ## 1.10.0 (2016-04-22)
12
+
13
+ * You can now opt-in to caching of SSH's known_hosts file for a speed boost
14
+ when deploying to a large fleet of servers. Refer to the
15
+ [README](https://github.com/capistrano/sshkit#known-hosts-caching) for
16
+ details. We plan to turn this on by default in a future version of SSHKit.
17
+ [PR #330](https://github.com/capistrano/sshkit/pull/330) @byroot
18
+ * SSHKit now explicitly closes its pooled SSH connections when Ruby exits;
19
+ this fixes `zlib(finalizer): the stream was freed prematurely` warnings
20
+ [PR #343](https://github.com/capistrano/sshkit/pull/343) @mattbrictson
21
+ * Allow command map entries (`SSHKit::CommandMap#[]`) to be Procs
22
+ [PR #310](https://github.com/capistrano/sshkit/pull/310)
23
+ @mikz
24
+
11
25
  ## 1.9.0
12
26
 
13
27
  **Refer to the 1.9.0.rc1 release notes for a full list of new features, fixes,
data/README.md CHANGED
@@ -508,6 +508,19 @@ pooling behaviour entirely by setting the idle_timeout to zero:
508
508
  SSHKit::Backend::Netssh.pool.idle_timeout = 0 # disabled
509
509
  ```
510
510
 
511
+ ## Known hosts caching
512
+
513
+ If you connect to many hosts with the `Netssh` backend, looking up `~/.ssh/known_hosts` can significantly impact performances.
514
+ You can mitigate this by using SSHKit's lookup caching like this:
515
+
516
+ ```ruby
517
+ SSHKit::Backend::Netssh.configure do |ssh|
518
+ ssh.ssh_options = {
519
+ known_hosts: SSHKit::Backend::Netssh::KnownHosts.new,
520
+ }
521
+ end
522
+ ```
523
+
511
524
  ## Tunneling and other related SSH themes
512
525
 
513
526
  In order to do special gymnasitcs with SSH, tunneling, aliasing, complex options, etc with SSHKit it is possible to use [the underlying Net::SSH API](https://github.com/capistrano/sshkit/blob/master/EXAMPLES.md#setting-global-ssh-options) however in many cases it is preferred to use the system SSH configuration file at [`~/.ssh/config`](http://man.cx/ssh_config). This allows you to have personal configuration tied to your machine that does not have to be committed with the repository. If this is not suitable (everyone on the team needs a proxy command, or some special aliasing) a file in the same format can be placed in the project directory at `~/yourproject/.ssh/config`, this will be merged with the system settings in `~/.ssh/config`, and with any configuration specified in [`SSHKit::Backend::Netssh.config.ssh_options`](https://github.com/capistrano/sshkit/blob/master/lib/sshkit/backends/netssh.rb#L133).
data/lib/sshkit.rb CHANGED
@@ -4,7 +4,7 @@ module SSHKit
4
4
 
5
5
  class << self
6
6
 
7
- attr_accessor :config
7
+ attr_writer :config
8
8
 
9
9
  def configure
10
10
  @@config ||= Configuration.new
data/lib/sshkit/all.rb CHANGED
@@ -35,5 +35,6 @@ require_relative 'backends/abstract'
35
35
  require_relative 'backends/connection_pool'
36
36
  require_relative 'backends/printer'
37
37
  require_relative 'backends/netssh'
38
+ require_relative 'backends/netssh/known_hosts'
38
39
  require_relative 'backends/local'
39
40
  require_relative 'backends/skipper'
@@ -78,10 +78,12 @@ class SSHKit::Backend::ConnectionPool
78
78
  end
79
79
  end
80
80
 
81
- private
81
+ protected
82
82
 
83
83
  attr_reader :caches, :timed_out_connections
84
84
 
85
+ private
86
+
85
87
  def cache_enabled?
86
88
  idle_timeout && idle_timeout > 0
87
89
  end
@@ -53,10 +53,12 @@ class SSHKit::Backend::ConnectionPool::Cache
53
53
  end
54
54
  end
55
55
 
56
- private
56
+ protected
57
57
 
58
58
  attr_reader :connections, :idle_timeout, :closer
59
59
 
60
+ private
61
+
60
62
  def fresh?(expires_at)
61
63
  expires_at > Time.now
62
64
  end
@@ -1,3 +1,6 @@
1
+ require 'English'
2
+ require 'strscan'
3
+ require 'mutex_m'
1
4
  require 'net/ssh'
2
5
  require 'net/scp'
3
6
 
@@ -5,6 +8,8 @@ module Net
5
8
  module SSH
6
9
  class Config
7
10
  class << self
11
+ remove_method :default_files
12
+
8
13
  def default_files
9
14
  @@default_files + [File.join(Dir.pwd, '.ssh/config')]
10
15
  end
@@ -18,13 +23,12 @@ module SSHKit
18
23
  module Backend
19
24
 
20
25
  class Netssh < Abstract
21
-
22
26
  class Configuration
23
27
  attr_accessor :connection_timeout, :pty
24
28
  attr_writer :ssh_options
25
29
 
26
30
  def ssh_options
27
- @ssh_options || {}
31
+ @ssh_options ||= {}
28
32
  end
29
33
  end
30
34
 
@@ -42,7 +46,12 @@ module SSHKit
42
46
  end
43
47
  end
44
48
 
49
+ # Note that this pool must be explicitly closed before Ruby exits to
50
+ # ensure the underlying IO objects are properly cleaned up. We register an
51
+ # at_exit handler to do this automatically, as long as Ruby is exiting
52
+ # cleanly (i.e. without an exception).
45
53
  @pool = SSHKit::Backend::ConnectionPool.new
54
+ at_exit { @pool.close_connections if @pool && !$ERROR_INFO }
46
55
 
47
56
  class << self
48
57
  attr_accessor :pool
@@ -0,0 +1,141 @@
1
+ module SSHKit
2
+
3
+ module Backend
4
+
5
+ class Netssh < Abstract
6
+
7
+ class KnownHostsKeys
8
+ include Mutex_m
9
+
10
+ def initialize(path)
11
+ super()
12
+ @path = File.expand_path(path)
13
+ @hosts_keys = nil
14
+ end
15
+
16
+ def keys_for(hostlist)
17
+ keys, hashes = hosts_keys, hosts_hashes
18
+ parse_file unless keys && hashes
19
+ keys, hashes = hosts_keys, hosts_hashes
20
+
21
+ hostlist.split(',').each do |host|
22
+ key_list = keys[host]
23
+ return key_list if key_list
24
+
25
+ hashes.each do |(hmac, salt), hash_keys|
26
+ if OpenSSL::HMAC.digest(sha1, salt, host) == hmac
27
+ return hash_keys
28
+ end
29
+ end
30
+ end
31
+
32
+ []
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :path
38
+ attr_accessor :hosts_keys, :hosts_hashes
39
+
40
+ def sha1
41
+ @sha1 ||= OpenSSL::Digest.new('sha1')
42
+ end
43
+
44
+ def parse_file
45
+ synchronize do
46
+ return if hosts_keys && hosts_hashes
47
+
48
+ unless File.readable?(path)
49
+ self.hosts_keys = {}
50
+ self.hosts_hashes = []
51
+ return
52
+ end
53
+
54
+ new_keys = {}
55
+ new_hashes = []
56
+ File.open(path) do |file|
57
+ scanner = StringScanner.new("")
58
+ file.each_line do |line|
59
+ scanner.string = line
60
+ parse_line(scanner, new_keys, new_hashes)
61
+ end
62
+ end
63
+ self.hosts_keys = new_keys
64
+ self.hosts_hashes = new_hashes
65
+ end
66
+ end
67
+
68
+ def parse_line(scanner, hosts_keys, hosts_hashes)
69
+ return if empty_line?(scanner)
70
+
71
+ hostlist = parse_hostlist(scanner)
72
+ return unless supported_type?(scanner)
73
+ key = parse_key(scanner)
74
+
75
+ if hostlist.size == 1 && hostlist.first =~ /\A\|1(\|.+){2}\z/
76
+ hosts_hashes << [parse_host_hash(hostlist.first), key]
77
+ else
78
+ hostlist.each do |host|
79
+ (hosts_keys[host] ||= []) << key
80
+ end
81
+ end
82
+ end
83
+
84
+ def parse_host_hash(line)
85
+ _, _, salt, hmac = line.split('|')
86
+ [Base64.decode64(hmac), Base64.decode64(salt)]
87
+ end
88
+
89
+ def empty_line?(scanner)
90
+ scanner.skip(/\s*/)
91
+ scanner.match?(/$|#/)
92
+ end
93
+
94
+ def parse_hostlist(scanner)
95
+ scanner.skip(/\s*/)
96
+ scanner.scan(/\S+/).split(',')
97
+ end
98
+
99
+ def supported_type?(scanner)
100
+ scanner.skip(/\s*/)
101
+ Net::SSH::KnownHosts::SUPPORTED_TYPE.include?(scanner.scan(/\S+/))
102
+ end
103
+
104
+ def parse_key(scanner)
105
+ scanner.skip(/\s*/)
106
+ Net::SSH::Buffer.new(scanner.rest.unpack("m*").first).read_key
107
+ end
108
+ end
109
+
110
+ class KnownHosts
111
+ include Mutex_m
112
+
113
+ def initialize
114
+ super()
115
+ @files = {}
116
+ end
117
+
118
+ def search_for(host, options = {})
119
+ keys = ::Net::SSH::KnownHosts.hostfiles(options).map do |path|
120
+ known_hosts_file(path).keys_for(host)
121
+ end.flatten
122
+ ::Net::SSH::HostKeys.new(keys, host, self, options)
123
+ end
124
+
125
+ def add(*args)
126
+ ::Net::SSH::KnownHosts.add(*args)
127
+ synchronize { @files = {} }
128
+ end
129
+
130
+ private
131
+
132
+ def known_hosts_file(path)
133
+ @files[path] || synchronize { @files[path] ||= KnownHostsKeys.new(path) }
134
+ end
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -31,18 +31,20 @@ module SSHKit
31
31
  end
32
32
  end
33
33
 
34
+ TO_VALUE = ->(obj) { obj.respond_to?(:call) ? obj.call : obj }
35
+
34
36
  def initialize(value = nil)
35
37
  @map = CommandHash.new(value || defaults)
36
38
  end
37
39
 
38
40
  def [](command)
39
41
  if prefix[command].any?
40
- prefixes = prefix[command].map{ |prefix| prefix.respond_to?(:call) ? prefix.call : prefix }
42
+ prefixes = prefix[command].map(&TO_VALUE)
41
43
  prefixes = prefixes.join(" ")
42
44
 
43
45
  "#{prefixes} #{command}"
44
46
  else
45
- @map[command]
47
+ TO_VALUE.(@map[command])
46
48
  end
47
49
  end
48
50
 
@@ -2,7 +2,7 @@ module SSHKit
2
2
 
3
3
  class Configuration
4
4
 
5
- attr_accessor :umask, :output_verbosity
5
+ attr_accessor :umask
6
6
  attr_writer :output, :backend, :default_env, :default_runner
7
7
 
8
8
  def output
data/lib/sshkit/host.rb CHANGED
@@ -99,7 +99,7 @@ module SSHKit
99
99
  class SimpleHostParser
100
100
 
101
101
  def self.suitable?(host_string)
102
- !host_string.match(/[:|@]/)
102
+ !host_string.match(/:|@/)
103
103
  end
104
104
 
105
105
  def initialize(host_string)
@@ -127,7 +127,7 @@ module SSHKit
127
127
  class HostWithPortParser < SimpleHostParser
128
128
 
129
129
  def self.suitable?(host_string)
130
- !host_string.match(/[@|\[|\]]/)
130
+ !host_string.match(/@|\[|\]/)
131
131
  end
132
132
 
133
133
  def port
@@ -1,3 +1,3 @@
1
1
  module SSHKit
2
- VERSION = "1.9.0"
2
+ VERSION = "1.10.0".freeze
3
3
  end
@@ -0,0 +1 @@
1
+ github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
@@ -0,0 +1 @@
1
+ |1|eKp+6E0rZ3lONgsIziurXEnaIik=|rcQB/rlJMUquUyFta64KugPjX4o= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
@@ -1,4 +1,5 @@
1
1
  require 'helper'
2
+ require 'tempfile'
2
3
 
3
4
  module SSHKit
4
5
  module Backend
@@ -53,6 +54,29 @@ module SSHKit
53
54
  end
54
55
  end
55
56
 
57
+ if Net::SSH::Version::CURRENT >= Net::SSH::Version[3, 1, 0]
58
+ def test_known_hosts_for_when_all_hosts_are_recognized
59
+ perform_known_hosts_test("github")
60
+ end
61
+
62
+ def test_known_hosts_for_when_an_host_hash_is_recognized
63
+ perform_known_hosts_test("github_hash")
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def perform_known_hosts_test(hostfile)
70
+ source = File.join(File.dirname(__FILE__), '../../known_hosts', hostfile)
71
+ kh = Netssh::KnownHosts.new
72
+ keys = kh.search_for('github.com', user_known_hosts_file: source, global_known_hosts_file: Tempfile.new('sshkit-test').path)
73
+
74
+ assert_instance_of ::Net::SSH::HostKeys, keys
75
+ assert_equal(1, keys.count)
76
+ keys.each do |key|
77
+ assert_equal("ssh-rsa", key.ssh_type)
78
+ end
79
+ end
56
80
  end
57
81
  end
58
82
  end
@@ -16,6 +16,15 @@ module SSHKit
16
16
  assert_equal map[:rake], "/usr/local/rbenv/shims/rake"
17
17
  end
18
18
 
19
+ def test_setter_procs
20
+ map = CommandMap.new
21
+ i = 0
22
+ map[:rake] = -> { i += 1; "/usr/local/rbenv/shims/rake#{i}" }
23
+
24
+ assert_equal map[:rake], "/usr/local/rbenv/shims/rake1"
25
+ assert_equal map[:rake], "/usr/local/rbenv/shims/rake2"
26
+ end
27
+
19
28
  def test_prefix
20
29
  map = CommandMap.new
21
30
  map.prefix[:rake].push("/home/vagrant/.rbenv/bin/rbenv exec")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sshkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Hambley
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-03-10 00:00:00.000000000 Z
12
+ date: 2016-04-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-ssh
@@ -158,6 +158,7 @@ files:
158
158
  - lib/sshkit/backends/connection_pool/nil_cache.rb
159
159
  - lib/sshkit/backends/local.rb
160
160
  - lib/sshkit/backends/netssh.rb
161
+ - lib/sshkit/backends/netssh/known_hosts.rb
161
162
  - lib/sshkit/backends/printer.rb
162
163
  - lib/sshkit/backends/skipper.rb
163
164
  - lib/sshkit/color.rb
@@ -189,6 +190,8 @@ files:
189
190
  - test/functional/backends/test_netssh.rb
190
191
  - test/functional/test_ssh_server_comes_up_for_functional_tests.rb
191
192
  - test/helper.rb
193
+ - test/known_hosts/github
194
+ - test/known_hosts/github_hash
192
195
  - test/support/vagrant_wrapper.rb
193
196
  - test/unit/backends/test_abstract.rb
194
197
  - test/unit/backends/test_connection_pool.rb
@@ -230,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
233
  version: '0'
231
234
  requirements: []
232
235
  rubyforge_project:
233
- rubygems_version: 2.6.1
236
+ rubygems_version: 2.6.2
234
237
  signing_key:
235
238
  specification_version: 4
236
239
  summary: SSHKit makes it easy to write structured, testable SSH commands in Ruby
@@ -240,6 +243,8 @@ test_files:
240
243
  - test/functional/backends/test_netssh.rb
241
244
  - test/functional/test_ssh_server_comes_up_for_functional_tests.rb
242
245
  - test/helper.rb
246
+ - test/known_hosts/github
247
+ - test/known_hosts/github_hash
243
248
  - test/support/vagrant_wrapper.rb
244
249
  - test/unit/backends/test_abstract.rb
245
250
  - test/unit/backends/test_connection_pool.rb