cheffish 1.5.0 → 1.6.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 +4 -4
- data/Gemfile +10 -0
- data/LICENSE +201 -201
- data/README.md +120 -120
- data/Rakefile +23 -23
- data/cheffish.gemspec +26 -0
- data/lib/chef/provider/chef_acl.rb +446 -439
- data/lib/chef/provider/chef_client.rb +53 -53
- data/lib/chef/provider/chef_container.rb +55 -55
- data/lib/chef/provider/chef_data_bag.rb +55 -55
- data/lib/chef/provider/chef_data_bag_item.rb +278 -278
- data/lib/chef/provider/chef_environment.rb +83 -83
- data/lib/chef/provider/chef_group.rb +83 -83
- data/lib/chef/provider/chef_mirror.rb +169 -169
- data/lib/chef/provider/chef_node.rb +87 -87
- data/lib/chef/provider/chef_organization.rb +155 -155
- data/lib/chef/provider/chef_resolved_cookbooks.rb +46 -46
- data/lib/chef/provider/chef_role.rb +84 -84
- data/lib/chef/provider/chef_user.rb +59 -59
- data/lib/chef/provider/private_key.rb +225 -225
- data/lib/chef/provider/public_key.rb +88 -88
- data/lib/chef/resource/chef_acl.rb +69 -69
- data/lib/chef/resource/chef_client.rb +48 -48
- data/lib/chef/resource/chef_container.rb +22 -22
- data/lib/chef/resource/chef_data_bag.rb +22 -22
- data/lib/chef/resource/chef_data_bag_item.rb +121 -121
- data/lib/chef/resource/chef_environment.rb +77 -77
- data/lib/chef/resource/chef_group.rb +53 -53
- data/lib/chef/resource/chef_mirror.rb +52 -52
- data/lib/chef/resource/chef_node.rb +22 -22
- data/lib/chef/resource/chef_organization.rb +69 -69
- data/lib/chef/resource/chef_resolved_cookbooks.rb +35 -35
- data/lib/chef/resource/chef_role.rb +110 -110
- data/lib/chef/resource/chef_user.rb +56 -56
- data/lib/chef/resource/private_key.rb +48 -48
- data/lib/chef/resource/public_key.rb +25 -25
- data/lib/cheffish.rb +235 -235
- data/lib/cheffish/actor_provider_base.rb +131 -131
- data/lib/cheffish/basic_chef_client.rb +184 -184
- data/lib/cheffish/chef_provider_base.rb +246 -246
- data/lib/cheffish/chef_run.rb +162 -162
- data/lib/cheffish/chef_run_data.rb +19 -19
- data/lib/cheffish/chef_run_listener.rb +30 -30
- data/lib/cheffish/key_formatter.rb +113 -113
- data/lib/cheffish/merged_config.rb +98 -94
- data/lib/cheffish/recipe_dsl.rb +157 -157
- data/lib/cheffish/rspec.rb +8 -8
- data/lib/cheffish/rspec/chef_run_support.rb +83 -83
- data/lib/cheffish/rspec/matchers.rb +4 -4
- data/lib/cheffish/rspec/matchers/be_idempotent.rb +16 -16
- data/lib/cheffish/rspec/matchers/emit_no_warnings_or_errors.rb +15 -15
- data/lib/cheffish/rspec/matchers/have_updated.rb +37 -37
- data/lib/cheffish/rspec/matchers/partially_match.rb +63 -63
- data/lib/cheffish/rspec/recipe_run_wrapper.rb +78 -78
- data/lib/cheffish/rspec/repository_support.rb +108 -108
- data/lib/cheffish/server_api.rb +52 -52
- data/lib/cheffish/version.rb +3 -3
- data/lib/cheffish/with_pattern.rb +21 -21
- data/spec/functional/fingerprint_spec.rb +64 -64
- data/spec/functional/merged_config_spec.rb +19 -19
- data/spec/functional/server_api_spec.rb +13 -13
- data/spec/integration/chef_acl_spec.rb +892 -879
- data/spec/integration/chef_client_spec.rb +105 -105
- data/spec/integration/chef_container_spec.rb +33 -33
- data/spec/integration/chef_group_spec.rb +309 -309
- data/spec/integration/chef_mirror_spec.rb +491 -491
- data/spec/integration/chef_node_spec.rb +786 -786
- data/spec/integration/chef_organization_spec.rb +226 -226
- data/spec/integration/chef_role_spec.rb +78 -78
- data/spec/integration/chef_user_spec.rb +85 -85
- data/spec/integration/private_key_spec.rb +399 -399
- data/spec/integration/recipe_dsl_spec.rb +28 -28
- data/spec/integration/rspec/converge_spec.rb +183 -183
- data/spec/support/key_support.rb +29 -29
- data/spec/support/spec_support.rb +15 -15
- data/spec/unit/get_private_key_spec.rb +131 -131
- data/spec/unit/recipe_run_wrapper_spec.rb +37 -37
- metadata +7 -5
data/lib/cheffish/chef_run.rb
CHANGED
@@ -1,162 +1,162 @@
|
|
1
|
-
require 'cheffish/basic_chef_client'
|
2
|
-
|
3
|
-
module Cheffish
|
4
|
-
class ChefRun
|
5
|
-
#
|
6
|
-
# @param chef_config A hash with symbol keys that looks suspiciously similar to `Chef::Config`.
|
7
|
-
# Some possible options:
|
8
|
-
# - stdout: <IO object> - where to stream stdout to
|
9
|
-
# - stderr: <IO object> - where to stream stderr to
|
10
|
-
# - log_level: :debug|:info|:warn|:error|:fatal
|
11
|
-
# - log_location: <path|IO object> - where to stream logs to
|
12
|
-
# - verbose_logging: true|false - true if you want verbose logging in :debug
|
13
|
-
#
|
14
|
-
def initialize(chef_config={})
|
15
|
-
@chef_config = chef_config || {}
|
16
|
-
end
|
17
|
-
|
18
|
-
attr_reader :chef_config
|
19
|
-
|
20
|
-
class StringIOTee < StringIO
|
21
|
-
def initialize(*streams)
|
22
|
-
super()
|
23
|
-
@streams = streams.flatten.select { |s| !s.nil? }
|
24
|
-
end
|
25
|
-
|
26
|
-
attr_reader :streams
|
27
|
-
|
28
|
-
def write(*args, &block)
|
29
|
-
super
|
30
|
-
streams.each { |s| s.write(*args, &block) }
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def client
|
35
|
-
@client ||= begin
|
36
|
-
chef_config = self.chef_config.dup
|
37
|
-
chef_config[:log_level] ||= :debug if !chef_config.has_key?(:log_level)
|
38
|
-
chef_config[:verbose_logging] = false if !chef_config.has_key?(:verbose_logging)
|
39
|
-
chef_config[:stdout] = StringIOTee.new(chef_config[:stdout])
|
40
|
-
chef_config[:stderr] = StringIOTee.new(chef_config[:stderr])
|
41
|
-
chef_config[:log_location] = StringIOTee.new(chef_config[:log_location])
|
42
|
-
@client = ::Cheffish::BasicChefClient.new(nil,
|
43
|
-
[ event_sink, Chef::Formatters.new(:doc, chef_config[:stdout], chef_config[:stderr]) ],
|
44
|
-
chef_config
|
45
|
-
)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def event_sink
|
50
|
-
@event_sink ||= EventSink.new
|
51
|
-
end
|
52
|
-
|
53
|
-
#
|
54
|
-
# output
|
55
|
-
#
|
56
|
-
def stdout
|
57
|
-
@client ? client.chef_config[:stdout].string : nil
|
58
|
-
end
|
59
|
-
def stderr
|
60
|
-
@client ? client.chef_config[:stderr].string : nil
|
61
|
-
end
|
62
|
-
def logs
|
63
|
-
@client ? client.chef_config[:log_location].string : nil
|
64
|
-
end
|
65
|
-
def logged_warnings
|
66
|
-
logs.lines.select { |l| l =~ /^\[[^\]]*\] WARN:/ }.join("\n")
|
67
|
-
end
|
68
|
-
def logged_errors
|
69
|
-
logs.lines.select { |l| l =~ /^\[[^\]]*\] ERROR:/ }.join("\n")
|
70
|
-
end
|
71
|
-
def logged_info
|
72
|
-
logs.lines.select { |l| l =~ /^\[[^\]]*\] INFO:/ }.join("\n")
|
73
|
-
end
|
74
|
-
|
75
|
-
def resources
|
76
|
-
client.resource_collection
|
77
|
-
end
|
78
|
-
|
79
|
-
def compile_recipe(&recipe)
|
80
|
-
client.load_block(&recipe)
|
81
|
-
end
|
82
|
-
|
83
|
-
def converge
|
84
|
-
begin
|
85
|
-
client.converge
|
86
|
-
@converged = true
|
87
|
-
rescue RuntimeError => e
|
88
|
-
@raised_exception = e
|
89
|
-
raise
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def reset
|
94
|
-
@client = nil
|
95
|
-
@converged = nil
|
96
|
-
@stdout = nil
|
97
|
-
@stderr = nil
|
98
|
-
@logs = nil
|
99
|
-
@raised_exception = nil
|
100
|
-
end
|
101
|
-
|
102
|
-
def converged?
|
103
|
-
!!@converged
|
104
|
-
end
|
105
|
-
|
106
|
-
def converge_failed?
|
107
|
-
@raised_exception.nil? ? false : true
|
108
|
-
end
|
109
|
-
|
110
|
-
def updated?
|
111
|
-
client.updated?
|
112
|
-
end
|
113
|
-
|
114
|
-
def up_to_date?
|
115
|
-
!client.updated?
|
116
|
-
end
|
117
|
-
|
118
|
-
def output_for_failure_message
|
119
|
-
message = ""
|
120
|
-
if stdout && !stdout.empty?
|
121
|
-
message << "--- ---\n"
|
122
|
-
message << "--- Chef Client Output ---\n"
|
123
|
-
message << "--- ---\n"
|
124
|
-
message << stdout
|
125
|
-
message << "\n" if !stdout.end_with?("\n")
|
126
|
-
end
|
127
|
-
if stderr && !stderr.empty?
|
128
|
-
message << "--- ---\n"
|
129
|
-
message << "--- Chef Client Error Output ---\n"
|
130
|
-
message << "--- ---\n"
|
131
|
-
message << stderr
|
132
|
-
message << "\n" if !stderr.end_with?("\n")
|
133
|
-
end
|
134
|
-
if logs && !logs.empty?
|
135
|
-
message << "--- ---\n"
|
136
|
-
message << "--- Chef Client Logs ---\n"
|
137
|
-
message << "--- ---\n"
|
138
|
-
message << logs
|
139
|
-
end
|
140
|
-
message
|
141
|
-
end
|
142
|
-
|
143
|
-
class EventSink
|
144
|
-
def initialize
|
145
|
-
@events = []
|
146
|
-
end
|
147
|
-
|
148
|
-
attr_reader :events
|
149
|
-
|
150
|
-
def method_missing(method, *args)
|
151
|
-
@events << [ method, *args ]
|
152
|
-
end
|
153
|
-
|
154
|
-
def respond_to_missing?(method_name, include_private = false)
|
155
|
-
# Chef::EventDispatch::Dispatcher calls #respond_to? to see (basically) if we'll accept an event;
|
156
|
-
# obviously, per above #method_missing, we'll accept whatever we're given. if there's a problem, it
|
157
|
-
# will surface higher up the stack.
|
158
|
-
true
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
1
|
+
require 'cheffish/basic_chef_client'
|
2
|
+
|
3
|
+
module Cheffish
|
4
|
+
class ChefRun
|
5
|
+
#
|
6
|
+
# @param chef_config A hash with symbol keys that looks suspiciously similar to `Chef::Config`.
|
7
|
+
# Some possible options:
|
8
|
+
# - stdout: <IO object> - where to stream stdout to
|
9
|
+
# - stderr: <IO object> - where to stream stderr to
|
10
|
+
# - log_level: :debug|:info|:warn|:error|:fatal
|
11
|
+
# - log_location: <path|IO object> - where to stream logs to
|
12
|
+
# - verbose_logging: true|false - true if you want verbose logging in :debug
|
13
|
+
#
|
14
|
+
def initialize(chef_config={})
|
15
|
+
@chef_config = chef_config || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :chef_config
|
19
|
+
|
20
|
+
class StringIOTee < StringIO
|
21
|
+
def initialize(*streams)
|
22
|
+
super()
|
23
|
+
@streams = streams.flatten.select { |s| !s.nil? }
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :streams
|
27
|
+
|
28
|
+
def write(*args, &block)
|
29
|
+
super
|
30
|
+
streams.each { |s| s.write(*args, &block) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def client
|
35
|
+
@client ||= begin
|
36
|
+
chef_config = self.chef_config.dup
|
37
|
+
chef_config[:log_level] ||= :debug if !chef_config.has_key?(:log_level)
|
38
|
+
chef_config[:verbose_logging] = false if !chef_config.has_key?(:verbose_logging)
|
39
|
+
chef_config[:stdout] = StringIOTee.new(chef_config[:stdout])
|
40
|
+
chef_config[:stderr] = StringIOTee.new(chef_config[:stderr])
|
41
|
+
chef_config[:log_location] = StringIOTee.new(chef_config[:log_location])
|
42
|
+
@client = ::Cheffish::BasicChefClient.new(nil,
|
43
|
+
[ event_sink, Chef::Formatters.new(:doc, chef_config[:stdout], chef_config[:stderr]) ],
|
44
|
+
chef_config
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def event_sink
|
50
|
+
@event_sink ||= EventSink.new
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# output
|
55
|
+
#
|
56
|
+
def stdout
|
57
|
+
@client ? client.chef_config[:stdout].string : nil
|
58
|
+
end
|
59
|
+
def stderr
|
60
|
+
@client ? client.chef_config[:stderr].string : nil
|
61
|
+
end
|
62
|
+
def logs
|
63
|
+
@client ? client.chef_config[:log_location].string : nil
|
64
|
+
end
|
65
|
+
def logged_warnings
|
66
|
+
logs.lines.select { |l| l =~ /^\[[^\]]*\] WARN:/ }.join("\n")
|
67
|
+
end
|
68
|
+
def logged_errors
|
69
|
+
logs.lines.select { |l| l =~ /^\[[^\]]*\] ERROR:/ }.join("\n")
|
70
|
+
end
|
71
|
+
def logged_info
|
72
|
+
logs.lines.select { |l| l =~ /^\[[^\]]*\] INFO:/ }.join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def resources
|
76
|
+
client.resource_collection
|
77
|
+
end
|
78
|
+
|
79
|
+
def compile_recipe(&recipe)
|
80
|
+
client.load_block(&recipe)
|
81
|
+
end
|
82
|
+
|
83
|
+
def converge
|
84
|
+
begin
|
85
|
+
client.converge
|
86
|
+
@converged = true
|
87
|
+
rescue RuntimeError => e
|
88
|
+
@raised_exception = e
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def reset
|
94
|
+
@client = nil
|
95
|
+
@converged = nil
|
96
|
+
@stdout = nil
|
97
|
+
@stderr = nil
|
98
|
+
@logs = nil
|
99
|
+
@raised_exception = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def converged?
|
103
|
+
!!@converged
|
104
|
+
end
|
105
|
+
|
106
|
+
def converge_failed?
|
107
|
+
@raised_exception.nil? ? false : true
|
108
|
+
end
|
109
|
+
|
110
|
+
def updated?
|
111
|
+
client.updated?
|
112
|
+
end
|
113
|
+
|
114
|
+
def up_to_date?
|
115
|
+
!client.updated?
|
116
|
+
end
|
117
|
+
|
118
|
+
def output_for_failure_message
|
119
|
+
message = ""
|
120
|
+
if stdout && !stdout.empty?
|
121
|
+
message << "--- ---\n"
|
122
|
+
message << "--- Chef Client Output ---\n"
|
123
|
+
message << "--- ---\n"
|
124
|
+
message << stdout
|
125
|
+
message << "\n" if !stdout.end_with?("\n")
|
126
|
+
end
|
127
|
+
if stderr && !stderr.empty?
|
128
|
+
message << "--- ---\n"
|
129
|
+
message << "--- Chef Client Error Output ---\n"
|
130
|
+
message << "--- ---\n"
|
131
|
+
message << stderr
|
132
|
+
message << "\n" if !stderr.end_with?("\n")
|
133
|
+
end
|
134
|
+
if logs && !logs.empty?
|
135
|
+
message << "--- ---\n"
|
136
|
+
message << "--- Chef Client Logs ---\n"
|
137
|
+
message << "--- ---\n"
|
138
|
+
message << logs
|
139
|
+
end
|
140
|
+
message
|
141
|
+
end
|
142
|
+
|
143
|
+
class EventSink
|
144
|
+
def initialize
|
145
|
+
@events = []
|
146
|
+
end
|
147
|
+
|
148
|
+
attr_reader :events
|
149
|
+
|
150
|
+
def method_missing(method, *args)
|
151
|
+
@events << [ method, *args ]
|
152
|
+
end
|
153
|
+
|
154
|
+
def respond_to_missing?(method_name, include_private = false)
|
155
|
+
# Chef::EventDispatch::Dispatcher calls #respond_to? to see (basically) if we'll accept an event;
|
156
|
+
# obviously, per above #method_missing, we'll accept whatever we're given. if there's a problem, it
|
157
|
+
# will surface higher up the stack.
|
158
|
+
true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
|
-
require 'chef/config'
|
2
|
-
require 'cheffish/with_pattern'
|
3
|
-
|
4
|
-
module Cheffish
|
5
|
-
class ChefRunData
|
6
|
-
def initialize(config)
|
7
|
-
@local_servers = []
|
8
|
-
@current_chef_server = Cheffish.default_chef_server(config)
|
9
|
-
end
|
10
|
-
|
11
|
-
extend Cheffish::WithPattern
|
12
|
-
with :data_bag
|
13
|
-
with :environment
|
14
|
-
with :data_bag_item_encryption
|
15
|
-
with :chef_server
|
16
|
-
|
17
|
-
attr_reader :local_servers
|
18
|
-
end
|
19
|
-
end
|
1
|
+
require 'chef/config'
|
2
|
+
require 'cheffish/with_pattern'
|
3
|
+
|
4
|
+
module Cheffish
|
5
|
+
class ChefRunData
|
6
|
+
def initialize(config)
|
7
|
+
@local_servers = []
|
8
|
+
@current_chef_server = Cheffish.default_chef_server(config)
|
9
|
+
end
|
10
|
+
|
11
|
+
extend Cheffish::WithPattern
|
12
|
+
with :data_bag
|
13
|
+
with :environment
|
14
|
+
with :data_bag_item_encryption
|
15
|
+
with :chef_server
|
16
|
+
|
17
|
+
attr_reader :local_servers
|
18
|
+
end
|
19
|
+
end
|
@@ -1,30 +1,30 @@
|
|
1
|
-
require 'chef/event_dispatch/base'
|
2
|
-
|
3
|
-
module Cheffish
|
4
|
-
class ChefRunListener < Chef::EventDispatch::Base
|
5
|
-
def initialize(node)
|
6
|
-
@node = node
|
7
|
-
end
|
8
|
-
|
9
|
-
attr_reader :node
|
10
|
-
|
11
|
-
def run_complete(node)
|
12
|
-
disconnect
|
13
|
-
end
|
14
|
-
|
15
|
-
def run_failed(exception)
|
16
|
-
disconnect
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def disconnect
|
22
|
-
# Stop the servers
|
23
|
-
if node.run_context
|
24
|
-
node.run_context.cheffish.local_servers.each do |server|
|
25
|
-
server.stop
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
1
|
+
require 'chef/event_dispatch/base'
|
2
|
+
|
3
|
+
module Cheffish
|
4
|
+
class ChefRunListener < Chef::EventDispatch::Base
|
5
|
+
def initialize(node)
|
6
|
+
@node = node
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :node
|
10
|
+
|
11
|
+
def run_complete(node)
|
12
|
+
disconnect
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_failed(exception)
|
16
|
+
disconnect
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def disconnect
|
22
|
+
# Stop the servers
|
23
|
+
if node.run_context
|
24
|
+
node.run_context.cheffish.local_servers.each do |server|
|
25
|
+
server.stop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,113 +1,113 @@
|
|
1
|
-
require 'openssl'
|
2
|
-
require 'net/ssh'
|
3
|
-
require 'etc'
|
4
|
-
require 'socket'
|
5
|
-
require 'digest/md5'
|
6
|
-
require 'base64'
|
7
|
-
|
8
|
-
module Cheffish
|
9
|
-
class KeyFormatter
|
10
|
-
# Returns nil or key, format
|
11
|
-
def self.decode(str, pass_phrase=nil, filename='')
|
12
|
-
key_format = {}
|
13
|
-
key_format[:format] = format_of(str)
|
14
|
-
|
15
|
-
case key_format[:format]
|
16
|
-
when :openssh
|
17
|
-
key = decode_openssh_key(str, filename)
|
18
|
-
else
|
19
|
-
begin
|
20
|
-
key = OpenSSL::PKey.read(str) { pass_phrase }
|
21
|
-
rescue
|
22
|
-
return nil
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
key_format[:type] = type_of(key)
|
27
|
-
key_format[:size] = size_of(key) if size_of(key)
|
28
|
-
key_format[:pass_phrase] = pass_phrase if pass_phrase
|
29
|
-
# TODO cipher, exponent
|
30
|
-
|
31
|
-
[key, key_format]
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.encode(key, key_format)
|
35
|
-
format = key_format[:format] || :pem
|
36
|
-
case format
|
37
|
-
when :openssh
|
38
|
-
encode_openssh_key(key)
|
39
|
-
when :pem
|
40
|
-
if key_format[:pass_phrase]
|
41
|
-
cipher = key_format[:cipher] || 'DES-EDE3-CBC'
|
42
|
-
key.to_pem(OpenSSL::Cipher.new(cipher), key_format[:pass_phrase])
|
43
|
-
else
|
44
|
-
key.to_pem
|
45
|
-
end
|
46
|
-
when :der
|
47
|
-
key.to_der
|
48
|
-
when :fingerprint, :pkcs1md5fingerprint
|
49
|
-
hexes = Digest::MD5.hexdigest(key.to_der)
|
50
|
-
# Put : between every pair of hexes
|
51
|
-
hexes.scan(/../).join(':')
|
52
|
-
when :rfc4716md5fingerprint
|
53
|
-
type, base64_data, etc = encode_openssh_key(key).split
|
54
|
-
data = Base64.decode64(base64_data)
|
55
|
-
hexes = Digest::MD5.hexdigest(data)
|
56
|
-
hexes.scan(/../).join(':')
|
57
|
-
when :pkcs8sha1fingerprint
|
58
|
-
if RUBY_VERSION.to_f >= 2.0
|
59
|
-
raise "PKCS8 SHA1 not supported in Ruby #{RUBY_VERSION}"
|
60
|
-
end
|
61
|
-
require 'openssl_pkcs8'
|
62
|
-
pkcs8_pem = key.to_pem_pkcs8
|
63
|
-
pkcs8_base64 = pkcs8_pem.split("\n").reject { |l| l =~ /^-----/ }
|
64
|
-
pkcs8_data = Base64.decode64(pkcs8_base64.join)
|
65
|
-
hexes = Digest::SHA1.hexdigest(pkcs8_data)
|
66
|
-
hexes.scan(/../).join(':')
|
67
|
-
else
|
68
|
-
raise "Unrecognized key format #{format}"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
def self.encode_openssh_key(key)
|
75
|
-
# TODO there really isn't a method somewhere in net/ssh or openssl that does this??
|
76
|
-
type = key.ssh_type
|
77
|
-
data = [ key.to_blob ].pack('m0')
|
78
|
-
"#{type} #{data} #{Etc.getlogin}@#{Socket.gethostname}"
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.decode_openssh_key(str, filename='')
|
82
|
-
Net::SSH::KeyFactory.load_data_public_key(str, filename)
|
83
|
-
end
|
84
|
-
|
85
|
-
def self.format_of(key_contents)
|
86
|
-
if key_contents.start_with?('-----BEGIN ')
|
87
|
-
:pem
|
88
|
-
elsif key_contents.start_with?('ssh-rsa ') || key_contents.start_with?('ssh-dss ')
|
89
|
-
:openssh
|
90
|
-
else
|
91
|
-
:der
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.type_of(key)
|
96
|
-
case key.class
|
97
|
-
when OpenSSL::PKey::RSA
|
98
|
-
:rsa
|
99
|
-
when OpenSSL::PKey::DSA
|
100
|
-
:dsa
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def self.size_of(key)
|
105
|
-
case key.class
|
106
|
-
when OpenSSL::PKey::RSA
|
107
|
-
key.n.num_bytes * 8
|
108
|
-
else
|
109
|
-
nil
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
1
|
+
require 'openssl'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'etc'
|
4
|
+
require 'socket'
|
5
|
+
require 'digest/md5'
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module Cheffish
|
9
|
+
class KeyFormatter
|
10
|
+
# Returns nil or key, format
|
11
|
+
def self.decode(str, pass_phrase=nil, filename='')
|
12
|
+
key_format = {}
|
13
|
+
key_format[:format] = format_of(str)
|
14
|
+
|
15
|
+
case key_format[:format]
|
16
|
+
when :openssh
|
17
|
+
key = decode_openssh_key(str, filename)
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
key = OpenSSL::PKey.read(str) { pass_phrase }
|
21
|
+
rescue
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
key_format[:type] = type_of(key)
|
27
|
+
key_format[:size] = size_of(key) if size_of(key)
|
28
|
+
key_format[:pass_phrase] = pass_phrase if pass_phrase
|
29
|
+
# TODO cipher, exponent
|
30
|
+
|
31
|
+
[key, key_format]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.encode(key, key_format)
|
35
|
+
format = key_format[:format] || :pem
|
36
|
+
case format
|
37
|
+
when :openssh
|
38
|
+
encode_openssh_key(key)
|
39
|
+
when :pem
|
40
|
+
if key_format[:pass_phrase]
|
41
|
+
cipher = key_format[:cipher] || 'DES-EDE3-CBC'
|
42
|
+
key.to_pem(OpenSSL::Cipher.new(cipher), key_format[:pass_phrase])
|
43
|
+
else
|
44
|
+
key.to_pem
|
45
|
+
end
|
46
|
+
when :der
|
47
|
+
key.to_der
|
48
|
+
when :fingerprint, :pkcs1md5fingerprint
|
49
|
+
hexes = Digest::MD5.hexdigest(key.to_der)
|
50
|
+
# Put : between every pair of hexes
|
51
|
+
hexes.scan(/../).join(':')
|
52
|
+
when :rfc4716md5fingerprint
|
53
|
+
type, base64_data, etc = encode_openssh_key(key).split
|
54
|
+
data = Base64.decode64(base64_data)
|
55
|
+
hexes = Digest::MD5.hexdigest(data)
|
56
|
+
hexes.scan(/../).join(':')
|
57
|
+
when :pkcs8sha1fingerprint
|
58
|
+
if RUBY_VERSION.to_f >= 2.0
|
59
|
+
raise "PKCS8 SHA1 not supported in Ruby #{RUBY_VERSION}"
|
60
|
+
end
|
61
|
+
require 'openssl_pkcs8'
|
62
|
+
pkcs8_pem = key.to_pem_pkcs8
|
63
|
+
pkcs8_base64 = pkcs8_pem.split("\n").reject { |l| l =~ /^-----/ }
|
64
|
+
pkcs8_data = Base64.decode64(pkcs8_base64.join)
|
65
|
+
hexes = Digest::SHA1.hexdigest(pkcs8_data)
|
66
|
+
hexes.scan(/../).join(':')
|
67
|
+
else
|
68
|
+
raise "Unrecognized key format #{format}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def self.encode_openssh_key(key)
|
75
|
+
# TODO there really isn't a method somewhere in net/ssh or openssl that does this??
|
76
|
+
type = key.ssh_type
|
77
|
+
data = [ key.to_blob ].pack('m0')
|
78
|
+
"#{type} #{data} #{Etc.getlogin}@#{Socket.gethostname}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.decode_openssh_key(str, filename='')
|
82
|
+
Net::SSH::KeyFactory.load_data_public_key(str, filename)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.format_of(key_contents)
|
86
|
+
if key_contents.start_with?('-----BEGIN ')
|
87
|
+
:pem
|
88
|
+
elsif key_contents.start_with?('ssh-rsa ') || key_contents.start_with?('ssh-dss ')
|
89
|
+
:openssh
|
90
|
+
else
|
91
|
+
:der
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.type_of(key)
|
96
|
+
case key.class
|
97
|
+
when OpenSSL::PKey::RSA
|
98
|
+
:rsa
|
99
|
+
when OpenSSL::PKey::DSA
|
100
|
+
:dsa
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.size_of(key)
|
105
|
+
case key.class
|
106
|
+
when OpenSSL::PKey::RSA
|
107
|
+
key.n.num_bytes * 8
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|