chef-vault 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.rubocop_todo.yml +97 -0
  4. data/Changelog.md +34 -0
  5. data/DEMO.md +13 -6
  6. data/README.md +9 -4
  7. data/Rakefile +47 -10
  8. data/THEORY.md +361 -0
  9. data/bin/chef-vault +5 -5
  10. data/chef-vault.gemspec +11 -2
  11. data/features/clean_unknown_clients.feature +28 -3
  12. data/features/step_definitions/chef-databag.rb +5 -0
  13. data/features/step_definitions/chef-repo.rb +46 -8
  14. data/features/step_definitions/chef-vault.rb +69 -15
  15. data/features/support/env.rb +2 -2
  16. data/features/vault_create.feature +54 -0
  17. data/features/vault_list.feature +26 -0
  18. data/features/vault_show.feature +46 -0
  19. data/features/vault_update.feature +17 -0
  20. data/features/wrong_private_key.feature +14 -0
  21. data/lib/chef-vault.rb +0 -1
  22. data/lib/chef-vault/certificate.rb +1 -1
  23. data/lib/chef-vault/chef_patch/api_client.rb +1 -1
  24. data/lib/chef-vault/chef_patch/user.rb +1 -1
  25. data/lib/chef-vault/exceptions.rb +33 -12
  26. data/lib/chef-vault/item.rb +262 -209
  27. data/lib/chef-vault/item_keys.rb +90 -88
  28. data/lib/chef-vault/user.rb +1 -1
  29. data/lib/chef-vault/version.rb +2 -2
  30. data/lib/chef/knife/decrypt.rb +1 -2
  31. data/lib/chef/knife/encrypt_create.rb +1 -2
  32. data/lib/chef/knife/encrypt_delete.rb +1 -2
  33. data/lib/chef/knife/encrypt_remove.rb +1 -2
  34. data/lib/chef/knife/encrypt_rotate_keys.rb +1 -2
  35. data/lib/chef/knife/encrypt_update.rb +1 -2
  36. data/lib/chef/knife/mixin/compat.rb +3 -3
  37. data/lib/chef/knife/mixin/helper.rb +6 -8
  38. data/lib/chef/knife/vault_admins.rb +1 -2
  39. data/lib/chef/knife/vault_base.rb +2 -2
  40. data/lib/chef/knife/vault_create.rb +3 -4
  41. data/lib/chef/knife/vault_decrypt.rb +3 -4
  42. data/lib/chef/knife/vault_delete.rb +2 -4
  43. data/lib/chef/knife/vault_download.rb +1 -2
  44. data/lib/chef/knife/vault_edit.rb +3 -6
  45. data/lib/chef/knife/vault_list.rb +53 -0
  46. data/lib/chef/knife/vault_refresh.rb +1 -2
  47. data/lib/chef/knife/vault_remove.rb +3 -7
  48. data/lib/chef/knife/vault_rotate_all_keys.rb +2 -4
  49. data/lib/chef/knife/vault_rotate_keys.rb +2 -4
  50. data/lib/chef/knife/vault_show.rb +4 -5
  51. data/lib/chef/knife/vault_update.rb +7 -9
  52. data/spec/chef-vault/certificate_spec.rb +0 -2
  53. data/spec/chef-vault/item_spec.rb +77 -1
  54. data/spec/chef-vault/user_spec.rb +0 -2
  55. data/spec/chef-vault_spec.rb +1 -1
  56. data/spec/spec_helper.rb +1 -3
  57. metadata +38 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58db597b870524dd9e554ad60f20722c3b95cf9f
4
- data.tar.gz: a49a6a5af9bcb52e84830383957205dd1d4862d2
3
+ metadata.gz: d1d708e9e29a406be66537a1e4f2001bfca5c4fd
4
+ data.tar.gz: e3d77a016b45ee414f9944657f6be4037b409d53
5
5
  SHA512:
