chef-encrypted-attributes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/README.md ADDED
@@ -0,0 +1,330 @@
1
+ # Chef-Encrypted-Attributes
2
+ [![Gem Version](https://badge.fury.io/rb/chef-encrypted-attributes.png)](http://badge.fury.io/rb/chef-encrypted-attributes)
3
+ [![Dependency Status](https://gemnasium.com/onddo/chef-encrypted-attributes.png)](https://gemnasium.com/onddo/chef-encrypted-attributes)
4
+ [![Build Status](https://travis-ci.org/onddo/chef-encrypted-attributes.png)](https://travis-ci.org/onddo/chef-encrypted-attributes)
5
+ [![Coverage Status](https://coveralls.io/repos/onddo/chef-encrypted-attributes/badge.png?branch=master)](https://coveralls.io/r/onddo/chef-encrypted-attributes?branch=master)
6
+
7
+ [Chef](http://www.getchef.com) plugin to add Node encrypted attributes support using client keys.
8
+
9
+ ## Description
10
+
11
+ Node attributes are encrypted using chef client and user keys with public key infrastructure (PKI). You can choose which clients, nodes or users will be able to read the attribute.
12
+
13
+ Node clients with read access can be specified using a `client_search` query. In case new nodes are added or removed, the data will be re-encrypted in the next *Chef Run* of the encrypting node (using the `#update` method shown below).
14
+
15
+ ## Requirements
16
+
17
+ * Ruby `>= 1.9`
18
+ * Chef Client `>= 11.4`
19
+ * yajl-ruby `~> 1.1` (included with Chef)
20
+
21
+ ## Usage in Recipes
22
+
23
+ ### Installing and Including the Gem
24
+
25
+ You need to install and include the `chef-encrypted-attributes` gem before using encrypted attributes inside a cookbook.
26
+
27
+ ```ruby
28
+ chef_gem "chef-encrypted-attributes"
29
+ require "chef-encrypted-attributes"
30
+ ```
31
+
32
+ ### Typical Example
33
+
34
+ In the following example we save a simple FTP user password.
35
+
36
+ ```ruby
37
+ chef_gem "chef-encrypted-attributes"
38
+ require "chef-encrypted-attributes"
39
+
40
+ Chef::Recipe.send(:include, Opscode::OpenSSL::Password) # include the #secure_password method
41
+
42
+ if Chef::EncryptedAttribute.exists?(node["myapp"]["ftp_password"])
43
+ # update with the new keys
44
+ Chef::EncryptedAttribute.update(node.set["myapp"]["ftp_password"])
45
+
46
+ # read the password
47
+ ftp_pass = Chef::EncryptedAttribute.load(node["myapp"]["ftp_password"])
48
+ else
49
+ # create the password and save it
50
+ ftp_pass = secure_password
51
+ node.set["myapp"]["ftp_password"] = Chef::EncryptedAttribute.create(ftp_pass)
52
+ end
53
+
54
+ # use `ftp_pass` for something here ...
55
+ ```
56
+
57
+ **Note:** This example requires the [openssl](http://community.opscode.com/cookbooks/openssl) cookbook.
58
+
59
+ ### Minimal Write Only Example
60
+
61
+ In this example we only need to save some data from the local node and read it from another:
62
+
63
+ ```ruby
64
+ chef_gem "chef-encrypted-attributes"
65
+ require "chef-encrypted-attributes"
66
+
67
+ # Allow all webapp nodes to read the attributes encrypted by me
68
+ Chef::Config[:encrypted_attributes][:client_search] = "role:webapp"
69
+
70
+ if Chef::EncryptedAttribute.exists?(node["myapp"]["encrypted_data"])
71
+ # when can used #load here as above if we need the `encrypted_data` outside this `if`
72
+
73
+ # update with the new keys
74
+ Chef::EncryptedAttribute.update(node.set["myapp"]["encrypted_data"])
75
+ else
76
+ # create the data, encrypt and save it
77
+ data_to_encrypt = # ....
78
+ node.set["myapp"]["encrypted_data"] = Chef::EncryptedAttribute.create(data_to_encrypt)
79
+ end
80
+ ```
81
+
82
+ Then we can read this attribute from another allowed node (a `"role:webapp"` node):
83
+
84
+ ```ruby
85
+ chef_gem "chef-encrypted-attributes"
86
+ require "chef-encrypted-attributes"
87
+
88
+ if Chef::EncryptedAttribute.exists_on_node?("random.example.com", ["myapp", "encrypted_data"])
89
+ data = Chef::EncryptedAttribute.load_from_node("random.example.com", ["myapp", "encrypted_data"])
90
+
91
+ # use `data` for something here ...
92
+ end
93
+ ```
94
+
95
+ **Note:** Be careful when using `#exists_on_node?` and `#load_from_node` and remember passing the attribute path to read as **Array of Strings** ~~instead of using `node[...]` (which points to the local node)~~.
96
+
97
+ ### Example Using User Keys Data Bag
98
+
99
+ Suppose we want to store users public keys in a data bag and give them access to the attributes. This can be a workaround for the [Chef Users Limitation](README.md#chef-users-limitation) problem.
100
+
101
+ You need to create a Data Bag Item with a content similar to the following:
102
+
103
+ ```json
104
+ {
105
+ "id": "chef_users",
106
+ "bob": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFA...",
107
+ "alice": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFA..."
108
+ }
109
+ ```
110
+
111
+ This data bag will contain the user public keys retrieved with `knife user show USER -a public_key -f json`.
112
+
113
+ Then, from a recipe, you can read this user keys and allow them to read the attributes.
114
+
115
+ ```ruby
116
+ chef_gem "chef-encrypted-attributes"
117
+ require "chef-encrypted-attributes"
118
+
119
+ chef_users = Chef::DataBagItem.load("global_data_bag", "chef_users")
120
+ chef_users.delete("id") # remove the data bag "id" to avoid to confuse it with a user
121
+
122
+ Chef::Log.debug("Admin users able to read the Encrypted Attributes: #{chef_users.keys.inspect}")
123
+ Chef::Config[:encrypted_attributes][:keys] = chef_users.values
124
+
125
+ # if Chef::EncryptedAttribute.exists_on_node?(...)
126
+ # Chef::EncryptedAttribute.update(...)
127
+ # else
128
+ # node.set[...][...] = Chef::EncryptedAttribute.create(...)
129
+ # ...
130
+ ```
131
+
132
+ **Note:** This data bag does **not** need to be **encrypted**, because it only stores **public keys**.
133
+
134
+ ## Chef::EncryptedAttribute API
135
+
136
+ See the [API.md](API.md) file for a more detailed documentation about `Chef::EncryptedAttribute` class and its methods.
137
+
138
+ ## Chef Users Limitation
139
+
140
+ Keep in mind that, from a Chef Node, *Chef User* *public keys* are inaccessible. So you have to pass them in raw mode in the recipe if you need any *Chef User* to be able to use the encrypted attributes (this is **required for** example to use the **knife commands** included in this gem, as knife is usually used by *Chef Users*). Summarizing, Chef Node inside a recipe (using its *Chef Client* key) will not be able to retrieve the *Chef Users* *public keys*, so you need to pass them using the `[:keys]` configuration value.
141
+
142
+ Chef Nodes (Clients) with *admin* privileges do have access to user public keys, but in most cases this is not a recommended practice.
143
+
144
+ *Chef Client* *public keys* do not have this problem, you can retrieve them from any place without limitation. You can use knife with an *Chef Admin Client* instead of a *Chef Admin User* key, but this is not common.
145
+
146
+ See the [Example Using User Keys Data Bag](README.md#example-using-user-keys-data-bag) section for a workaround.
147
+
148
+ **Note:** *Chef Clients* usually are Chef Nodes and *chef-validation*/*chef-webui* keys. *Chef Users* usually are knife users. The main difference between *Chef Users* and *Chef Clients* is that the former are able to log in via *web-ui* (has a password).
149
+
150
+ ## Knife Commands
151
+
152
+ There are multiple commands to read, create and modify the encrypted attributes. All the commands will grant access privileges to the affected node by default (encrypted attributes are written in Node Attributes). But you will not be allowed to access them by default, so remember to give your own knife user privileges before creating or saving the attribute.
153
+
154
+ The `ATTRIBUTE` name must be specified using *dots* notation. For example, for `node['encrypted']['attribute']`, you must specify `"encrypted.attribute"` as knife argument. If the attribute key has a *dot* in its name, you must escape it. For example: `"encrypted.attribute\.with\.dots"`.
155
+
156
+ Read the [Chef Users Limitation](README.md#chef-users-limitation) caveat before trying to use any knife command.
157
+
158
+ ### Installing the Required Gem
159
+
160
+ You need to install the `chef-encrypted-attributes` gem before using this knife commands.
161
+
162
+ $ gem install chef-encrypted-attributes
163
+
164
+ ### knife.rb
165
+
166
+ Some configuration values can be set in your local `knife.rb` configuration file inside the `knife[:encrypted_attributes]` configuraiton space. For example:
167
+
168
+ ```ruby
169
+ knife[:encrypted_attributes][:users] = '*' # allow access to all knife users
170
+ ```
171
+
172
+ See the [API Configuration](API.md#configuration) section for more configuration values.
173
+
174
+ ### knife encrypted attribute show
175
+
176
+ Shows the decrypted attribute content.
177
+
178
+ $ knife encrypted attribute show NODE ATTRIBUTE (options)
179
+
180
+ For example:
181
+
182
+ $ knife encrypted attribute show ftp.example.com myapp.ftp_password
183
+
184
+ ### knife encrypted attribute create
185
+
186
+ Creates an encrypted attribute in a node. The attribute cannot already exist.
187
+
188
+ $ knife encrypted attribute create NODE ATTRIBUTE (options)
189
+
190
+ If the input is in JSON format (`-i`), you can create a JSON in *quirk* mode like `false`, `5` or `"some string"`. You don't need to create an Array or a Hash as the JSON standard forces.
191
+
192
+ For example:
193
+
194
+ $ export EDITOR=vi
195
+ $ knife encrypted attribute create ftp.example.com myapp.ftp_password \
196
+ -U bob -U alice
197
+
198
+ ### knife encrypted attribute update
199
+
200
+ Updates who can read the attribute (for `:client_search` changes).
201
+
202
+ $ knife encrypted attribute update NODE ATTRIBUTE (options)
203
+
204
+ **You must be careful to pass the same privilege arguments that you used in its creation** (this will surely be fixed in a future).
205
+
206
+ For example:
207
+
208
+ $ knife encrypted attribute update ftp.example.com myapp.ftp_password \
209
+ --client-search admin:true \
210
+ --client-search role:webapp \
211
+ -U bob -U alice
212
+
213
+ ### knife encrypted attribute edit
214
+
215
+ Edits an existing encrypted attribute. The attribute must exist.
216
+
217
+ $ knife encrypted attribute edit NODE ATTRIBUTE (options)
218
+
219
+ If the input is in JSON format (`-i`), you can create a JSON in *quirk* mode like `false`, `5` or `"some string"`. You don't need to create an Array or a Hash as the JSON standard forces.
220
+
221
+ **You must be careful to pass the same privilege arguments that you used in its creation** (this will surely be fixed in a future).
222
+
223
+ For example:
224
+
225
+ $ export EDITOR=vi
226
+ $ knife encrypted attribute edit ftp.example.com myapp.ftp_password \
227
+ --client-search admin:true \
228
+ --client-search role:webapp \
229
+ -U bob -U alice
230
+
231
+ ### knife encrypted attribute delete
232
+
233
+ Deletes an existing attribute. If you have no privileges to read it, you must use the `--force` flag.
234
+
235
+ $ knife encrypted attribute delete NODE ATTRIBUTE (options)
236
+
237
+ For example:
238
+
239
+ $ knife encrypted attribute delete ftp.example.com myapp.ftp_password --force
240
+
241
+ ### Knife Options
242
+
243
+ <table>
244
+ <tr>
245
+ <th>Short</th>
246
+ <th>Long</th>
247
+ <th>Description</th>
248
+ <th>Valid Values</th>
249
+ <th>Sub-Commands</th>
250
+ </tr>
251
+ <tr>
252
+ <td>&nbsp;</td>
253
+ <td>--encrypted-attribute-version</td>
254
+ <td>Encrypted Attribute protocol version to use</td>
255
+ <td>"0", "1" <em>(default)</em></td>
256
+ <td>create, edit, update</td>
257
+ </tr>
258
+ <tr>
259
+ <td>-P</td>
260
+ <td>--disable-partial-search</td>
261
+ <td>Disable partial search</td>
262
+ <td>&nbsp;</td>
263
+ <td>create, edit, update</td>
264
+ </tr>
265
+ <tr>
266
+ <td>-C</td>
267
+ <td>--client-search</td>
268
+ <td>Client search query. Can be specified multiple times</td>
269
+ <td>&nbsp;</td>
270
+ <td>create, edit, update</td>
271
+ </tr>
272
+ <tr>
273
+ <td>-U</td>
274
+ <td>--user</td>
275
+ <td>User name to allow access to. Can be specified multiple times</td>
276
+ <td>&nbsp;</td>
277
+ <td>create, edit, update</td>
278
+ </tr>
279
+ <tr>
280
+ <td>-i</td>
281
+ <td>--input-format</td>
282
+ <td>Input (<em>EDITOR</em>) format</td>
283
+ <td>"plain" <em>(default)</em>, "json"</td>
284
+ <td>create, edit</td>
285
+ </tr>
286
+ <tr>
287
+ <td>-f</td>
288
+ <td>--force</td>
289
+ <td>Force the attribute deletion even if you cannot read it</td>
290
+ <td>&nbsp;</td>
291
+ <td>delete</td>
292
+ </tr>
293
+ </table>
294
+
295
+ ## Internal Documentation
296
+
297
+ See the [INTERNAL.md](INTERNAL.md) file for a more low level documentation.
298
+
299
+ ## Contributing
300
+
301
+ 1. Fork the repository on Github.
302
+ 2. Create a named feature branch (like `add_component_x`).
303
+ 3. Write tests for your change.
304
+ 4. Write your change.
305
+ 5. Run the tests, ensuring they all pass (try as much as possible not to reduce coverage).
306
+ 6. Submit a Pull Request using Github.
307
+
308
+ See the [TESTING.md](TESTING.md) file to know how to run the tests properly.
309
+
310
+ You can also see the [TODO.md](TODO.md) file if you're looking for inspiration.
311
+
312
+ ## License and Author
313
+
314
+ | | |
315
+ |:---------------------|:-----------------------------------------|
316
+ | **Author:** | [Xabier de Zuazo](https://github.com/zuazo) (<xabier@onddo.com>)
317
+ | **Copyright:** | Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
318
+ | **License:** | Apache License, Version 2.0
319
+
320
+ Licensed under the Apache License, Version 2.0 (the "License");
321
+ you may not use this file except in compliance with the License.
322
+ You may obtain a copy of the License at
323
+
324
+ http://www.apache.org/licenses/LICENSE-2.0
325
+
326
+ Unless required by applicable law or agreed to in writing, software
327
+ distributed under the License is distributed on an "AS IS" BASIS,
328
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
329
+ See the License for the specific language governing permissions and
330
+ limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env rake
2
+ # encoding: utf-8
3
+
4
+ #
5
+ # Author:: Xabier de Zuazo (<xabier@onddo.com>)
6
+ # Copyright:: Copyright (c) 2014 Onddo Labs, SL. (www.onddo.com)
7
+ # License:: Apache License, Version 2.0
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+
22
+ require 'bundler'
23
+ Bundler::GemHelper.install_tasks
24
+
25
+ require 'rake/testtask'
26
+
27
+ {
28
+ :test => '{unit,integration}',
29
+ :unit => 'unit',
30
+ :integration => 'integration',
31
+ :benchmark => 'benchmark',
32
+ }.each do |test, dir|
33
+ Rake::TestTask.new(test) do |test|
34
+ test.libs << 'lib' << 'spec'
35
+ test.pattern = "spec/#{dir}/**/*.rb"
36
+ test.verbose = true
37
+ end
38
+ end
39
+
40
+ if RUBY_VERSION < '1.9.3'
41
+ # integration tests are broken in 1.9.2 due to a chef-zero bug
42
+ # https://github.com/opscode/chef-zero/issues/65
43
+ task :default => :unit
44
+ else
45
+ task :default => :test
46
+ end
data/TESTING.md ADDED
@@ -0,0 +1,45 @@
1
+ # Testing
2
+
3
+ ## All the Tests
4
+
5
+ $ rake test
6
+
7
+ ## Unit Tests
8
+
9
+ $ rake unit
10
+
11
+ ## Integration Tests
12
+
13
+ $ rake integration
14
+
15
+ ## Benchmarks
16
+
17
+ You can run some simple benchmarks, not at all realistic:
18
+
19
+ $ rspec spec/benchmark/*
20
+ user system total real
21
+ Local EncryptedAttribute read (v=0) 0.430000 0.000000 0.430000 ( 0.428674)
22
+ Local EncryptedAttribute read (v=1) 0.420000 0.010000 0.430000 ( 0.433974)
23
+ Local EncryptedDataBag read 0.010000 0.000000 0.010000 ( 0.011308)
24
+ Remote EncryptedAttribute read (v=0) 1.510000 0.050000 1.560000 ( 1.567265)
25
+ Remote EncryptedAttribute read (v=1) 1.500000 0.060000 1.560000 ( 1.570522)
26
+ Remote EncryptedDataBag read 0.930000 0.050000 0.980000 ( 0.989036)
27
+ Local EncryptedAttribute write (v=0) 0.060000 0.000000 0.060000 ( 0.059326)
28
+ Local EncryptedAttribute write (v=1) 0.090000 0.000000 0.090000 ( 0.090731)
29
+ Local EncryptedDataBag write 0.020000 0.000000 0.020000 ( 0.016910)
30
+ Remote EncryptedAttribute write (v=0) 1.140000 0.080000 1.220000 ( 1.218726)
31
+ Remote EncryptedAttribute write (v=1) 1.180000 0.070000 1.250000 ( 1.259015)
32
+ Remote EncryptedDataBag write 2.050000 0.150000 2.200000 ( 2.208485)
33
+ Local EncryptedAttribute read/write (v=0) 0.560000 0.010000 0.570000 ( 0.569158)
34
+ Local EncryptedAttribute read/write (v=1) 0.570000 0.010000 0.580000 ( 0.589196)
35
+ Local EncryptedDataBag read/write 0.980000 0.050000 1.030000 ( 1.044585)
36
+ Remote EncryptedAttribute read/write (v=0) 2.720000 0.130000 2.850000 ( 2.859289)
37
+ Remote EncryptedAttribute read/write (v=1) 2.660000 0.130000 2.790000 ( 2.813521)
38
+ Remote EncryptedDataBag read/write 3.110000 0.190000 3.300000 ( 3.301053)
39
+
40
+ Finished in 22 seconds
41
+ 18 examples, 0 failures
42
+
43
+ These benchmarks run 100 passes for each test.
44
+
45
+ Its sole purpose is to avoid accidentally including code that can be too slow.
data/TODO.md ADDED
@@ -0,0 +1,20 @@
1
+ TODO
2
+ ====
3
+
4
+ * knife encrypted attribute create/edit from file.
5
+ * Save config inside encrypted data: `:client_search` and `:keys` (including user keys).
6
+ * Chef internal node attribute integration monkey-patch. It may require some `EncryptedMash` class rewrite or adding some methods.
7
+ * Support for Chef `< 11.4` (add `JSONCompat#map_to_rb_obj`, disable `Chef::User` for `< 11.2`, ...).
8
+ * Test with Chef `10`.
9
+ * Add Ruby `1.8` support?
10
+ * Document the Ruby code.
11
+ * Add more info/debug prints.
12
+ * Space-optimized `EncryptedMash::Version2` class.
13
+ * Tests: Add test helper functions (key generation, ApiClients including priv keys, Node creation...).
14
+ * Tests: Add more tests for `EncryptedMash::Version1`.
15
+ * Tests: Add unit tests for `EncryptedAttribute`.
16
+ * Tests: Add unit tests for all knife commands.
17
+ * Tests: Tests `raise_error` always include regex.
18
+ * Tests: Review and clean some tests.
19
+ * Add `chef-vault` to benchmarks.
20
+ * Signed attributes?
@@ -0,0 +1,19 @@
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'