cheffish 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|