6
- metadata.gz: 7a420cc9e5b246ffcf9c44f3c00ebac5d339f8fe3c6e7c2bbe4849a8fd126cecd4386831549ec0666836ac9304da7641ff1d9f18d1395058aaca5339798f38ef
7
- data.tar.gz: f29af664121f81f45070bef3548894729fd0c6e88afba0f2669edd449876c4a1341738ec8e9d375c30044424a4b22b907cfa0ba74ccb8128a14d3a6ac6f479e5
6
+ metadata.gz: 728d1dc1cad74668f52576130e0171a1bcea7ccdf21a6f1a32861e231e194e52016d94bce5e9028f91ef87ec6a877daa22b63db05f1afe6a441910551c34bdae
7
+ data.tar.gz: e811b693b9363d55fea010ef9cdd20e2bfbe8955686eaf087d494cab30f1cdc6b64a9737d3a04b6e730622a24aee03dfc163f3c95c3ea5c24468d6360ab132d2
@@ -0,0 +1 @@
1
+ inherit_from: .rubocop_todo.yml
@@ -0,0 +1,97 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`
2
+ # on 2015-02-09 09:22:33 -0800 using RuboCop version 0.29.0.
3
+ # The point is for the user to remove these configuration records
4
+ # one by one as the offenses are removed from the code base.
5
+ # Note that changes in the inspected code, or installation of new
6
+ # versions of RuboCop, may require this file to be generated again.
7
+
8
+ # Offense count: 12
9
+ Metrics/AbcSize:
10
+ Max: 43
11
+
12
+ # Offense count: 1
13
+ Metrics/BlockNesting:
14
+ Max: 4
15
+
16
+ # Offense count: 1
17
+ # Configuration parameters: CountComments.
18
+ Metrics/ClassLength:
19
+ Max: 247
20
+
21
+ # Offense count: 5
22
+ Metrics/CyclomaticComplexity:
23
+ Max: 14
24
+
25
+ # Offense count: 45
26
+ # Configuration parameters: AllowURI, URISchemes.
27
+ Metrics/LineLength:
28
+ Max: 136
29
+
30
+ # Offense count: 22
31
+ # Configuration parameters: CountComments.
32
+ Metrics/MethodLength:
33
+ Max: 40
34
+
35
+ # Offense count: 4
36
+ Metrics/PerceivedComplexity:
37
+ Max: 15
38
+
39
+ # Offense count: 1
40
+ Style/AccessorMethodName:
41
+ Enabled: false
42
+
43
+ # Offense count: 43
44
+ # Cop supports --auto-correct.
45
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
46
+ Style/AlignParameters:
47
+ Enabled: false
48
+
49
+ # Offense count: 30
50
+ Style/Documentation:
51
+ Enabled: false
52
+
53
+ # Offense count: 6
54
+ # Configuration parameters: Exclude.
55
+ Style/FileName:
56
+ Enabled: false
57
+
58
+ # Offense count: 77
59
+ # Cop supports --auto-correct.
60
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
61
+ Style/HashSyntax:
62
+ Enabled: false
63
+
64
+ # Offense count: 7
65
+ # Cop supports --auto-correct.
66
+ Style/MethodCallParentheses:
67
+ Enabled: false
68
+
69
+ # Offense count: 7
70
+ # Cop supports --auto-correct.
71
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
72
+ Style/SignalException:
73
+ Enabled: false
74
+
75
+ # Offense count: 11
76
+ # Cop supports --auto-correct.
77
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
78
+ Style/SpaceAroundEqualsInParameterDefault:
79
+ Enabled: false
80
+
81
+ # Offense count: 16
82
+ # Cop supports --auto-correct.
83
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
84
+ Style/SpaceBeforeBlockBraces:
85
+ Enabled: false
86
+
87
+ # Offense count: 18
88
+ # Cop supports --auto-correct.
89
+ # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
90
+ Style/SpaceInsideBlockBraces:
91
+ Enabled: false
92
+
93
+ # Offense count: 135
94
+ # Cop supports --auto-correct.
95
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
96
+ Style/StringLiterals:
97
+ Enabled: false
@@ -1,6 +1,40 @@
1
1
  ## Planned (Unreleased)
2
+ ## v2.6.0
3
+
4
+ This release will focus on adding any new features covered by open issues
5
+
6
+ ## v2.7.0
7
+
8
+ This release will focus on reducing tech debt:
9
+
10
+ * improve the coverage of the RSpec suite
11
+ * ensure there are Aruba tests for all the subcommands and scenarios that match DEMO.md
12
+ * clean up any leftover Rubocop issues
13
+
14
+ ## v2.99.0
15
+
16
+ This will be the last release in the 2.x branch, and is for anyone who
17
+ is constraining to '~> 2.4' (for example). Anything that we decide to
18
+ deprecate for v3.0.0 will produce warnings in this release.
19
+
20
+ ## v3.0.0
21
+
22
+ This will be a major refactor. The primary goal is to solve the bootstrap
23
+ problem where a vault can't be encrypted for a node until the node has been
24
+ created. Exactly how we will do that is open to discussion (watch the
25
+ chef-vault issues on github for news).
26
+
27
+ This release will also remove the chef-vault 1.x commands (encrypt/decrypt)
2
28
 
3
29
  ## Released
30
+ ## v2.5.0 / 2015-02-09
31
+ * when decrypting, if the vault is encrypted for the node but decryption fails, emit a more friendly error message than 'OpenSSL::PKey::RSAError: padding check failed'
32
+ * when attempting to add a client key to a vault item, warn and skip if the node doesn't have a public key (reported by Nik Ormseth)
33
+ * add a new 'knife vault list' command that lists the data bags that are vaults
34
+ * Add more detailed explanation of how chef-vault works in THEORY.md (Issue #109)
35
+ * fix a problem with the --clean-unknown-clients switch to `rotate keys` that made it impossible to delete a client that could not be searched for (i.e. the node object is deleted)
36
+ * add rubocop tasks to Rakefile and take a first pass at the low-hanging fruit
37
+
4
38
  ## v2.4.0 / 2014-12-03
5
39
  * add simplecov test coverage configuration (Doug Ireton)
6
40
  * add --clean-unknown-clients switch to knife remove/rotate (Thomas Gschwind and Reto Hermann)
data/DEMO.md CHANGED
@@ -8,11 +8,11 @@
8
8
 
9
9
  ###Put the hat in the magic show
10
10
 
11
- export assistant=aug24 #Change this to your chef id
11
+ export assistant=aug24 #Change this to your chef id
12
12
  export role=magician #Change this to the role you need to pass the secret to
13
-
13
+
14
14
  knife vault create magicshow hat \ #Create a hat object in a data bag called magicshow
15
- --mode client \ #Talk to the chef server rather than local
15
+ --mode client \ #Talk to the chef server rather than local
16
16
  --file tophat \ #Use the hat (file) we put the bunny in
17
17
  --search "role:${role}" \ #Encrypted for all *current* nodes with the magician role
18
18
  --admins "${assistant}" #Encrypted for the assistant
@@ -20,34 +20,41 @@
20
20
  ###Check the magic show is on the chef server
21
21
 
22
22
  knife data bag list
23
+ knife vault list
23
24
 
24
25
  ###Check the hat is there (and that nobody can see what's in it)
26
+
25
27
  knife data bag show magicshow hat
26
28
 
27
29
  ###Check you can see what's in it
30
+
28
31
  knife vault show magicshow hat file-content --mode client
29
32
 
30
33
  ##'Hop' on to a node with a role of 'magician'
31
34
 
32
35
  ###Install required software
36
+
33
37
  sudo apt-get install ruby-dev --yes
34
38
  sudo gem install chef-vault --no-ri --no-rdoc
35
39
 
36
40
  ###Get the bunny back out of the hat!
41
+
37
42
  sudo chef-shell --client <<EOF
38
43
  require 'chef-vault'
39
44
  puts ChefVault::Item.load('magicshow', 'hat')['file-content']
40
45
  EOF
41
46
 
42
- If you are on a node which is not a magician, an exception will be thrown,
47
+ If you are on a node which is not a magician, an exception will be thrown,
43
48
  and the node cannot see what is in the hat.
44
49
 
45
50
  #Finally, do a disappearing act.
46
51
 
47
52
  ###Make the hat disappear...
53
+
48
54
  knife vault delete magicshow hat --mode client
49
-
55
+
50
56
  ###Make the entire magic show disappear...
57
+
51
58
  knife data bag delete magicshow
52
59
 
53
- ###Thank you!
60
+ ###Thank you!
data/README.md CHANGED
@@ -3,10 +3,14 @@
3
3
 
4
4
  [![Build Status](https://travis-ci.org/Nordstrom/chef-vault.png?branch=master)](https://travis-ci.org/Nordstrom/chef-vault)
5
5
 
6
+ [![Code Climate](https://codeclimate.com/github/Nordstrom/chef-vault/badges/gpa.svg)](https://codeclimate.com/github/Nordstrom/chef-vault)
7
+
6
8
  ## DESCRIPTION:
7
9
 
8
10
  Gem that allows you to encrypt a Chef Data Bag Item using the public keys of a list of chef nodes. This allows only those chef nodes to decrypt the encrypted values.
9
11
 
12
+ For a more detailed explanation of how chef-vault works, please refer to the file THEORY.md
13
+
10
14
  ## INSTALLATION:
11
15
 
12
16
  Be sure you are running the latest version Chef. Versions earlier than 0.10.0 don't support plugins:
@@ -167,16 +171,17 @@ Author:: Kevin Moser - @moserke<br>
167
171
  Author:: Eli Klein - @eliklein<br>
168
172
  Author:: Joey Geiger - @jgeiger<br>
169
173
  Author:: Joshua Timberman - @jtimberman<br>
174
+ Author:: James FitzGibbon - @jf647<br>
170
175
 
171
176
  ## Contributors
172
177
 
173
- Contributor:: Matt Brimstone (https://github.com/brimstone)
174
- Contributor:: Thomas Gschwind (https://github.com/thg65)
175
- Contributor:: Reto Hermann
178
+ Contributor:: Matt Brimstone - @brimstone<br>
179
+ Contributor:: Thomas Gschwind - @thg65<br>
180
+ Contributor:: Reto Hermann<br>
176
181
 
177
182
  ## License
178
183
 
179
- Copyright:: Copyright (c) 2013-14 Nordstrom, Inc.<br>
184
+ Copyright:: Copyright (c) 2013-15 Nordstrom, Inc.<br>
180
185
  License:: Apache License, Version 2.0
181
186
 
182
187
  Licensed under the Apache License, Version 2.0 (the "License");
data/Rakefile CHANGED
@@ -1,16 +1,53 @@
1
1
  require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
- require 'cucumber'
4
- require 'cucumber/rake/task'
5
2
 
6
- RSpec::Core::RakeTask.new(:spec)
3
+ # Style Tests
4
+ begin
5
+ require 'rubocop/rake_task'
6
+ RuboCop::RakeTask.new do |t|
7
+ t.formatters = ['progress']
8
+ t.options = ['-D']
9
+ t.patterns = %w(
10
+ lib/**/*.rb
11
+ spec/**/*.rb
12
+ ./Rakefile
13
+ )
14
+ end
7
15
 
8
- Cucumber::Rake::Task.new(:features)
16
+ # style is an alias for rubocop
17
+ task style: :rubocop
18
+ rescue LoadError
19
+ puts 'Rubocop not available; disabling rubocop tasks'
20
+ end
9
21
 
10
- task default: [:spec, :features]
22
+ # Unit Tests
23
+ begin
24
+ require 'rspec/core/rake_task'
25
+ RSpec::Core::RakeTask.new
26
+
27
+ # Coverage
28
+ desc 'Generate unit test coverage report'
29
+ task :coverage do
30
+ ENV['COVERAGE'] = 'true'
31
+ Rake::Task[:spec].invoke
32
+ end
33
+ rescue LoadError
34
+ puts 'RSpec not available; disabling rspec tasks'
35
+ # create a no-op spec task for :default
36
+ task :spec
37
+ end
11
38
 
12
- task :coverage do
13
- ENV['COVERAGE'] = '1'
14
- Rake::Task[:spec].invoke
15
- Rake::Task[:features].invoke
39
+ # Feature Tests
40
+ begin
41
+ require 'cucumber'
42
+ require 'cucumber/rake/task'
43
+ Cucumber::Rake::Task.new(:features)
44
+ rescue LoadError
45
+ puts 'Cucumber/Aruba not available; disabling feature tasks'
46
+ # create a no-op spec task for :default
47
+ task :features
16
48
  end
49
+
50
+ # test or the default task runs spec and features
51
+ desc 'run all tests'
52
+ task default: [:spec, :features]
53
+ task test: :default
@@ -0,0 +1,361 @@
1
+ # How chef-vault works
2
+
3
+ ## The Problem
4
+
5
+ Chef provides [encrypted data bags](https://docs.chef.io/chef/essentials_data_bags.html)
6
+ as part of the core software, but leaves the problem of key
7
+ distribution up to end users. Encrypted data bags are
8
+ symmetrically encrypted, and so you have to distribute the
9
+ decryption key out-of-band to your nodes (e.g. baking it into
10
+ your image or pushing it to the system before running
11
+ chef-client).
12
+
13
+ ## The Solution
14
+
15
+ Every node managed by chef has an associated client object
16
+ on the Chef Server. The client object has the public half of
17
+ an RSA keypair. The private half only lives on the node,
18
+ typically in `/etc/chef/client.pem`.
19
+
20
+ Every API user on the chef server also has an RSA keypair.
21
+ This is typically kept in your `~/.chef` directory in PEM
22
+ format. Like nodes, the public half is stored on the Chef
23
+ server.
24
+
25
+ chef-vault creates an encrypted data bag which is symmetrically
26
+ encrypted using a random secret (a 32-byte string generated
27
+ using [SecureRandom.random_bytes](http://ruby-doc.org/stdlib-2.1.2/libdoc/securerandom/rdoc/SecureRandom.html#method-c-random_bytes)). We'll refer to this secret as the 'shared secret' through
28
+ the rest of this document.
29
+
30
+ A vault has a list of administrative users and a list of
31
+ clients. The shared secret is asymmetrically encrypted
32
+ for each of the administrators and clients using their public
33
+ key (a separate copy for each).
34
+
35
+ The administrators of a vault are API users named explicitly
36
+ when the vault is created. The clients can be provided
37
+ explicitly, but are more often determined by running a SOLR
38
+ query against the Chef server. The query is stored as part
39
+ of the \_keys data bag so that the clients can be updated by
40
+ re-executing the search.
41
+
42
+ The asymmmetrically encrypted keys are stored in a second
43
+ data bag item whose name is appended with \_keys
44
+
45
+ ## Data Bag Structure
46
+
47
+ These examples assume that I have two nodes in my Chef
48
+ server/organization, named 'one' and 'two'. I also have
49
+ two administrators named 'alice' and 'bob'.
50
+
51
+ Given a file named `item.json` containin the following:
52
+
53
+ ```json
54
+ { "foo": "bar" }
55
+ ```
56
+
57
+ Running
58
+
59
+ > knife vault create foo bar -A alice,bob -S '*:*' -J item.json
60
+
61
+ will create a data bag named 'foo' and two data bag items
62
+ within named 'bar' and 'bar_keys':
63
+
64
+ > knife data bag show foo
65
+ bar
66
+ bar_keys
67
+
68
+ The 'bar' item contains the symmetrically encrypted version
69
+ of the JSON data:
70
+
71
+ > knife data bag show -F json foo bar
72
+ {
73
+ "id": "bar",
74
+ "foo": {
75
+ "encrypted_data": "k9On2aJxnLDRwOeCr60l0C41XjIJ2+5Xu0AYFbmSvFw=\n",
76
+ "iv": "oKeiEkIlaspvhKghee30GA==\n",
77
+ "version": 1,
78
+ "cipher": "aes-256-cbc"
79
+ }
80
+ }
81
+
82
+ (this is a standard Chef encrypted data bag)
83
+
84
+ The 'bar_keys' item contains four copies of the asymetrically
85
+ encrypted shared secret:
86
+
87
+ > knife data bag show -F json foo bar_keys
88
+ {
89
+ "id": "bar_keys",
90
+ "admins": [
91
+ "alice",
92
+ "bob"
93
+ ],
94
+ "clients": [
95
+ "one",
96
+ "two"
97
+ ],
98
+ "search_query": "*:*",
99
+ "alice": "MW7hOvoc7XvYHJbbm0gWAaLNbVcHnxv5YDMnYsjiK/F1qxnFrY4X8pTwzgUI\nRsZuREpEpCSWY9C23ESolTHnBtgEHkR6Xe74NFUr9OURiAGljZL9zEzVUFJu\npds8pWjgGnqpwULxiPZT96xKEw+BnMy0ipYChdF2iaJtQzVAlIzXZoPaOXeH\nJPd1dwmD/G0X2nK0+cNEnJGUP6gideMun3S+dTN9rpP0/7bInjNRPAAXV5yY\nKNRBFgtFyG828B9uXXJ8wXaYYOzp7VLK4ehJw25g5VNkMttqNQVWyIbxGirf\nuvys/PTlCXzLkJ3+e0X5q4ZSotQ+1zJ5UVs16VOChQ==\n",
100
+ "bob": "h9qvmFyR3ygYUQgoW42ABIeCov16cSyYFlj9wKrscLFDzhs0jRrFRdvpcBBl\nqU3Glk79Y898L3C4+/EYomi2I7/EuxZozP+wgeTJDIcXQdeZwEWxGzY1JZq2\nxZYezdoWKATysAtPJEtNIPRzOGiloq+QanHDrxq3JVvZrJ/L5fE0eyV0Nh3T\nbX4X0KzZ4LzeGeUCXXOVa9C2rEHpf61PsMF79iAnULDpD++YdxDGHv6KgHJS\nVENHvyIWi4erRGrcwZRq709iB1BRm/14Zb9ZzZT/HcHIw5P47Ht0wgZ8x71V\nbhAjK410AWG9QtefDf6ybD/ERkKgVjeqcZ57TysdvQ==\n",
101
+ "one": "Ugwpeq2du2RqEzAcn1GA+Uj+dW9fHq7+coCT4LWD2CLo9og9Qu7MSkwuZXaj\nZngl31skSCyvE15ZVhhXilkwmJmrOoEU0B5FlbZTzjHxlq/ga2MhemXmASAH\nyu39if2fb++sE/g5RLy1A9EQs7oeVY53BLZCtENA5XHGjMA1WoBi1PfQTpUs\nZZKW604vs4i/zw88j1Np5o7xb77Wt7zZQsRS+uLxp7qWOTPaT85usChk5ygS\nFrPNZF/F95ODe74o6qwxAtQhKroEeUV6GSWCB2M9FTIoGH+Fhj7oDSiLT1AA\n4VqF4mCMuVMeAM2GTx5IvdIYja2GV7DbomTBNYsGiA==\n",
102
+ "two": "yO6eaCnDmNnQP5d1h1LxZyQdHhYh0BvhBhauVBv8RXWuyuY/8qC7iREPlN52\nRFCr38BStHO9/D4m+uv6SnJhKREe99eOtpddSXD92K/I4bMSCszC+/TmaZWj\nNibZonivam1SuutMbh6WPlHT6/yjIXb1w0cXxple5R+WmPttuMj13V0at0wY\nWMg4JC5/PpYoX8qfmUKvcrVxqFdbQ0YlgAzzdJwzWJOpN+ZEfiFSJopREt6L\n2wSkWezHylGmIWuGLmANSCdluk0oaEVyA1Panf8HL87tWlEc+BajY53JZxY1\n3YIZNWpelU6W/Nl8zu8R206ksKNNMk0yuhd++7F+yw==\n"
103
+ }
104
+
105
+ It also contains the list of administrators and clients for
106
+ which the key is encrypted and the query used to choose which
107
+ clients can decrypt the vault.
108
+
109
+ In practice, the search query would not be `*:*`. More common
110
+ practice is to select which nodes can decrypt the vault based
111
+ on characteristics like:
112
+
113
+ * which recipes are in the node's runlist
114
+ * which chef environment the node belongs to
115
+ * what OS or platform the node runs
116
+ * attributes collected by Ohai Plugins (such as AWS tags).
117
+
118
+ While there are four copies of the shared secret, any single
119
+ user or node will only be able to decrypt one of the copies,
120
+ because no user or node (should) have more than one private key.
121
+
122
+ Likewise, a node or user for whom the vault was not encrypted
123
+ is able to see the contents of the 'bar_keys' data bag,
124
+ but since they lack a private key that can decrypt any of the
125
+ copies of the shared secret, they cannot access the 'bar' encrypted
126
+ data bag item.
127
+
128
+ As alice or bob, I can show the contents of the vault:
129
+
130
+ > knife vault show -F json foo bar
131
+ {
132
+ "id": "bar",
133
+ "foo": "bar"
134
+ }
135
+
136
+ Or in a recipe that runs on node one or two:
137
+
138
+ vaultitem = ChefVault::Item.load('foo', 'bar')
139
+ Chef::Log.debug "the vault value foo is #{vaultitem['foo']}"
140
+
141
+ ## Use Cases
142
+
143
+ In these use cases, assume there are three API clients:
144
+
145
+ * alice (Administrator)
146
+ * bob (Administrator)
147
+ * charlie (Normal User)
148
+
149
+ And three nodes, of the given platform:
150
+
151
+ * one (Linux, Ubuntu)
152
+ * two (Linux, CentOS)
153
+ * three (Windows)
154
+
155
+ The use cases are written from the perspective of 'client'
156
+ mode using a Chef server, but work similarly in 'solo' mode
157
+ assuming that the commands are run from the directory containing
158
+ the Chef repository.
159
+
160
+ We also assume that the commands are being run from a Linux
161
+ terminal session (paths to key files are slightly different
162
+ on Windows)
163
+
164
+ ### Creating a Vault
165
+
166
+ The actor in this case is alice. When she runs
167
+
168
+ knife vault create foo bar -S os:linux -A alice,bob
169
+
170
+ After editing the empty JSON document and saving it, chef-vault
171
+ generates a random 32 byte string for the shared secret.
172
+
173
+ A new data bag named 'foo/bar_keys' is created. chef-vault then
174
+ executes the search 'os:linux' against the node index.
175
+
176
+ For each of the nodes returned from the search (one and two), the
177
+ public keys are fetched for the like-named client and the shared
178
+ secret is encrypted using it. The client name is added to the
179
+ 'clients' array in the bar_keys data bag item. The node-specific
180
+ encrypted copy of the shared secret is stored in the bar_keys
181
+ data bag item, keyed by node name.
182
+
183
+ The search query is also stored in the keys data bag, for use
184
+ during future update or refresh operations.
185
+
186
+ In a similar fashion, the public keys for alice and bob are
187
+ retrieved from the Chef server and the shared secret is
188
+ asymmetrically encrypted for each. The names of the admins are
189
+ stored in the 'admins' array in the bar_keys data bag item.
190
+
191
+ At this point, the bar_keys data bag item is only present in
192
+ memory. It is saved to the server, and if that operation fails
193
+ the vault create throws an error.
194
+
195
+ The data bag 'foo' is then fetched (or created if it does
196
+ not already exist). An encrypted data bag item named 'bar'
197
+ is created, encrypted with the shared secret. The data bag
198
+ item is save to the Chef server.
199
+
200
+ ### Decrypting a Vault using knife
201
+
202
+ The actor in this case is alice. When she runs
203
+
204
+ knife vault show foo bar
205
+
206
+ chef-vault fetches the data bag item foo/bar_keys. It
207
+ then looks for a key in the data bag named 'alice'. The value
208
+ of this key is the asymmetrically encrypted copy of the shared
209
+ secret specific to alice.
210
+
211
+ chef-vault then uses alice's private key (typically `~/.chef/alice.pem`) to decrypt the shared secret.
212
+
213
+ If the \_keys data bag did not have a key 'alice', chef-vault
214
+ would have emitted an error to the effect that the vault was not
215
+ encrypted for her and that someone else who does have access to
216
+ the bag needs to add her as an administrator before she can view
217
+ it.
218
+
219
+ Using the decrypted shared secret, chef-vault loads the Chef
220
+ encrypted data bag foo/bar. The plaintext contents of this bag
221
+ are then displayed to alice. If the appropriate options are
222
+ provided on the command line, additional information such as
223
+ the list of administrators, the list of clients and the search
224
+ query (all of which are stored in the bar_keys data bag item)
225
+ are also displayed.
226
+
227
+ ### Decrypting a Vault in a Chef Recipe
228
+
229
+ The actor in this case is chef-client (running as root) on
230
+ the server 'one'.
231
+
232
+ Assuming that the recipe contains code such as this:
233
+
234
+ chef_gem 'chef-vault'
235
+ require 'chef-vault'
236
+ vaultitem = ChefVault::Item.load('foo', 'bar')
237
+
238
+ chef-vault fetches the data bag item foo/bar_keys. It
239
+ then looks for a key in the data bag named 'one'. The value
240
+ of this key is the asymetrically encrypted copy of the shared
241
+ secret specific to the node 'one'.
242
+
243
+ chef-vault then uses the private key of node one (typically
244
+ `/etc/chef/client.pem`) to decrypt the shared secret.
245
+
246
+ If the \_keys data bag did not have a key 'one', chef-vault
247
+ would have thrown an exception indicating that the vault was
248
+ not encrypted for the node and that an administrator needs to
249
+ refresh the vault (possibly after updating the search query)
250
+ before the recipe can use the vault.
251
+
252
+ Uncaught, this would cause chef-client to abort in the compile
253
+ phase. Robust recipes can be written that catch the exception
254
+ and do other things, such as only converging resources that do
255
+ not need the secrets, or sending an alert so that a human can
256
+ update the vault, or even sending a request to a work queue
257
+ to automatically update the vault.
258
+
259
+ Using the decrypted shared secret, chef-vault loads the Chef
260
+ encrypted data bag foo/bar. The plaintext contents of this bag
261
+ are now available to the recipe in the local variable named
262
+ 'vaultitem'. At this point the data looks and feels like a
263
+ normal data bag (i.e. it behaves in a hash-like way)
264
+
265
+ ### Adding a new Administrator
266
+
267
+ The actor in this case is Alice, who wants to make charlie
268
+ an administrator of the vault. We assume she is working with
269
+ the vault created in the section 'Creating a Vault'. When she
270
+ runs
271
+
272
+ knife vault update -A charlie foo bar
273
+
274
+ This vault is decrypted in the same fashion as described
275
+ in 'Decrypting a Vault using knife'. This results in the
276
+ plain text of the shared secret being available in memory.
277
+
278
+ The search query is run again, returning the same two nodes.
279
+ The shared secret is then asymmetrically encrypted for each
280
+ as described in 'Creating a Vault'.
281
+
282
+ The shared secret is then asymmetrically encrypted for all
283
+ of the admins (alice, bob and the newly-added charlie) as
284
+ described in 'Creating a Vault'.
285
+
286
+ The data bag item 'foo/bar_keys' is then saved, followed by the
287
+ data bag item 'foo/bar'.
288
+
289
+ charlie can now view the contents of the vault using `knife
290
+ vault show` because there is a now a copy of the shared
291
+ secret that he can access.
292
+
293
+ ### Adding a new Node
294
+
295
+ The actor in this case is Alice, who wants to encrypt the
296
+ value for all nodes instead of just those running linux.
297
+ When she runs
298
+
299
+ knife vault update -S '*:*' foo bar
300
+
301
+ This vault is decrypted in the same fashion as described
302
+ in 'Decrypting a Vault using knife'. This results in the
303
+ plain text of the shared secret being available in memory.
304
+
305
+ The search query is run again, returning three nodes this
306
+ time: one, two and three. The shared secret is then
307
+ asymmetrically encrypted for each as described in 'Creating
308
+ a Vault'.
309
+
310
+ The data bag item 'foo/bar_keys' is then saved, followed by the
311
+ data bag item 'foo/bar'.
312
+
313
+ Node three can now use the vault in a recipe because
314
+ there is a now a copy of the shared secret that he it access.
315
+
316
+ ### Rotating Keys
317
+
318
+ Rotating keys chooses a new shared secret for the bag and
319
+ encrypts it for all of the administrators and clients
320
+ who currently have access. Unlike the `knife vault update`
321
+ command, the search query is not re-run to pick up new clients.
322
+
323
+ The actor in this case is Alice, who wants to rotate the keys
324
+ (perhaps to conform to an internal security policy). When
325
+ she runs
326
+
327
+ knife vault rotate_keys foo bar
328
+
329
+ The vault is decrypted in the same fashion as described
330
+ in 'Decrypting a Vault using knife'.
331
+
332
+ chef-vault generates a new 32-byte random string. It then
333
+ creates an asymmetrically encrypted version of the new
334
+ shared secret for each of the clients and administrators
335
+ listed in the data bag item 'foo/bar_keys'.
336
+
337
+ The data bag item 'foo/bar_keys' is then saved, followed by the
338
+ data bag item 'foo/bar'.
339
+
340
+ ## Failure Scenarios
341
+
342
+ Because the secret data is just a normal Chef encrypted
343
+ data bag item, the keys are stored separately in a data bag
344
+ suffixed with \_keys. When the vault is saved, the data bag
345
+ item containing the keys is saved before the encrypted data
346
+ bag is.
347
+
348
+ If the key data bag item save succeeds, but the vault data bag
349
+ item fails, the vault will be in an unsable state, because
350
+ it will be encrypted with the old shared secret, but the keys
351
+ data bag item contains asymmetrically encrypted copies of the
352
+ new shared secret.
353
+
354
+ This can be mitigated by using solo mode, which writes the
355
+ encrypted data bags to JSON files on disk rather than making
356
+ changes using the Chef Server API. Another option is to use
357
+ `knife download` to temporarily store a on-disk version of the
358
+ vault data bag item and the keys data bag item before making
359
+ changes. If any probems are encountered, the old copy of
360
+ the data bags can be loaded back into the Chef server using
361
+ `knife data bag from file`.