chef-encrypted-attributes 0.1.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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/API.md +163 -0
  3. data/CHANGELOG.md +7 -0
  4. data/INTERNAL.md +111 -0
  5. data/LICENSE +190 -0
  6. data/README.md +330 -0
  7. data/Rakefile +46 -0
  8. data/TESTING.md +45 -0
  9. data/TODO.md +20 -0
  10. data/lib/chef-encrypted-attributes.rb +19 -0
  11. data/lib/chef/encrypted_attribute.rb +218 -0
  12. data/lib/chef/encrypted_attribute/cache_lru.rb +74 -0
  13. data/lib/chef/encrypted_attribute/config.rb +200 -0
  14. data/lib/chef/encrypted_attribute/encrypted_mash.rb +122 -0
  15. data/lib/chef/encrypted_attribute/encrypted_mash/version0.rb +143 -0
  16. data/lib/chef/encrypted_attribute/encrypted_mash/version1.rb +140 -0
  17. data/lib/chef/encrypted_attribute/exceptions.rb +38 -0
  18. data/lib/chef/encrypted_attribute/local_node.rb +38 -0
  19. data/lib/chef/encrypted_attribute/remote_clients.rb +46 -0
  20. data/lib/chef/encrypted_attribute/remote_node.rb +111 -0
  21. data/lib/chef/encrypted_attribute/remote_users.rb +73 -0
  22. data/lib/chef/encrypted_attribute/search_helper.rb +144 -0
  23. data/lib/chef/encrypted_attribute/version.rb +23 -0
  24. data/lib/chef/knife/core/config.rb +19 -0
  25. data/lib/chef/knife/core/encrypted_attribute_editor_options.rb +100 -0
  26. data/lib/chef/knife/encrypted_attribute_create.rb +67 -0
  27. data/lib/chef/knife/encrypted_attribute_delete.rb +71 -0
  28. data/lib/chef/knife/encrypted_attribute_edit.rb +68 -0
  29. data/lib/chef/knife/encrypted_attribute_show.rb +86 -0
  30. data/lib/chef/knife/encrypted_attribute_update.rb +65 -0
  31. data/spec/benchmark_helper.rb +32 -0
  32. data/spec/integration_helper.rb +20 -0
  33. data/spec/spec_helper.rb +38 -0
  34. metadata +204 -0
