chef-vault 2.4.0 → 2.5.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +97 -0
- data/Changelog.md +34 -0
- data/DEMO.md +13 -6
- data/README.md +9 -4
- data/Rakefile +47 -10
- data/THEORY.md +361 -0
- data/bin/chef-vault +5 -5
- data/chef-vault.gemspec +11 -2
- data/features/clean_unknown_clients.feature +28 -3
- data/features/step_definitions/chef-databag.rb +5 -0
- data/features/step_definitions/chef-repo.rb +46 -8
- data/features/step_definitions/chef-vault.rb +69 -15
- data/features/support/env.rb +2 -2
- data/features/vault_create.feature +54 -0
- data/features/vault_list.feature +26 -0
- data/features/vault_show.feature +46 -0
- data/features/vault_update.feature +17 -0
- data/features/wrong_private_key.feature +14 -0
- data/lib/chef-vault.rb +0 -1
- data/lib/chef-vault/certificate.rb +1 -1
- data/lib/chef-vault/chef_patch/api_client.rb +1 -1
- data/lib/chef-vault/chef_patch/user.rb +1 -1
- data/lib/chef-vault/exceptions.rb +33 -12
- data/lib/chef-vault/item.rb +262 -209
- data/lib/chef-vault/item_keys.rb +90 -88
- data/lib/chef-vault/user.rb +1 -1
- data/lib/chef-vault/version.rb +2 -2
- data/lib/chef/knife/decrypt.rb +1 -2
- data/lib/chef/knife/encrypt_create.rb +1 -2
- data/lib/chef/knife/encrypt_delete.rb +1 -2
- data/lib/chef/knife/encrypt_remove.rb +1 -2
- data/lib/chef/knife/encrypt_rotate_keys.rb +1 -2
- data/lib/chef/knife/encrypt_update.rb +1 -2
- data/lib/chef/knife/mixin/compat.rb +3 -3
- data/lib/chef/knife/mixin/helper.rb +6 -8
- data/lib/chef/knife/vault_admins.rb +1 -2
- data/lib/chef/knife/vault_base.rb +2 -2
- data/lib/chef/knife/vault_create.rb +3 -4
- data/lib/chef/knife/vault_decrypt.rb +3 -4
- data/lib/chef/knife/vault_delete.rb +2 -4
- data/lib/chef/knife/vault_download.rb +1 -2
- data/lib/chef/knife/vault_edit.rb +3 -6
- data/lib/chef/knife/vault_list.rb +53 -0
- data/lib/chef/knife/vault_refresh.rb +1 -2
- data/lib/chef/knife/vault_remove.rb +3 -7
- data/lib/chef/knife/vault_rotate_all_keys.rb +2 -4
- data/lib/chef/knife/vault_rotate_keys.rb +2 -4
- data/lib/chef/knife/vault_show.rb +4 -5
- data/lib/chef/knife/vault_update.rb +7 -9
- data/spec/chef-vault/certificate_spec.rb +0 -2
- data/spec/chef-vault/item_spec.rb +77 -1
- data/spec/chef-vault/user_spec.rb +0 -2
- data/spec/chef-vault_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -3
- metadata +38 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1d708e9e29a406be66537a1e4f2001bfca5c4fd
|
4
|
+
data.tar.gz: e3d77a016b45ee414f9944657f6be4037b409d53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 728d1dc1cad74668f52576130e0171a1bcea7ccdf21a6f1a32861e231e194e52016d94bce5e9028f91ef87ec6a877daa22b63db05f1afe6a441910551c34bdae
|
7
|
+
data.tar.gz: e811b693b9363d55fea010ef9cdd20e2bfbe8955686eaf087d494cab30f1cdc6b64a9737d3a04b6e730622a24aee03dfc163f3c95c3ea5c24468d6360ab132d2
|
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
data/.rubocop_todo.yml
ADDED
@@ -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
|
data/Changelog.md
CHANGED
@@ -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
|
[](https://travis-ci.org/Nordstrom/chef-vault)
|
5
5
|
|
6
|
+
[](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
|
174
|
-
Contributor:: Thomas Gschwind
|
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-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
data/THEORY.md
ADDED
@@ -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`.
|