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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -0
  3. data/LICENSE +201 -201
  4. data/README.md +120 -120
  5. data/Rakefile +23 -23
  6. data/cheffish.gemspec +26 -0
  7. data/lib/chef/provider/chef_acl.rb +446 -439
  8. data/lib/chef/provider/chef_client.rb +53 -53
  9. data/lib/chef/provider/chef_container.rb +55 -55
  10. data/lib/chef/provider/chef_data_bag.rb +55 -55
  11. data/lib/chef/provider/chef_data_bag_item.rb +278 -278
  12. data/lib/chef/provider/chef_environment.rb +83 -83
  13. data/lib/chef/provider/chef_group.rb +83 -83
  14. data/lib/chef/provider/chef_mirror.rb +169 -169
  15. data/lib/chef/provider/chef_node.rb +87 -87
  16. data/lib/chef/provider/chef_organization.rb +155 -155
  17. data/lib/chef/provider/chef_resolved_cookbooks.rb +46 -46
  18. data/lib/chef/provider/chef_role.rb +84 -84
  19. data/lib/chef/provider/chef_user.rb +59 -59
  20. data/lib/chef/provider/private_key.rb +225 -225
  21. data/lib/chef/provider/public_key.rb +88 -88
  22. data/lib/chef/resource/chef_acl.rb +69 -69
  23. data/lib/chef/resource/chef_client.rb +48 -48
  24. data/lib/chef/resource/chef_container.rb +22 -22
  25. data/lib/chef/resource/chef_data_bag.rb +22 -22
  26. data/lib/chef/resource/chef_data_bag_item.rb +121 -121
  27. data/lib/chef/resource/chef_environment.rb +77 -77
  28. data/lib/chef/resource/chef_group.rb +53 -53
  29. data/lib/chef/resource/chef_mirror.rb +52 -52
  30. data/lib/chef/resource/chef_node.rb +22 -22
  31. data/lib/chef/resource/chef_organization.rb +69 -69
  32. data/lib/chef/resource/chef_resolved_cookbooks.rb +35 -35
  33. data/lib/chef/resource/chef_role.rb +110 -110
  34. data/lib/chef/resource/chef_user.rb +56 -56
  35. data/lib/chef/resource/private_key.rb +48 -48
  36. data/lib/chef/resource/public_key.rb +25 -25
  37. data/lib/cheffish.rb +235 -235
  38. data/lib/cheffish/actor_provider_base.rb +131 -131
  39. data/lib/cheffish/basic_chef_client.rb +184 -184
  40. data/lib/cheffish/chef_provider_base.rb +246 -246
  41. data/lib/cheffish/chef_run.rb +162 -162
  42. data/lib/cheffish/chef_run_data.rb +19 -19
  43. data/lib/cheffish/chef_run_listener.rb +30 -30
  44. data/lib/cheffish/key_formatter.rb +113 -113
  45. data/lib/cheffish/merged_config.rb +98 -94
  46. data/lib/cheffish/recipe_dsl.rb +157 -157
  47. data/lib/cheffish/rspec.rb +8 -8
  48. data/lib/cheffish/rspec/chef_run_support.rb +83 -83
  49. data/lib/cheffish/rspec/matchers.rb +4 -4
  50. data/lib/cheffish/rspec/matchers/be_idempotent.rb +16 -16
  51. data/lib/cheffish/rspec/matchers/emit_no_warnings_or_errors.rb +15 -15
  52. data/lib/cheffish/rspec/matchers/have_updated.rb +37 -37
  53. data/lib/cheffish/rspec/matchers/partially_match.rb +63 -63
  54. data/lib/cheffish/rspec/recipe_run_wrapper.rb +78 -78
  55. data/lib/cheffish/rspec/repository_support.rb +108 -108
  56. data/lib/cheffish/server_api.rb +52 -52
  57. data/lib/cheffish/version.rb +3 -3
  58. data/lib/cheffish/with_pattern.rb +21 -21
  59. data/spec/functional/fingerprint_spec.rb +64 -64
  60. data/spec/functional/merged_config_spec.rb +19 -19
  61. data/spec/functional/server_api_spec.rb +13 -13
  62. data/spec/integration/chef_acl_spec.rb +892 -879
  63. data/spec/integration/chef_client_spec.rb +105 -105
  64. data/spec/integration/chef_container_spec.rb +33 -33
  65. data/spec/integration/chef_group_spec.rb +309 -309
  66. data/spec/integration/chef_mirror_spec.rb +491 -491
  67. data/spec/integration/chef_node_spec.rb +786 -786
  68. data/spec/integration/chef_organization_spec.rb +226 -226
  69. data/spec/integration/chef_role_spec.rb +78 -78
  70. data/spec/integration/chef_user_spec.rb +85 -85
  71. data/spec/integration/private_key_spec.rb +399 -399
  72. data/spec/integration/recipe_dsl_spec.rb +28 -28
  73. data/spec/integration/rspec/converge_spec.rb +183 -183
  74. data/spec/support/key_support.rb +29 -29
  75. data/spec/support/spec_support.rb +15 -15
  76. data/spec/unit/get_private_key_spec.rb +131 -131
  77. data/spec/unit/recipe_run_wrapper_spec.rb +37 -37
  78. metadata +7 -5