@@ -0,0 +1,218 @@
1
+ #
2
+ # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
+ # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/encrypted_attribute/config'
20
+ require 'chef/encrypted_attribute/encrypted_mash'
21
+ require 'chef/config'
22
+ require 'chef/mash'
23
+ require 'chef/api_client'
24
+
25
+ require 'chef/encrypted_attribute/local_node'
26
+ require 'chef/encrypted_attribute/remote_node'
27
+ require 'chef/encrypted_attribute/remote_clients'
28
+ require 'chef/encrypted_attribute/remote_users'
29
+ require 'chef/encrypted_attribute/encrypted_mash/version0'
30
+ require 'chef/encrypted_attribute/encrypted_mash/version1'
31
+
32
+ Chef::Config[:encrypted_attributes] = Mash.new unless Chef::Config[:encrypted_attributes].kind_of?(Hash)
33
+
34
+ class Chef
35
+ class EncryptedAttribute
36
+
37
+ def initialize(c=nil)
38
+ config(c)
39
+ end
40
+
41
+ def config(arg=nil)
42
+ @config ||= EncryptedAttribute::Config.new(Chef::Config[:encrypted_attributes])
43
+ @config.update!(arg) unless arg.nil?
44
+ @config
45
+ end
46
+
47
+ # Decrypts an encrypted attribute from a (encrypted) Hash
48
+ def load(enc_hs, key=nil)
49
+ enc_attr = EncryptedMash.json_create(enc_hs)
50
+ decrypted = enc_attr.decrypt(key || local_key)
51
+ decrypted['content'] # TODO check this Hash
52
+ end
53
+
54
+ # Decrypts a encrypted attribute from a remote node
55
+ def load_from_node(name, attr_ary, key=nil)
56
+ remote_node = RemoteNode.new(name)
57
+ self.load(remote_node.load_attribute(attr_ary, config.partial_search), key)
58
+ end
59
+
60
+ # Creates an encrypted attribute from a Hash
61
+ def create(value, keys=nil)
62
+ decrypted = { 'content' => value }
63
+
64
+ enc_attr = EncryptedMash.create(config.version)
65
+ enc_attr.encrypt(decrypted, target_keys(keys))
66
+ end
67
+
68
+ def create_on_node(name, attr_ary, value)
69
+ # read the client public key
70
+ node_public_key = Chef::ApiClient.load(name).public_key
71
+
72
+ # create the encrypted attribute
73
+ enc_attr = self.create(value, [ node_public_key ])
74
+
75
+ # save encrypted attribute
76
+ remote_node = RemoteNode.new(name)
77
+ remote_node.save_attribute(attr_ary, enc_attr)
78
+ end
79
+
80
+ # Updates the keys for which the attribute is encrypted
81
+ def update(enc_hs, keys=nil)
82
+ old_enc_attr = EncryptedMash.json_create(enc_hs)
83
+ if old_enc_attr.needs_update?(target_keys(keys))
84
+ hs = old_enc_attr.decrypt(local_key)
85
+ new_enc_attr = create(hs['content'], keys) # TODO check this Hash
86
+ enc_hs.replace(new_enc_attr)
87
+ true
88
+ else
89
+ false
90
+ end
91
+ end
92
+
93
+ def update_on_node(name, attr_ary)
94
+ # read the client public key
95
+ node_public_key = Chef::ApiClient.load(name).public_key
96
+
97
+ # update the encrypted attribute
98
+ remote_node = RemoteNode.new(name)
99
+ enc_hs = remote_node.load_attribute(attr_ary, config.partial_search)
100
+ updated = update(enc_hs, [ node_public_key ])
101
+
102
+ # save encrypted attribute
103
+ if updated
104
+ # TODO Node is accessed twice (here and RemoteNode#load_attribute above)
105
+ remote_node.save_attribute(attr_ary, enc_hs)
106
+ end
107
+ updated
108
+ end
109
+
110
+ protected
111
+
112
+ def remote_client_keys
113
+ RemoteClients.get_public_keys(config.client_search, config.partial_search)
114
+ end
115
+
116
+ def remote_user_keys
117
+ RemoteUsers.get_public_keys(config.users)
118
+ end
119
+
120
+ def target_keys(keys=nil)
121
+ target_keys = config.keys + remote_client_keys + remote_user_keys
122
+ target_keys += keys if keys.kind_of?(Array)
123
+ target_keys
124
+ end
125
+
126
+ def local_key
127
+ self.class.local_node.key
128
+ end
129
+
130
+ def self.local_node
131
+ LocalNode.new
132
+ end
133
+
134
+ def self.config(arg)
135
+ config = EncryptedAttribute::Config.new(Chef::Config[:encrypted_attributes])
136
+ config.update!(arg)
137
+ config.keys(config.keys + [ self.local_node.public_key ])
138
+ config
139
+ end
140
+
141
+ public
142
+
143
+ def self.load(hs, c={})
144
+ Chef::Log.debug("#{self.class.name}: Loading Local Encrypted Attribute from: #{hs.to_s}")
145
+ enc_attr = EncryptedAttribute.new(self.config(c))
146
+ result = enc_attr.load(hs)
147
+ Chef::Log.debug("#{self.class.name}: Local Encrypted Attribute loaded.")
148
+ result
149
+ end
150
+
151
+ def self.load_from_node(name, attr_ary, c={})
152
+ Chef::Log.debug("#{self.class.name}: Loading Remote Encrypted Attribute from #{name}: #{attr_ary.to_s}")
153
+ enc_attr = EncryptedAttribute.new(self.config(c))
154
+ result = enc_attr.load_from_node(name, attr_ary)
155
+ Chef::Log.debug("#{self.class.name}: Remote Encrypted Attribute loaded.")
156
+ result
157
+ end
158
+
159
+ def self.create(value, c={})
160
+ Chef::Log.debug("#{self.class.name}: Creating Encrypted Attribute.")
161
+ enc_attr = EncryptedAttribute.new(self.config(c))
162
+ result = enc_attr.create(value)
163
+ Chef::Log.debug("#{self.class.name}: Encrypted Attribute created.")
164
+ result
165
+ end
166
+
167
+ def self.create_on_node(name, attr_ary, value, c={})
168
+ Chef::Log.debug("#{self.class.name}: Creating Remote Encrypted Attribute on #{name}: #{attr_ary.to_s}")
169
+ enc_attr = EncryptedAttribute.new(self.config(c))
170
+ result = enc_attr.create_on_node(name, attr_ary, value)
171
+ Chef::Log.debug("#{self.class.name}: Encrypted Remote Attribute created.")
172
+ result
173
+ end
174
+
175
+ def self.update(hs, c={})
176
+ Chef::Log.debug("#{self.class.name}: Updating Encrypted Attribute: #{hs.to_s}")
177
+ enc_attr = EncryptedAttribute.new(self.config(c))
178
+ result = enc_attr.update(hs)
179
+ if result
180
+ Chef::Log.debug("#{self.class.name}: Encrypted Attribute updated.")
181
+ else
182
+ Chef::Log.debug("#{self.class.name}: Encrypted Attribute not updated.")
183
+ end
184
+ result
185
+ end
186
+
187
+ def self.update_on_node(name, attr_ary, c={})
188
+ Chef::Log.debug("#{self.class.name}: Updating Remote Encrypted Attribute on #{name}: #{attr_ary.to_s}")
189
+ enc_attr = EncryptedAttribute.new(self.config(c))
190
+ result = enc_attr.update_on_node(name, attr_ary)
191
+ if result
192
+ Chef::Log.debug("#{self.class.name}: Encrypted Remote Attribute updated.")
193
+ else
194
+ Chef::Log.debug("#{self.class.name}: Encrypted Remote Attribute not updated.")
195
+ end
196
+ result
197
+ end
198
+
199
+ def self.exists?(hs)
200
+ Chef::Log.debug("#{self.class.name}: Checking if Encrypted Attribute exists here: #{hs.to_s}")
201
+ result = EncryptedMash.exists?(hs)
202
+ if result
203
+ Chef::Log.debug("#{self.class.name}: Encrypted Attribute found.")
204
+ else
205
+ Chef::Log.debug("#{self.class.name}: Encrypted Attribute not found.")
206
+ end
207
+ result
208
+ end
209
+
210
+ def self.exists_on_node?(name, attr_ary, c={})
211
+ Chef::Log.debug("#{self.class.name}: Checking if Remote Encrypted Attribute exists on #{name}")
212
+ remote_node = RemoteNode.new(name)
213
+ node_attr = remote_node.load_attribute(attr_ary, self.config(c).partial_search)
214
+ Chef::EncryptedAttribute.exists?(node_attr)
215
+ end
216
+
217
+ end
218
+ end
@@ -0,0 +1,74 @@
1
+ #
2
+ # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
+ # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/mixin/params_validate'
20
+
21
+ # Based on https://github.com/SamSaffron/lru_redux
22
+ class Chef
23
+ class EncryptedAttribute
24
+ class CacheLru < Hash
25
+ include ::Chef::Mixin::ParamsValidate
26
+
27
+ def initialize(size=nil)
28
+ super
29
+ max_size(size)
30
+ end
31
+
32
+ def max_size(arg=nil)
33
+ set_or_return(
34
+ :max_size,
35
+ arg,
36
+ :kind_of => [ Fixnum ],
37
+ :default => 1024,
38
+ :callbacks => begin
39
+ { 'should not be lower that zero' => lambda { |x| x >= 0 } }
40
+ end
41
+ )
42
+ pop_tail unless arg.nil?
43
+ @max_size
44
+ end
45
+
46
+ def [](key)
47
+ if has_key?(key)
48
+ val = super(key)
49
+ self[key] = val
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ def []=(key, val)
56
+ if max_size > 0 # unnecessary "if", small optimization?
57
+ delete(key)
58
+ super(key, val)
59
+ pop_tail
60
+ end
61
+ val
62
+ end
63
+
64
+ protected
65
+
66
+ def pop_tail
67
+ while size > max_size
68
+ delete(first[0])
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,200 @@
1
+ #
2
+ # Author:: Xabier de Zuazo (<xabier@onddo.com>)
3
+ # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/mixin/params_validate'
20
+
21
+ class Chef
22
+ class EncryptedAttribute
23
+ class Config
24
+ include ::Chef::Mixin::ParamsValidate
25
+
26
+ OPTIONS = [
27
+ :version,
28
+ :partial_search,
29
+ :client_search,
30
+ :users,
31
+ :keys,
32
+ ].freeze
33
+
34
+ def initialize(config=nil)
35
+ update!(config) unless config.nil?
36
+ end
37
+
38
+ def version(arg=nil)
39
+ unless arg.nil? or not arg.kind_of?(String)
40
+ arg = Integer(arg) rescue arg
41
+ end
42
+ set_or_return(
43
+ :version,
44
+ arg,
45
+ :kind_of => [ Fixnum, String ],
46
+ :default => 1
47
+ )
48
+ end
49
+
50
+ def partial_search(arg=nil)
51
+ set_or_return(
52
+ :partial_search,
53
+ arg,
54
+ :kind_of => [ TrueClass, FalseClass ],
55
+ :default => true
56
+ )
57
+ end
58
+
59
+ def client_search(arg=nil)
60
+ unless arg.nil? or not arg.kind_of?(String)
61
+ arg = [ arg ]
62
+ end
63
+ set_or_return(
64
+ :client_search,
65
+ arg,
66
+ :kind_of => Array,
67
+ :default => [],
68
+ :callbacks => config_search_array_callbacks
69
+ )
70
+ end
71
+
72
+ def users(arg=nil)
73
+ set_or_return(
74
+ :users,
75
+ arg,
76
+ :kind_of => [ String, Array ],
77
+ :default => [],
78
+ :callbacks => config_users_arg_callbacks
79
+ )
80
+ end
81
+
82
+ def keys(arg=nil)
83
+ set_or_return(
84
+ :keys,
85
+ arg,
86
+ :kind_of => Array,
87
+ :default => [],
88
+ :callbacks => config_valid_keys_array_callbacks
89
+ )
90
+ end
91
+
92
+ def update!(config)
93
+ if config.kind_of?(self.class)
94
+ OPTIONS.each do |attr|
95
+ value = dup_object(config.send(attr))
96
+ self.instance_variable_set("@#{attr.to_s}", value)
97
+ end
98
+ elsif config.kind_of?(Hash)
99
+ config.each do |attr, value|
100
+ attr = attr.to_sym if attr.kind_of?(String)
101
+ if OPTIONS.include?(attr)
102
+ value = dup_object(value)
103
+ self.send(attr, value)
104
+ else
105
+ Chef::Log.warn("#{self.class.to_s}: configuration method not found: \"#{attr.to_s}\".")
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ def [](key)
112
+ key = key.to_sym if key.kind_of?(String)
113
+ if OPTIONS.include?(key)
114
+ self.send(key)
115
+ end
116
+ end
117
+
118
+ def []=(key, value)
119
+ key = key.to_sym if key.kind_of?(String)
120
+ if OPTIONS.include?(key)
121
+ self.send(key, value)
122
+ end
123
+ end
124
+
125
+ protected
126
+
127
+ def dup_object(o)
128
+ o.dup
129
+ rescue TypeError
130
+ o
131
+ end
132
+
133
+ def config_valid_search_array?(s_ary)
134
+ s_ary.each do |s|
135
+ return false unless s.kind_of?(String)
136
+ end
137
+ true
138
+ end
139
+
140
+ def config_search_array_callbacks
141
+ {
142
+ 'should be a valid array of search patterns' => lambda do |cs|
143
+ config_valid_search_array?(cs)
144
+ end
145
+ }
146
+ end
147
+
148
+ def config_valid_user_arg?(users)
149
+ return users == '*' if users.kind_of?(String)
150
+ users.each do |u|
151
+ return false unless u.kind_of?(String) and u.match(/^[a-z0-9\-_]+$/)
152
+ end
153
+ true
154
+ end
155
+
156
+ def config_users_arg_callbacks
157
+ {
158
+ 'should be a valid array of search patterns' => lambda do |us|
159
+ config_valid_user_arg?(us)
160
+ end
161
+ }
162
+ end
163
+
164
+ def config_valid_key?(k)
165
+ rsa_k = case k
166
+ when OpenSSL::PKey::RSA
167
+ k
168
+ when String
169
+ begin
170
+ OpenSSL::PKey::RSA.new(k)
171
+ rescue OpenSSL::PKey::RSAError, TypeError
172
+ nil
173
+ end
174
+ else
175
+ nil
176
+ end
177
+ return false if rsa_k.nil?
178
+ rsa_k.public?
179
+ end
180
+
181
+ def config_valid_keys_array?(k_ary)
182
+ k_ary.each do |k|
183
+ unless config_valid_key?(k)
184
+ return false
185
+ end
186
+ end
187
+ true
188
+ end
189
+
190
+ def config_valid_keys_array_callbacks
191
+ {
192
+ 'should be a valid array of keys' => lambda do |keys|
193
+ config_valid_keys_array?(keys)
194
+ end
195
+ }
196
+ end
197
+
198
+ end
199
+ end
200
+ end