clc-cheffish 0.8.clc

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