chef-encrypted-attributes 0.3.0 → 0.4.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 (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +8 -0
  5. data/CHANGELOG.md +40 -4
  6. data/CONTRIBUTING.md +7 -6
  7. data/KNIFE.md +151 -0
  8. data/README.md +70 -192
  9. data/Rakefile +27 -14
  10. data/TESTING.md +18 -7
  11. data/TODO.md +2 -5
  12. data/lib/chef-encrypted-attributes.rb +7 -1
  13. data/lib/chef/encrypted_attribute.rb +282 -121
  14. data/lib/chef/encrypted_attribute/api.rb +521 -0
  15. data/lib/chef/encrypted_attribute/assertions.rb +16 -6
  16. data/lib/chef/encrypted_attribute/cache_lru.rb +54 -13
  17. data/lib/chef/encrypted_attribute/config.rb +198 -89
  18. data/lib/chef/encrypted_attribute/encrypted_mash.rb +127 -33
  19. data/lib/chef/encrypted_attribute/encrypted_mash/version0.rb +236 -48
  20. data/lib/chef/encrypted_attribute/encrypted_mash/version1.rb +249 -36
  21. data/lib/chef/encrypted_attribute/encrypted_mash/version2.rb +133 -19
  22. data/lib/chef/encrypted_attribute/exceptions.rb +19 -3
  23. data/lib/chef/encrypted_attribute/local_node.rb +15 -4
  24. data/lib/chef/encrypted_attribute/remote_clients.rb +33 -17
  25. data/lib/chef/encrypted_attribute/remote_node.rb +84 -29
  26. data/lib/chef/encrypted_attribute/remote_nodes.rb +62 -11
  27. data/lib/chef/encrypted_attribute/remote_users.rb +58 -19
  28. data/lib/chef/encrypted_attribute/search_helper.rb +214 -74
  29. data/lib/chef/encrypted_attribute/version.rb +3 -1
  30. data/lib/chef/encrypted_attributes.rb +20 -0
  31. data/lib/chef/knife/core/config.rb +4 -1
  32. data/lib/chef/knife/core/encrypted_attribute_base.rb +179 -0
  33. data/lib/chef/knife/core/encrypted_attribute_depends.rb +43 -0
  34. data/lib/chef/knife/core/encrypted_attribute_editor_options.rb +125 -61
  35. data/lib/chef/knife/encrypted_attribute_create.rb +51 -31
  36. data/lib/chef/knife/encrypted_attribute_delete.rb +32 -40
  37. data/lib/chef/knife/encrypted_attribute_edit.rb +51 -32
  38. data/lib/chef/knife/encrypted_attribute_show.rb +30 -55
  39. data/lib/chef/knife/encrypted_attribute_update.rb +43 -28
  40. data/spec/benchmark_helper.rb +2 -1
  41. data/spec/integration_helper.rb +1 -0
  42. data/spec/spec_helper.rb +21 -7
  43. metadata +75 -36
  44. metadata.gz.sig +1 -1
  45. data/API.md +0 -174
  46. data/INTERNAL.md +0 -166
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  #
2
3
  # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
4
  # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
@@ -20,17 +21,26 @@ require 'chef/encrypted_attribute/exceptions'
20
21
 
21
22
  class Chef
22
23
  class EncryptedAttribute
24
+ # Include some assertions that throw exceptions if not met.
23
25
  module Assertions
24
-
26
+ # Checks some assertions related with OpenSSL AEAD support, required to
27
+ # to use [GCM](http://en.wikipedia.org/wiki/Galois/Counter_Mode).
28
+ #
29
+ # @param algorithm [String] the name of the algorithm to use.
30
+ # @return void
31
+ # @raise [RequirementsFailure] if any of the requirements to use AEAD is
32
+ # not met.
25
33
  def assert_aead_requirements_met!(algorithm)
26
34
  unless OpenSSL::Cipher.method_defined?(:auth_data=)
27
- raise RequirementsFailure, 'The used Encrypted Attributes protocol version requires Ruby >= 1.9'
28
- end
29
- unless OpenSSL::Cipher.ciphers.include?(algorithm)
30
- raise RequirementsFailure, "The used Encrypted Attributes protocol version requires an OpenSSL version with \"#{algorithm}\" algorithm support"
35
+ fail RequirementsFailure,
36
+ 'The used Encrypted Attributes protocol version requires Ruby '\
37
+ '>= 1.9'
31
38
  end
39
+ return if OpenSSL::Cipher.ciphers.include?(algorithm)
40
+ fail RequirementsFailure,
41
+ 'The used Encrypted Attributes protocol version requires an '\
42
+ "OpenSSL version with \"#{algorithm}\" algorithm support"
32
43
  end
33
-
34
44
  end
35
45
  end
36
46
  end
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  #
2
3
  # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
4
  # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
@@ -18,33 +19,63 @@
18
19
 
19
20
  require 'chef/mixin/params_validate'
20
21
 
21
- # Based on https://github.com/SamSaffron/lru_redux
22
22
  class Chef
23
23
  class EncryptedAttribute
24
+ # Implements an LRU (Least Recently Used) cache object.
25
+ #
26
+ # The LRU cache algorithm discards the least recently used items first.
27
+ #
28
+ # This class extends from *Hash* class and adds methods to behave as a
29
+ # cache.
30
+ #
31
+ # You can use the `clear` class method to clean a cache:
32
+ #
33
+ # ```ruby
34
+ # Chef::EncryptedAttribute::RemoteClients.cache.clear
35
+ # Chef::EncryptedAttribute::RemoteNodes.cache.clear
36
+ # Chef::EncryptedAttribute::RemoteUsers.cache.clear
37
+ # Chef::EncryptedAttribute::RemoteNode.cache.clear
38
+ # ```
39
+ #
40
+ # @note Based on [SamSaffron](https://github.com/SamSaffron) work:
41
+ # https://github.com/SamSaffron/lru_redux
42
+ # @see API
24
43
  class CacheLru < Hash
25
44
  include ::Chef::Mixin::ParamsValidate
26
45
 
27
- def initialize(size=nil)
46
+ # Constructs a new Cache LRU object.
47
+ #
48
+ # @param size [Fixnum] Cache maximum size in object count.
49
+ def initialize(size = nil)
28
50
  super
29
51
  max_size(size)
30
52
  end
31
53
 
32
- def max_size(arg=nil)
54
+ # Reads or sets the cache maximum size.
55
+ #
56
+ # Removes some values if needed (when the size is reduced).
57
+ #
58
+ # The cache size is `1024` by default.
59
+ #
60
+ # @param arg [Fixnum] cache maximum size to set.
61
+ # @return [Fixnum] cache maximum size.
62
+ def max_size(arg = nil)
33
63
  set_or_return(
34
64
  :max_size,
35
65
  arg,
36
- :kind_of => [ Fixnum ],
37
- :default => 1024,
38
- :callbacks => begin
39
- { 'should not be lower that zero' => lambda { |x| x >= 0 } }
40
- end
66
+ kind_of: [Fixnum], default: 1024,
67
+ callbacks: { 'should not be lower that zero' => ->(x) { x >= 0 } }
41
68
  )
42
69
  pop_tail unless arg.nil?
43
70
  @max_size
44
71
  end
45
72
 
73
+ # Reads a cache key.
74
+ #
75
+ # @param key [String, Symbol] cache key to read.
76
+ # @return [Mixed] cache key value.
46
77
  def [](key)
47
- if has_key?(key)
78
+ if key?(key)
48
79
  val = super(key)
49
80
  self[key] = val
50
81
  else
@@ -52,6 +83,14 @@ class Chef
52
83
  end
53
84
  end
54
85
 
86
+ # Sets a cache key.
87
+ #
88
+ # Some keys will be removed if the cache size grows too much. The keys to
89
+ # be removed will be chosen using the LRU algorithm.
90
+ #
91
+ # @param key [String, Symbol] cache key to set.
92
+ # @param val [Mixed] cache key value.
93
+ # @return [Mixed] cache key value.
55
94
  def []=(key, val)
56
95
  if max_size > 0 # unnecessary "if", small optimization?
57
96
  delete(key)
@@ -63,12 +102,14 @@ class Chef
63
102
 
64
103
  protected
65
104
 
105
+ # Removes the tail elements until the size is correct.
106
+ #
107
+ # This method is needed to implement the LRU algorithm.
108
+ #
109
+ # @return void
66
110
  def pop_tail
67
- while size > max_size
68
- delete(first[0])
69
- end
111
+ delete(first[0]) while size > max_size
70
112
  end
71
-
72
113
  end
73
114
  end
74
115
  end
@@ -1,3 +1,4 @@
1
+ # encoding: UTF-8
1
2
  #
2
3
  # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
4
  # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
@@ -20,137 +21,191 @@ require 'chef/mixin/params_validate'
20
21
 
21
22
  class Chef
22
23
  class EncryptedAttribute
24
+ # Encrypted attributes configuration options object.
23
25
  class Config
24
26
  include ::Chef::Mixin::ParamsValidate
25
27
 
28
+ # Returns configuration options list.
29
+ #
30
+ # @api private
26
31
  OPTIONS = [
27
32
  :version,
28
33
  :partial_search,
29
34
  :client_search,
30
35
  :node_search,
31
36
  :users,
32
- :keys,
37
+ :keys
33
38
  ].freeze
34
39
 
35
- def initialize(config=nil)
40
+ # Constructs a {Config} object.
41
+ #
42
+ # @param config [Config, Hash] configuration object to clone.
43
+ def initialize(config = nil)
36
44
  update!(config) unless config.nil?
37
45
  end
38
46
 
39
- def version(arg=nil)
40
- unless arg.nil? or not arg.kind_of?(String)
41
- arg = Integer(arg) rescue arg
47
+ # Reads or sets Encrypted Mash protocol version.
48
+ #
49
+ # @param arg [String, Fixnum] protocol version to use. Must be a number.
50
+ # @return [Fixnum] protocol version.
51
+ def version(arg = nil)
52
+ unless arg.nil? || !arg.is_a?(String)
53
+ begin
54
+ arg = Integer(arg)
55
+ rescue ArgumentError
56
+ arg
57
+ end
42
58
  end
43
- set_or_return(
44
- :version,
45
- arg,
46
- :kind_of => [ Fixnum, String ],
47
- :default => 1
48
- )
59
+ set_or_return(:version, arg, kind_of: [Fixnum, String], default: 1)
49
60
  end
50
61
 
51
- def partial_search(arg=nil)
62
+ # Reads or sets partial search support.
63
+ #
64
+ # Set it to `false` to disable partial search. Defaults to `true`.
65
+ #
66
+ # @param arg [Boolean] whether to enable partial search.
67
+ # @return [Boolean] partial search usage.
68
+ # @see
69
+ # http://docs.getchef.com/chef_search.html Chef Search documentation
70
+ def partial_search(arg = nil)
52
71
  set_or_return(
53
- :partial_search,
54
- arg,
55
- :kind_of => [ TrueClass, FalseClass ],
56
- :default => true
72
+ :partial_search, arg, kind_of: [TrueClass, FalseClass], default: true
57
73
  )
58
74
  end
59
75
 
60
- def client_search(arg=nil)
61
- unless arg.nil? or not arg.kind_of?(String)
62
- arg = [ arg ]
63
- end
64
- set_or_return(
65
- :client_search,
66
- arg,
67
- :kind_of => Array,
68
- :default => [],
69
- :callbacks => config_search_array_callbacks
70
- )
76
+ # Reads or sets client search query.
77
+ #
78
+ # This query will return a list of clients that will be able to read the
79
+ # encrypted attribute.
80
+ #
81
+ # @param arg [String, Array<String>] list of client queries to perform.
82
+ # @return [Array<String>] list of client queries.
83
+ # @see
84
+ # http://docs.getchef.com/chef_search.html Chef Search documentation
85
+ def client_search(arg = nil)
86
+ set_or_return_search_array(:client_search, arg)
71
87
  end
72
88
 
73
- def node_search(arg=nil)
74
- unless arg.nil? or not arg.kind_of?(String)
75
- arg = [ arg ]
76
- end
77
- set_or_return(
78
- :node_search,
79
- arg,
80
- :kind_of => Array,
81
- :default => [],
82
- :callbacks => config_search_array_callbacks
83
- )
89
+ # Reads or sets node search query.
90
+ #
91
+ # This query will return a list of nodes that will be able to read the
92
+ # encrypted attribute.
93
+ #
94
+ # @param arg [String, Array<String>] list of node queries to perform.
95
+ # @return [Array<String>] list of node queries.
96
+ def node_search(arg = nil)
97
+ set_or_return_search_array(:node_search, arg)
84
98
  end
85
99
 
86
- def users(arg=nil)
100
+ # Reads or sets user list.
101
+ #
102
+ # This contains the user list that will be able to read the encrypted
103
+ # attribute.
104
+ #
105
+ # @param arg [String, Array<String>] list of users to set.
106
+ # @return [Array<String>] list of users.
107
+ def users(arg = nil)
87
108
  set_or_return(
88
- :users,
89
- arg,
90
- :kind_of => [ String, Array ],
91
- :default => [],
92
- :callbacks => config_users_arg_callbacks
109
+ :users, arg,
110
+ kind_of: [String, Array], default: [],
111
+ callbacks: config_users_arg_callbacks
93
112
  )
94
113
  end
95
114
 
96
- def keys(arg=nil)
115
+ # Reads or sets key list.
116
+ #
117
+ # This contains the raw key list that will be able to read the encrypted
118
+ # attribute.
119
+ #
120
+ # @param arg [Array<String, OpenSSL::PKey::RSA>] the keys in PEM format.
121
+ # @return [Array<String, OpenSSL::PKey::RSA>] the keys in PEM format
122
+ def keys(arg = nil)
97
123
  set_or_return(
98
- :keys,
99
- arg,
100
- :kind_of => Array,
101
- :default => [],
102
- :callbacks => config_valid_keys_array_callbacks
124
+ :keys, arg,
125
+ kind_of: Array, default: [],
126
+ callbacks: config_valid_keys_array_callbacks
103
127
  )
104
128
  end
105
129
 
130
+ # Replaces the current config.
131
+ #
132
+ # When setting using a {Chef::EncryptedAttribute::Config} class, all the
133
+ # configuration options will be replaced.
134
+ #
135
+ # When setting using a _Hash_, only the provided keys will be replaced.
136
+ #
137
+ # @param config [Config, Hash] the configuration to set.
138
+ # @return [Config] `self`.
106
139
  def update!(config)
107
- if config.kind_of?(self.class)
108
- OPTIONS.each do |attr|
109
- value = dup_object(config.send(attr))
110
- self.instance_variable_set("@#{attr.to_s}", value)
111
- end
112
- elsif config.kind_of?(Hash)
113
- config.each do |attr, value|
114
- attr = attr.to_sym if attr.kind_of?(String)
115
- if OPTIONS.include?(attr)
116
- value = dup_object(value)
117
- self.send(attr, value)
118
- else
119
- Chef::Log.warn("#{self.class.to_s}: configuration method not found: \"#{attr.to_s}\".")
120
- end
121
- end
140
+ if config.is_a?(self.class)
141
+ update_from_config!(config)
142
+ elsif config.is_a?(Hash)
143
+ update_from_hash!(config)
122
144
  end
123
145
  end
124
146
 
147
+ # Reads a configuration option.
148
+ #
149
+ # @param key [String, Symbol] configuration option to read.
150
+ # @return [Mixed] configuration value.
125
151
  def [](key)
126
- key = key.to_sym if key.kind_of?(String)
127
- if OPTIONS.include?(key)
128
- self.send(key)
129
- end
152
+ key = key.to_sym if key.is_a?(String)
153
+ send(key) if OPTIONS.include?(key)
130
154
  end
131
155
 
156
+ # Sets a configuration option.
157
+ #
158
+ # @param key [String, Symbol] configuration option name to set.
159
+ # @param value [Mixed] configuration value to set.
160
+ # @return [Mixed] configuration value.
132
161
  def []=(key, value)
133
- key = key.to_sym if key.kind_of?(String)
134
- if OPTIONS.include?(key)
135
- self.send(key, value)
136
- end
162
+ key = key.to_sym if key.is_a?(String)
163
+ send(key, value) if OPTIONS.include?(key)
137
164
  end
138
165
 
139
166
  protected
140
167
 
168
+ # Duplicates an object avoiding Ruby exceptions if not supported.
169
+ #
170
+ # @param o [Object] object to duplicate.
171
+ # @return [Object] duplicated object.
141
172
  def dup_object(o)
142
173
  o.dup
143
174
  rescue TypeError
144
175
  o
145
176
  end
146
177
 
178
+ # Creates getter and setter method for **search array** configuration
179
+ # options.
180
+ #
181
+ # This configuration options contains an array of search queries.
182
+ #
183
+ # @param name [Symbol] configuration option name.
184
+ # @param arg [Array<String>, String] configuration option value to set.
185
+ # @return [Array<String>] configuration option value.
186
+ def set_or_return_search_array(name, arg = nil)
187
+ arg = [arg] unless arg.nil? || !arg.is_a?(String)
188
+ set_or_return(
189
+ name, arg,
190
+ kind_of: Array, default: [], callbacks: config_search_array_callbacks
191
+ )
192
+ end
193
+
194
+ # Checks a search query array list.
195
+ #
196
+ # @param s_ary [Array<String>] search query array.
197
+ # @return [Boolean] `true` if the search query list is in the correct
198
+ # format.
147
199
  def config_valid_search_array?(s_ary)
148
200
  s_ary.each do |s|
149
- return false unless s.kind_of?(String)
201
+ return false unless s.is_a?(String)
150
202
  end
151
203
  true
152
204
  end
153
205
 
206
+ # Returns configuration option callback function for search arrays.
207
+ #
208
+ # @return [Proc] search arrays checking callback function.
154
209
  def config_search_array_callbacks
155
210
  {
156
211
  'should be a valid array of search patterns' => lambda do |cs|
@@ -159,14 +214,22 @@ class Chef
159
214
  }
160
215
  end
161
216
 
217
+ # Checks a user list option value.
218
+ #
219
+ # @param users [Array<String>, '*'] user list to check.
220
+ # @return [Boolean] `true` if the user list is in the correct
221
+ # format.
162
222
  def config_valid_user_arg?(users)
163
- return users == '*' if users.kind_of?(String)
223
+ return users == '*' if users.is_a?(String)
164
224
  users.each do |u|
165
- return false unless u.kind_of?(String) and u.match(/^[a-z0-9\-_]+$/)
225
+ return false unless u.is_a?(String) && u.match(/^[a-z0-9\-_]+$/)
166
226
  end
167
227
  true
168
228
  end
169
229
 
230
+ # Returns configuration option callback function for user lists.
231
+ #
232
+ # @return [Proc] user lists checking callback function.
170
233
  def config_users_arg_callbacks
171
234
  {
172
235
  'should be a valid array of search patterns' => lambda do |us|
@@ -175,32 +238,45 @@ class Chef
175
238
  }
176
239
  end
177
240
 
241
+ # Checks if an OpenSSL key is in the correct format.
242
+ #
243
+ # Only checks that has a public key. It may lack private key.
244
+ #
245
+ # @param k [String, OpenSSL::PKey::RSA] key to check.
246
+ # @return [Boolean] `true` if the public key is correct.
178
247
  def config_valid_key?(k)
179
- rsa_k = case k
180
- when OpenSSL::PKey::RSA
181
- k
182
- when String
183
- begin
184
- OpenSSL::PKey::RSA.new(k)
185
- rescue OpenSSL::PKey::RSAError, TypeError
248
+ rsa_k =
249
+ case k
250
+ when OpenSSL::PKey::RSA then k
251
+ when String
252
+ begin
253
+ OpenSSL::PKey::RSA.new(k)
254
+ rescue OpenSSL::PKey::RSAError, TypeError
255
+ nil
256
+ end
257
+ else
186
258
  nil
187
259
  end
188
- else
189
- nil
190
- end
191
260
  return false if rsa_k.nil?
192
261
  rsa_k.public?
193
262
  end
194
263
 
264
+ # Checks if an OpenSSL key array is in the correct format.
265
+ #
266
+ # Only checks that the keys have a public key. They may lack private key.
267
+ #
268
+ # @param k_ary [Array<String, OpenSSL::PKey::RSA>] array of keys to check.
269
+ # @return [Boolean] `true` if the public keys are all correct.
195
270
  def config_valid_keys_array?(k_ary)
196
271
  k_ary.each do |k|
197
- unless config_valid_key?(k)
198
- return false
199
- end
272
+ return false unless config_valid_key?(k)
200
273
  end
201
274
  true
202
275
  end
203
276
 
277
+ # Returns configuration option callback function for public keys.
278
+ #
279
+ # @return [Proc] public keys checking callback function.
204
280
  def config_valid_keys_array_callbacks
205
281
  {
206
282
  'should be a valid array of keys' => lambda do |keys|
@@ -209,6 +285,39 @@ class Chef
209
285
  }
210
286
  end
211
287
 
288
+ # Copies a configuration. All the current configuration options will be
289
+ # replaced.
290
+ #
291
+ # Called by {#update_from!} for {Config} objects.
292
+ #
293
+ # @param config [Config] configuration options to copy.
294
+ def update_from_config!(config)
295
+ OPTIONS.each do |attr|
296
+ value = dup_object(config.send(attr))
297
+ instance_variable_set("@#{attr}", value)
298
+ end
299
+ end
300
+
301
+ # Copies a configuration option. Only the provided Hash keys will be
302
+ # replaced, the others will be preserved.
303
+ #
304
+ # Called by {#update_from!} for *Hash* objects.
305
+ #
306
+ # @param config [Hash] configuration options to copy.
307
+ def update_from_hash!(config)
308
+ config.each do |attr, value|
309
+ attr = attr.to_sym if attr.is_a?(String)
310
+ if OPTIONS.include?(attr)
311
+ value = dup_object(value)
312
+ send(attr, value)
313
+ else
314
+ Chef::Log.warn(
315
+ "#{self.class}: configuration method not found: "\
316
+ "#{attr.to_s.inspect}."
317
+ )
318
+ end
319
+ end
320
+ end
212
321
  end
213
322
  end
214
323
  end