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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +13 -0
- data/lib/sshkit.rb +1 -1
- data/lib/sshkit/all.rb +1 -0
- data/lib/sshkit/backends/connection_pool.rb +3 -1
- data/lib/sshkit/backends/connection_pool/cache.rb +3 -1
- data/lib/sshkit/backends/netssh.rb +11 -2
- data/lib/sshkit/backends/netssh/known_hosts.rb +141 -0
- data/lib/sshkit/command_map.rb +4 -2
- data/lib/sshkit/configuration.rb +1 -1
- data/lib/sshkit/host.rb +2 -2
- data/lib/sshkit/version.rb +1 -1
- data/test/known_hosts/github +1 -0
- data/test/known_hosts/github_hash +1 -0
- data/test/unit/backends/test_netssh.rb +24 -0
- data/test/unit/test_command_map.rb +9 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9da07f57b3af779a599eacb874eb170a75c70013
|
4
|
+
data.tar.gz: 547bea80a8bacc021f25188ddb2d031078077a4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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'
|
@@ -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
|
data/lib/sshkit/command_map.rb
CHANGED
@@ -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
|
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
|
|
data/lib/sshkit/configuration.rb
CHANGED
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
|
data/lib/sshkit/version.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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
|