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.
- 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
|
[![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
|
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`.
|