@@ -1,46 +1,46 @@
1
- require 'chef/provider/lwrp_base'
2
- require 'chef_zero'
3
-
4
- class Chef
5
- class Provider
6
- class ChefResolvedCookbooks < Chef::Provider::LWRPBase
7
- provides :chef_resolved_cookbooks
8
-
9
- action :resolve do
10
- new_resource.cookbooks_from.each do |path|
11
- ::Dir.entries(path).each do |name|
12
- if ::File.directory?(::File.join(path, name)) && name != '.' && name != '..'
13
- new_resource.berksfile.cookbook name, :path => ::File.join(path, name)
14
- end
15
- end
16
- end
17
-
18
- new_resource.berksfile.install
19
-
20
- # Ridley really really wants a key :/
21
- if new_resource.chef_server[:options][:signing_key_filename]
22
- new_resource.berksfile.upload(
23
- :server_url => new_resource.chef_server[:chef_server_url],
24
- :client_name => new_resource.chef_server[:options][:client_name],
25
- :client_key => new_resource.chef_server[:options][:signing_key_filename])
26
- else
27
- file = Tempfile.new('privatekey')
28
- begin
29
- file.write(ChefZero::PRIVATE_KEY)
30
- file.close
31
-
32
- new_resource.berksfile.upload(
33
- :server_url => new_resource.chef_server[:chef_server_url],
34
- :client_name => new_resource.chef_server[:options][:client_name] || 'me',
35
- :client_key => file.path)
36
-
37
- ensure
38
- file.close
39
- file.unlink
40
- end
41
- end
42
- end
43
-
44
- end
45
- end
46
- end
1
+ require 'chef/provider/lwrp_base'
2
+ require 'chef_zero'
3
+
4
+ class Chef
5
+ class Provider
6
+ class ChefResolvedCookbooks < Chef::Provider::LWRPBase
7
+ provides :chef_resolved_cookbooks
8
+
9
+ action :resolve do
10
+ new_resource.cookbooks_from.each do |path|
11
+ ::Dir.entries(path).each do |name|
12
+ if ::File.directory?(::File.join(path, name)) && name != '.' && name != '..'
13
+ new_resource.berksfile.cookbook name, :path => ::File.join(path, name)
14
+ end
15
+ end
16
+ end
17
+
18
+ new_resource.berksfile.install
19
+
20
+ # Ridley really really wants a key :/
21
+ if new_resource.chef_server[:options][:signing_key_filename]
22
+ new_resource.berksfile.upload(
23
+ :server_url => new_resource.chef_server[:chef_server_url],
24
+ :client_name => new_resource.chef_server[:options][:client_name],
25
+ :client_key => new_resource.chef_server[:options][:signing_key_filename])
26
+ else
27
+ file = Tempfile.new('privatekey')
28
+ begin
29
+ file.write(ChefZero::PRIVATE_KEY)
30
+ file.close
31
+
32
+ new_resource.berksfile.upload(
33
+ :server_url => new_resource.chef_server[:chef_server_url],
34
+ :client_name => new_resource.chef_server[:options][:client_name] || 'me',
35
+ :client_key => file.path)
36
+
37
+ ensure
38
+ file.close
39
+ file.unlink
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -1,84 +1,84 @@
1
- require 'cheffish/chef_provider_base'
2
- require 'chef/resource/chef_role'
3
- require 'chef/chef_fs/data_handler/role_data_handler'
4
-
5
- class Chef
6
- class Provider
7
- class ChefRole < Cheffish::ChefProviderBase
8
- provides :chef_role
9
-
10
- def whyrun_supported?
11
- true
12
- end
13
-
14
- action :create do
15
- differences = json_differences(current_json, new_json)
16
-
17
- if current_resource_exists?
18
- if differences.size > 0
19
- description = [ "update role #{new_resource.name} at #{rest.url}" ] + differences
20
- converge_by description do
21
- rest.put("roles/#{new_resource.name}", normalize_for_put(new_json))
22
- end
23
- end
24
- else
25
- description = [ "create role #{new_resource.name} at #{rest.url}" ] + differences
26
- converge_by description do
27
- rest.post("roles", normalize_for_post(new_json))
28
- end
29
- end
30
- end
31
-
32
- action :delete do
33
- if current_resource_exists?
34
- converge_by "delete role #{new_resource.name} at #{rest.url}" do
35
- rest.delete("roles/#{new_resource.name}")
36
- end
37
- end
38
- end
39
-
40
- def load_current_resource
41
- begin
42
- @current_resource = json_to_resource(rest.get("roles/#{new_resource.name}"))
43
- rescue Net::HTTPServerException => e
44
- if e.response.code == "404"
45
- @current_resource = not_found_resource
46
- else
47
- raise
48
- end
49
- end
50
- end
51
-
52
- def augment_new_json(json)
53
- # Apply modifiers
54
- json['run_list'] = apply_run_list_modifiers(new_resource.run_list_modifiers, new_resource.run_list_removers, json['run_list'])
55
- json['default_attributes'] = apply_modifiers(new_resource.default_attribute_modifiers, json['default_attributes'])
56
- json['override_attributes'] = apply_modifiers(new_resource.override_attribute_modifiers, json['override_attributes'])
57
- json
58
- end
59
-
60
- #
61
- # Helpers
62
- #
63
-
64
- def resource_class
65
- Chef::Resource::ChefRole
66
- end
67
-
68
- def data_handler
69
- Chef::ChefFS::DataHandler::RoleDataHandler.new
70
- end
71
-
72
- def keys
73
- {
74
- 'name' => :name,
75
- 'description' => :description,
76
- 'run_list' => :run_list,
77
- 'env_run_lists' => :env_run_lists,
78
- 'default_attributes' => :default_attributes,
79
- 'override_attributes' => :override_attributes
80
- }
81
- end
82
- end
83
- end
84
- end
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_role'
3
+ require 'chef/chef_fs/data_handler/role_data_handler'
4
+
5
+ class Chef
6
+ class Provider
7
+ class ChefRole < Cheffish::ChefProviderBase
8
+ provides :chef_role
9
+
10
+ def whyrun_supported?
11
+ true
12
+ end
13
+
14
+ action :create do
15
+ differences = json_differences(current_json, new_json)
16
+
17
+ if current_resource_exists?
18
+ if differences.size > 0
19
+ description = [ "update role #{new_resource.name} at #{rest.url}" ] + differences
20
+ converge_by description do
21
+ rest.put("roles/#{new_resource.name}", normalize_for_put(new_json))
22
+ end
23
+ end
24
+ else
25
+ description = [ "create role #{new_resource.name} at #{rest.url}" ] + differences
26
+ converge_by description do
27
+ rest.post("roles", normalize_for_post(new_json))
28
+ end
29
+ end
30
+ end
31
+
32
+ action :delete do
33
+ if current_resource_exists?
34
+ converge_by "delete role #{new_resource.name} at #{rest.url}" do
35
+ rest.delete("roles/#{new_resource.name}")
36
+ end
37
+ end
38
+ end
39
+
40
+ def load_current_resource
41
+ begin
42
+ @current_resource = json_to_resource(rest.get("roles/#{new_resource.name}"))
43
+ rescue Net::HTTPServerException => e
44
+ if e.response.code == "404"
45
+ @current_resource = not_found_resource
46
+ else
47
+ raise
48
+ end
49
+ end
50
+ end
51
+
52
+ def augment_new_json(json)
53
+ # Apply modifiers
54
+ json['run_list'] = apply_run_list_modifiers(new_resource.run_list_modifiers, new_resource.run_list_removers, json['run_list'])
55
+ json['default_attributes'] = apply_modifiers(new_resource.default_attribute_modifiers, json['default_attributes'])
56
+ json['override_attributes'] = apply_modifiers(new_resource.override_attribute_modifiers, json['override_attributes'])
57
+ json
58
+ end
59
+
60
+ #
61
+ # Helpers
62
+ #
63
+
64
+ def resource_class
65
+ Chef::Resource::ChefRole
66
+ end
67
+
68
+ def data_handler
69
+ Chef::ChefFS::DataHandler::RoleDataHandler.new
70
+ end
71
+
72
+ def keys
73
+ {
74
+ 'name' => :name,
75
+ 'description' => :description,
76
+ 'run_list' => :run_list,
77
+ 'env_run_lists' => :env_run_lists,
78
+ 'default_attributes' => :default_attributes,
79
+ 'override_attributes' => :override_attributes
80
+ }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,59 +1,59 @@
1
- require 'cheffish/actor_provider_base'
2
- require 'chef/resource/chef_user'
3
- require 'chef/chef_fs/data_handler/user_data_handler'
4
-
5
- class Chef
6
- class Provider
7
- class ChefUser < Cheffish::ActorProviderBase
8
- provides :chef_user
9
-
10
- def whyrun_supported?
11
- true
12
- end
13
-
14
- action :create do
15
- create_actor
16
- end
17
-
18
- action :delete do
19
- delete_actor
20
- end
21
-
22
- #
23
- # Helpers
24
- #
25
- # Gives us new_json, current_json, not_found_json, etc.
26
-
27
- def actor_type
28
- 'user'
29
- end
30
-
31
- def actor_path
32
- "#{rest.root_url}/users"
33
- end
34
-
35
- def resource_class
36
- Chef::Resource::ChefUser
37
- end
38
-
39
- def data_handler
40
- Chef::ChefFS::DataHandler::UserDataHandler.new
41
- end
42
-
43
- def keys
44
- {
45
- 'name' => :name,
46
- 'username' => :name,
47
- 'display_name' => :display_name,
48
- 'admin' => :admin,
49
- 'email' => :email,
50
- 'password' => :password,
51
- 'external_authentication_uid' => :external_authentication_uid,
52
- 'recovery_authentication_enabled' => :recovery_authentication_enabled,
53
- 'public_key' => :source_key
54
- }
55
- end
56
-
57
- end
58
- end
59
- end
1
+ require 'cheffish/actor_provider_base'
2
+ require 'chef/resource/chef_user'
3
+ require 'chef/chef_fs/data_handler/user_data_handler'
4
+
5
+ class Chef
6
+ class Provider
7
+ class ChefUser < Cheffish::ActorProviderBase
8
+ provides :chef_user
9
+
10
+ def whyrun_supported?
11
+ true
12
+ end
13
+
14
+ action :create do
15
+ create_actor
16
+ end
17
+
18
+ action :delete do
19
+ delete_actor
20
+ end
21
+
22
+ #
23
+ # Helpers
24
+ #
25
+ # Gives us new_json, current_json, not_found_json, etc.
26
+
27
+ def actor_type
28
+ 'user'
29
+ end
30
+
31
+ def actor_path
32
+ "#{rest.root_url}/users"
33
+ end
34
+
35
+ def resource_class
36
+ Chef::Resource::ChefUser
37
+ end
38
+
39
+ def data_handler
40
+ Chef::ChefFS::DataHandler::UserDataHandler.new
41
+ end
42
+
43
+ def keys
44
+ {
45
+ 'name' => :name,
46
+ 'username' => :name,
47
+ 'display_name' => :display_name,
48
+ 'admin' => :admin,
49
+ 'email' => :email,
50
+ 'password' => :password,
51
+ 'external_authentication_uid' => :external_authentication_uid,
52
+ 'recovery_authentication_enabled' => :recovery_authentication_enabled,
53
+ 'public_key' => :source_key
54
+ }
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -1,225 +1,225 @@
1
- require 'chef/provider/lwrp_base'
2
- require 'openssl'
3
- require 'cheffish/key_formatter'
4
-
5
- class Chef
6
- class Provider
7
- class PrivateKey < Chef::Provider::LWRPBase
8
- provides :private_key
9
-
10
- action :create do
11
- create_key(false, :create)
12
- end
13
-
14
- action :regenerate do
15
- create_key(true, :regenerate)
16
- end
17
-
18
- action :delete do
19
- if current_resource.path
20
- converge_by "delete private key #{new_path}" do
21
- ::File.unlink(new_path)
22
- end
23
- end
24
- end
25
-
26
- use_inline_resources
27
-
28
- def whyrun_supported?
29
- true
30
- end
31
-
32
- def create_key(regenerate, action)
33
- if @should_create_directory
34
- Cheffish.inline_resource(self, action) do
35
- directory run_context.config[:private_key_write_path]
36
- end
37
- end
38
-
39
- final_private_key = nil
40
- if new_source_key
41
- #
42
- # Create private key from source
43
- #
44
- desired_output = encode_private_key(new_source_key)
45
- if current_resource.path == :none || desired_output != IO.read(new_path)
46
- converge_by "reformat key at #{new_resource.source_key_path} to #{new_resource.format} private key #{new_path} (#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})" do
47
- IO.write(new_path, desired_output)
48
- end
49
- end
50
-
51
- final_private_key = new_source_key
52
-
53
- else
54
- #
55
- # Generate a new key
56
- #
57
- if current_resource.action == [ :delete ] || regenerate ||
58
- (new_resource.regenerate_if_different &&
59
- (!current_private_key ||
60
- current_resource.size != new_resource.size ||
61
- current_resource.type != new_resource.type))
62
-
63
- case new_resource.type
64
- when :rsa
65
- if new_resource.exponent
66
- final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size, new_resource.exponent)
67
- else
68
- final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size)
69
- end
70
- when :dsa
71
- final_private_key = OpenSSL::PKey::DSA.generate(new_resource.size)
72
- end
73
-
74
- generated_key = true
75
- elsif !current_private_key
76
- raise "Could not read private key from #{current_resource.path}: missing pass phrase?"
77
- else
78
- final_private_key = current_private_key
79
- generated_key = false
80
- end
81
-
82
- if generated_key
83
- generated_description = " (#{new_resource.size} bits#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})"
84
-
85
- if new_path != :none
86
- action = current_resource.path == :none ? 'create' : 'overwrite'
87
- converge_by "#{action} #{new_resource.type} private key #{new_path}#{generated_description}" do
88
- write_private_key(final_private_key)
89
- end
90
- else
91
- converge_by "generate private key#{generated_description}" do
92
- end
93
- end
94
- else
95
- # Warn if existing key has different characteristics than expected
96
- if current_resource.size != new_resource.size
97
- Chef::Log.warn("Mismatched key size! #{current_resource.path} is #{current_resource.size} bytes, desired is #{new_resource.size} bytes. Use action :regenerate to force key regeneration.")
98
- elsif current_resource.type != new_resource.type
99
- Chef::Log.warn("Mismatched key type! #{current_resource.path} is #{current_resource.type}, desired is #{new_resource.type} bytes. Use action :regenerate to force key regeneration.")
100
- end
101
-
102
- if current_resource.format != new_resource.format
103
- converge_by "change format of #{new_resource.type} private key #{new_path} from #{current_resource.format} to #{new_resource.format}" do
104
- write_private_key(current_private_key)
105
- end
106
- elsif (@current_file_mode & 0077) != 0
107
- new_mode = @current_file_mode & 07700
108
- converge_by "change mode of private key #{new_path} to #{new_mode.to_s(8)}" do
109
- ::File.chmod(new_mode, new_path)
110
- end
111
- end
112
- end
113
- end
114
-
115
- if new_resource.public_key_path
116
- public_key_path = new_resource.public_key_path
117
- public_key_format = new_resource.public_key_format
118
- Cheffish.inline_resource(self, action) do
119
- public_key public_key_path do
120
- source_key final_private_key
121
- format public_key_format
122
- end
123
- end
124
- end
125
-
126
- if new_resource.after
127
- new_resource.after.call(new_resource, final_private_key)
128
- end
129
- end
130
-
131
- def encode_private_key(key)
132
- key_format = {}
133
- key_format[:format] = new_resource.format if new_resource.format
134
- key_format[:pass_phrase] = new_resource.pass_phrase if new_resource.pass_phrase
135
- key_format[:cipher] = new_resource.cipher if new_resource.cipher
136
- Cheffish::KeyFormatter.encode(key, key_format)
137
- end
138
-
139
- def write_private_key(key)
140
- ::File.open(new_path, 'w') do |file|
141
- file.chmod(0600)
142
- file.write(encode_private_key(key))
143
- end
144
- end
145
-
146
- def new_source_key
147
- @new_source_key ||= begin
148
- if new_resource.source_key.is_a?(String)
149
- source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
150
- source_key
151
- elsif new_resource.source_key
152
- new_resource.source_key
153
- elsif new_resource.source_key_path
154
- source_key, source_key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.source_key_path), new_resource.source_key_pass_phrase, new_resource.source_key_path)
155
- source_key
156
- else
157
- nil
158
- end
159
- end
160
- end
161
-
162
- attr_reader :current_private_key
163
-
164
- def new_path
165
- new_key_with_path[1]
166
- end
167
-
168
- def new_key_with_path
169
- path = new_resource.path
170
- if path.is_a?(Symbol)
171
- return [ nil, path ]
172
- elsif Pathname.new(path).relative?
173
- private_key, private_key_path = Cheffish.get_private_key_with_path(path, run_context.config)
174
- if private_key
175
- return [ private_key, (private_key_path || :none) ]
176
- elsif run_context.config[:private_key_write_path]
177
- @should_create_directory = true
178
- path = ::File.join(run_context.config[:private_key_write_path], path)
179
- return [ nil, path ]
180
- else
181
- raise "Could not find key #{path} and Chef::Config.private_key_write_path is not set."
182
- end
183
- elsif ::File.exist?(path)
184
- return [ IO.read(path), path ]
185
- else
186
- return [ nil, path ]
187
- end
188
- end
189
-
190
- def load_current_resource
191
- resource = Chef::Resource::PrivateKey.new(new_resource.name, run_context)
192
-
193
- new_key, new_path = new_key_with_path
194
- if new_path != :none && ::File.exist?(new_path)
195
- resource.path new_path
196
- @current_file_mode = ::File.stat(new_path).mode
197
- else
198
- resource.path :none
199
- end
200
-
201
- if new_key
202
- begin
203
- key, key_format = Cheffish::KeyFormatter.decode(new_key, new_resource.pass_phrase, new_path)
204
- if key
205
- @current_private_key = key
206
- resource.format key_format[:format]
207
- resource.type key_format[:type]
208
- resource.size key_format[:size]
209
- resource.exponent key_format[:exponent]
210
- resource.pass_phrase key_format[:pass_phrase]
211
- resource.cipher key_format[:cipher]
212
- end
213
- rescue
214
- # If there's an error reading, we assume format and type are wrong and don't futz with them
215
- Chef::Log.warn("Error reading #{new_path}: #{$!}")
216
- end
217
- else
218
- resource.action :delete
219
- end
220
-
221
- @current_resource = resource
222
- end
223
- end
224
- end
225
- end
1
+ require 'chef/provider/lwrp_base'
2
+ require 'openssl'
3
+ require 'cheffish/key_formatter'
4
+
5
+ class Chef
6
+ class Provider
7
+ class PrivateKey < Chef::Provider::LWRPBase
8
+ provides :private_key
9
+
10
+ action :create do
11
+ create_key(false, :create)
12
+ end
13
+
14
+ action :regenerate do
15
+ create_key(true, :regenerate)
16
+ end
17
+
18
+ action :delete do
19
+ if current_resource.path
20
+ converge_by "delete private key #{new_path}" do
21
+ ::File.unlink(new_path)
22
+ end
23
+ end
24
+ end
25
+
26
+ use_inline_resources
27
+
28
+ def whyrun_supported?
29
+ true
30
+ end
31
+
32
+ def create_key(regenerate, action)
33
+ if @should_create_directory
34
+ Cheffish.inline_resource(self, action) do
35
+ directory run_context.config[:private_key_write_path]
36
+ end
37
+ end
38
+
39
+ final_private_key = nil
40
+ if new_source_key
41
+ #
42
+ # Create private key from source
43
+ #
44
+ desired_output = encode_private_key(new_source_key)
45
+ if current_resource.path == :none || desired_output != IO.read(new_path)
46
+ converge_by "reformat key at #{new_resource.source_key_path} to #{new_resource.format} private key #{new_path} (#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})" do
47
+ IO.write(new_path, desired_output)
48
+ end
49
+ end
50
+
51
+ final_private_key = new_source_key
52
+
53
+ else
54
+ #
55
+ # Generate a new key
56
+ #
57
+ if current_resource.action == [ :delete ] || regenerate ||
58
+ (new_resource.regenerate_if_different &&
59
+ (!current_private_key ||
60
+ current_resource.size != new_resource.size ||
61
+ current_resource.type != new_resource.type))
62
+
63
+ case new_resource.type
64
+ when :rsa
65
+ if new_resource.exponent
66
+ final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size, new_resource.exponent)
67
+ else
68
+ final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size)
69
+ end
70
+ when :dsa
71
+ final_private_key = OpenSSL::PKey::DSA.generate(new_resource.size)
72
+ end
73
+
74
+ generated_key = true
75
+ elsif !current_private_key
76
+ raise "Could not read private key from #{current_resource.path}: missing pass phrase?"
77
+ else
78
+ final_private_key = current_private_key
79
+ generated_key = false
80
+ end
81
+
82
+ if generated_key
83
+ generated_description = " (#{new_resource.size} bits#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})"
84
+
85
+ if new_path != :none
86
+ action = current_resource.path == :none ? 'create' : 'overwrite'
87
+ converge_by "#{action} #{new_resource.type} private key #{new_path}#{generated_description}" do
88
+ write_private_key(final_private_key)
89
+ end
90
+ else
91
+ converge_by "generate private key#{generated_description}" do
92
+ end
93
+ end
94
+ else
95
+ # Warn if existing key has different characteristics than expected
96
+ if current_resource.size != new_resource.size
97
+ Chef::Log.warn("Mismatched key size! #{current_resource.path} is #{current_resource.size} bytes, desired is #{new_resource.size} bytes. Use action :regenerate to force key regeneration.")
98
+ elsif current_resource.type != new_resource.type
99
+ Chef::Log.warn("Mismatched key type! #{current_resource.path} is #{current_resource.type}, desired is #{new_resource.type} bytes. Use action :regenerate to force key regeneration.")
100
+ end
101
+
102
+ if current_resource.format != new_resource.format
103
+ converge_by "change format of #{new_resource.type} private key #{new_path} from #{current_resource.format} to #{new_resource.format}" do
104
+ write_private_key(current_private_key)
105
+ end
106
+ elsif (@current_file_mode & 0077) != 0
107
+ new_mode = @current_file_mode & 07700
108
+ converge_by "change mode of private key #{new_path} to #{new_mode.to_s(8)}" do
109
+ ::File.chmod(new_mode, new_path)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ if new_resource.public_key_path
116
+ public_key_path = new_resource.public_key_path
117
+ public_key_format = new_resource.public_key_format
118
+ Cheffish.inline_resource(self, action) do
119
+ public_key public_key_path do
120
+ source_key final_private_key
121
+ format public_key_format
122
+ end
123
+ end
124
+ end
125
+
126
+ if new_resource.after
127
+ new_resource.after.call(new_resource, final_private_key)
128
+ end
129
+ end
130
+
131
+ def encode_private_key(key)
132
+ key_format = {}
133
+ key_format[:format] = new_resource.format if new_resource.format
134
+ key_format[:pass_phrase] = new_resource.pass_phrase if new_resource.pass_phrase
135
+ key_format[:cipher] = new_resource.cipher if new_resource.cipher
136
+ Cheffish::KeyFormatter.encode(key, key_format)
137
+ end
138
+
139
+ def write_private_key(key)
140
+ ::File.open(new_path, 'w') do |file|
141
+ file.chmod(0600)
142
+ file.write(encode_private_key(key))
143
+ end
144
+ end
145
+
146
+ def new_source_key
147
+ @new_source_key ||= begin
148
+ if new_resource.source_key.is_a?(String)
149
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
150
+ source_key
151
+ elsif new_resource.source_key
152
+ new_resource.source_key
153
+ elsif new_resource.source_key_path
154
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.source_key_path), new_resource.source_key_pass_phrase, new_resource.source_key_path)
155
+ source_key
156
+ else
157
+ nil
158
+ end
159
+ end
160
+ end
161
+
162
+ attr_reader :current_private_key
163
+
164
+ def new_path
165
+ new_key_with_path[1]
166
+ end
167
+
168
+ def new_key_with_path
169
+ path = new_resource.path
170
+ if path.is_a?(Symbol)
171
+ return [ nil, path ]
172
+ elsif Pathname.new(path).relative?
173
+ private_key, private_key_path = Cheffish.get_private_key_with_path(path, run_context.config)
174
+ if private_key
175
+ return [ private_key, (private_key_path || :none) ]
176
+ elsif run_context.config[:private_key_write_path]
177
+ @should_create_directory = true
178
+ path = ::File.join(run_context.config[:private_key_write_path], path)
179
+ return [ nil, path ]
180
+ else
181
+ raise "Could not find key #{path} and Chef::Config.private_key_write_path is not set."
182
+ end
183
+ elsif ::File.exist?(path)
184
+ return [ IO.read(path), path ]
185
+ else
186
+ return [ nil, path ]
187
+ end
188
+ end
189
+
190
+ def load_current_resource
191
+ resource = Chef::Resource::PrivateKey.new(new_resource.name, run_context)
192
+
193
+ new_key, new_path = new_key_with_path
194
+ if new_path != :none && ::File.exist?(new_path)
195
+ resource.path new_path
196
+ @current_file_mode = ::File.stat(new_path).mode
197
+ else
198
+ resource.path :none
199
+ end
200
+
201
+ if new_key
202
+ begin
203
+ key, key_format = Cheffish::KeyFormatter.decode(new_key, new_resource.pass_phrase, new_path)
204
+ if key
205
+ @current_private_key = key
206
+ resource.format key_format[:format]
207
+ resource.type key_format[:type]
208
+ resource.size key_format[:size]
209
+ resource.exponent key_format[:exponent]
210
+ resource.pass_phrase key_format[:pass_phrase]
211
+ resource.cipher key_format[:cipher]
212
+ end
213
+ rescue
214
+ # If there's an error reading, we assume format and type are wrong and don't futz with them
215
+ Chef::Log.warn("Error reading #{new_path}: #{$!}")
216
+ end
217
+ else
218
+ resource.action :delete
219
+ end
220
+
221
+ @current_resource = resource
222
+ end
223
+ end
224
+ end
225
+ end