chef-vault 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +5 -1
  3. data/.travis.yml +5 -1
  4. data/Changelog.md +36 -3
  5. data/KNIFE_EXAMPLES.md +49 -74
  6. data/README.md +166 -104
  7. data/THEORY.md +4 -2
  8. data/bin/chef-vault +2 -2
  9. data/chef-vault.gemspec +2 -2
  10. data/features/clean_on_refresh.feature +28 -0
  11. data/features/isvault.feature +24 -0
  12. data/features/itemtype.feature +25 -0
  13. data/features/step_definitions/chef-databag.rb +4 -0
  14. data/features/step_definitions/chef-vault.rb +21 -0
  15. data/features/step_definitions/chef_databagitem.rb +9 -0
  16. data/features/vault_show_vaultname.feature +22 -0
  17. data/features/vault_update.feature +1 -1
  18. data/features/verify_id_matches.feature +11 -0
  19. data/lib/chef-vault/certificate.rb +1 -1
  20. data/lib/chef-vault/exceptions.rb +3 -0
  21. data/lib/chef-vault/item.rb +161 -18
  22. data/lib/chef-vault/user.rb +1 -1
  23. data/lib/chef-vault/version.rb +1 -1
  24. data/lib/chef/knife/decrypt.rb +1 -1
  25. data/lib/chef/knife/encrypt_create.rb +1 -1
  26. data/lib/chef/knife/encrypt_delete.rb +1 -1
  27. data/lib/chef/knife/encrypt_remove.rb +1 -1
  28. data/lib/chef/knife/encrypt_rotate_keys.rb +1 -1
  29. data/lib/chef/knife/encrypt_update.rb +1 -1
  30. data/lib/chef/knife/vault_base.rb +22 -0
  31. data/lib/chef/knife/vault_create.rb +0 -1
  32. data/lib/chef/knife/vault_decrypt.rb +1 -1
  33. data/lib/chef/knife/vault_isvault.rb +43 -0
  34. data/lib/chef/knife/vault_itemtype.rb +43 -0
  35. data/lib/chef/knife/vault_list.rb +7 -18
  36. data/lib/chef/knife/vault_refresh.rb +6 -12
  37. data/lib/chef/knife/vault_rotate_all_keys.rb +1 -1
  38. data/lib/chef/knife/vault_show.rb +15 -1
  39. data/lib/chef/knife/vault_update.rb +1 -1
  40. data/spec/chef-vault/certificate_spec.rb +9 -2
  41. data/spec/chef-vault/item_spec.rb +164 -3
  42. data/spec/chef-vault/user_spec.rb +9 -2
  43. metadata +14 -6
@@ -24,7 +24,7 @@ class ChefVault
24
24
  end
25
25
 
26
26
  def decrypt_password
27
- puts "WARNING: This method is deprecated, please switch to item['value'] calls"
27
+ $stdout.puts "WARNING: This method is deprecated, please switch to item['value'] calls"
28
28
  @item["password"]
29
29
  end
30
30
  end
@@ -14,6 +14,6 @@
14
14
  # limitations under the License.
15
15
 
16
16
  class ChefVault
17
- VERSION = "2.5.0"
17
+ VERSION = '2.6.0'
18
18
  MAJOR, MINOR, TINY = VERSION.split('.')
19
19
  end
@@ -24,7 +24,7 @@ class Chef
24
24
  banner "knife decrypt VAULT ITEM [VALUES] (options)"
25
25
 
26
26
  def run
27
- puts "DEPRECATION WARNING: knife decrypt is deprecated. Please use knife vault decrypt instead."
27
+ $stdout.puts "DEPRECATION WARNING: knife decrypt is deprecated. Please use knife vault decrypt instead."
28
28
  super
29
29
  end
30
30
  end
@@ -43,7 +43,7 @@ class Chef
43
43
  :description => 'File to be added to vault item as file-content'
44
44
 
45
45
  def run
46
- puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
46
+ $stdout.puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
47
47
  super
48
48
  end
49
49
  end
@@ -24,7 +24,7 @@ class Chef
24
24
  banner "knife encrypt delete VAULT ITEM (options)"
25
25
 
26
26
  def run
27
- puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
27
+ $stdout.puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
28
28
  super
29
29
  end
30
30
  end
@@ -34,7 +34,7 @@ class Chef
34
34
  :description => 'Chef users to be added as admins'
35
35
 
36
36
  def run
37
- puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
37
+ $stdout.puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
38
38
  super
39
39
  end
40
40
  end
@@ -24,7 +24,7 @@ class Chef
24
24
  banner "knife encrypt rotate keys VAULT ITEM (options)"
25
25
 
26
26
  def run
27
- puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
27
+ $stdout.puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
28
28
  super
29
29
  end
30
30
  end
@@ -43,7 +43,7 @@ class Chef
43
43
  banner "knife encrypt update VAULT ITEM VALUES (options)"
44
44
 
45
45
  def run
46
- puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
46
+ $stdout.puts "DEPRECATION WARNING: knife encrypt is deprecated. Please use knife vault instead."
47
47
  super
48
48
  end
49
49
  end
@@ -41,6 +41,28 @@ class Chef
41
41
  super
42
42
  exit 1
43
43
  end
44
+
45
+ private
46
+
47
+ def bag_is_vault?(bagname)
48
+ bag = Chef::DataBag.load(bagname)
49
+ # vaults have at even number of keys >= 2
50
+ return false unless bag.keys.size >= 2 && 0 == bag.keys.size % 2
51
+ # partition into those that end in _keys
52
+ keylike, notkeylike = split_vault_keys(bag)
53
+ # there must be an equal number of keyline and not-keylike items
54
+ return false unless keylike.size == notkeylike.size
55
+ # strip the _keys suffix and check if the sets match
56
+ keylike.map! { |k| k.gsub(/_keys$/, '') }
57
+ return false unless keylike.sort == notkeylike.sort
58
+ # it's (probably) a vault
59
+ true
60
+ end
61
+
62
+ def split_vault_keys(bag)
63
+ # partition into those that end in _keys
64
+ bag.keys.partition { |k| k =~ /_keys$/ }
65
+ end
44
66
  end
45
67
  end
46
68
  end
@@ -63,7 +63,6 @@ class Chef
63
63
  rescue ChefVault::Exceptions::KeysNotFound,
64
64
  ChefVault::Exceptions::ItemNotFound
65
65
  vault_item = ChefVault::Item.new(vault, item)
66
-
67
66
  if values || json_file || file
68
67
  merge_values(values, json_file).each do |key, value|
69
68
  vault_item[key] = value
@@ -23,7 +23,7 @@ class Chef
23
23
  banner "knife vault decrypt VAULT ITEM [VALUES] (options)"
24
24
 
25
25
  def run
26
- puts "DEPRECATION WARNING: knife vault decrypt is deprecated. Please use knife vault show instead."
26
+ $stdout.puts "DEPRECATION WARNING: knife vault decrypt is deprecated. Please use knife vault show instead."
27
27
  vault = @name_args[0]
28
28
  item = @name_args[1]
29
29
  values = @name_args[2]
@@ -0,0 +1,43 @@
1
+ # Description: Chef-Vault VaultIsvault class
2
+ # Copyright 2013, Nordstrom, Inc.
3
+
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'chef/knife/vault_base'
17
+
18
+ class Chef
19
+ class Knife
20
+ class VaultIsvault < Knife
21
+ include Chef::Knife::VaultBase
22
+
23
+ banner "knife vault isvault VAULT ITEM (options)"
24
+
25
+ option :mode,
26
+ :short => '-M MODE',
27
+ :long => '--mode MODE',
28
+ :description => 'Chef mode to run in default - solo'
29
+
30
+ def run
31
+ vault = @name_args[0]
32
+ item = @name_args[1]
33
+
34
+ if vault && item
35
+ set_mode(config[:vault_mode])
36
+ exit ChefVault::Item.vault?(vault, item) ? 0 : 1
37
+ else
38
+ show_usage
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # Description: Chef-Vault VaultItemtype class
2
+ # Copyright 2013, Nordstrom, Inc.
3
+
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'chef/knife/vault_base'
17
+
18
+ class Chef
19
+ class Knife
20
+ class VaultItemtype < Knife
21
+ include Chef::Knife::VaultBase
22
+
23
+ banner "knife vault itemtype VAULT ITEM (options)"
24
+
25
+ option :mode,
26
+ :short => '-M MODE',
27
+ :long => '--mode MODE',
28
+ :description => 'Chef mode to run in default - solo'
29
+
30
+ def run
31
+ vault = @name_args[0]
32
+ item = @name_args[1]
33
+
34
+ if vault && item
35
+ set_mode(config[:vault_mode])
36
+ output ChefVault::Item.data_bag_item_type(vault, item)
37
+ else
38
+ show_usage
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,4 +1,4 @@
1
- # Description: Chef-Vault VaultShow class
1
+ # Description: Chef-Vault VaultList class
2
2
  # Copyright 2013, Nordstrom, Inc.
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,7 +22,13 @@ class Chef
22
22
 
23
23
  banner "knife vault list (options)"
24
24
 
25
+ option :mode,
26
+ :short => '-M MODE',
27
+ :long => '--mode MODE',
28
+ :description => 'Chef mode to run in default - solo'
29
+
25
30
  def run
31
+ set_mode(config[:vault_mode])
26
32
  vaultbags = []
27
33
  # iterate over all the data bags
28
34
  bags = Chef::DataBag.list
@@ -31,23 +37,6 @@ class Chef
31
37
  end
32
38
  output vaultbags.join("\n")
33
39
  end
34
-
35
- private
36
-
37
- def bag_is_vault?(bagname)
38
- bag = Chef::DataBag.load(bagname)
39
- # vaults have at even number of keys >= 2
40
- return false unless bag.keys.size >= 2 && 0 == bag.keys.size % 2
41
- # partition into those that end in _keys
42
- keylike, notkeylike = bag.keys.partition { |k| k =~ /_keys$/ }
43
- # there must be an equal number of keyline and not-keylike items
44
- return false unless keylike.size == notkeylike.size
45
- # strip the _keys suffix and check if the sets match
46
- keylike.map! { |k| k.gsub(/_keys$/, '') }
47
- return false unless keylike.sort == notkeylike.sort
48
- # it's (probably) a vault
49
- true
50
- end
51
40
  end
52
41
  end
53
42
  end
@@ -22,27 +22,21 @@ class Chef
22
22
 
23
23
  banner "knife vault refresh VAULT ITEM"
24
24
 
25
+ option :clean_unknown_clients,
26
+ :long => '--clean-unknown-clients',
27
+ :description => 'Remove unknown clients during refresh'
28
+
25
29
  def run
26
30
  vault = @name_args[0]
27
31
  item = @name_args[1]
32
+ clean = config[:clean_unknown_clients]
28
33
 
29
34
  set_mode(config[:vault_mode])
30
35
 
31
36
  if vault && item
32
37
  begin
33
38
  vault_item = ChefVault::Item.load(vault, item)
34
- search = vault_item.search
35
-
36
- unless search
37
- raise ChefVault::Exceptions::SearchNotFound,
38
- "#{vault}/#{item} does not have a stored search_query, "\
39
- "probably because it was created with an older version "\
40
- "of chef-vault. Use 'knife vault update' to update the "\
41
- "databag with the search query."
42
- end
43
-
44
- vault_item.clients(search)
45
- vault_item.save
39
+ vault_item.refresh(clean)
46
40
  rescue ChefVault::Exceptions::KeysNotFound,
47
41
  ChefVault::Exceptions::ItemNotFound
48
42
 
@@ -52,7 +52,7 @@ class Chef
52
52
  end
53
53
 
54
54
  def rotate_vault_item_keys(vault, item, clean_unknown_clients)
55
- puts "Rotating keys for: #{vault} #{item}"
55
+ $stdout.puts "Rotating keys for: #{vault} #{item}"
56
56
  ChefVault::Item.load(vault, item).rotate_keys!(clean_unknown_clients)
57
57
  end
58
58
  end
@@ -20,7 +20,7 @@ class Chef
20
20
  class VaultShow < Knife
21
21
  include Chef::Knife::VaultBase
22
22
 
23
- banner "knife vault show VAULT ITEM [VALUES] (options)"
23
+ banner "knife vault show VAULT [ITEM] [VALUES] (options)"
24
24
 
25
25
  option :mode,
26
26
  :short => '-M MODE',
@@ -40,6 +40,9 @@ class Chef
40
40
  if vault && item
41
41
  set_mode(config[:vault_mode])
42
42
  print_values(vault, item, values)
43
+ elsif vault
44
+ set_mode(config[:vault_mode])
45
+ print_keys(vault)
43
46
  else
44
47
  show_usage
45
48
  end
@@ -83,6 +86,17 @@ class Chef
83
86
  end
84
87
  output(output_data)
85
88
  end
89
+
90
+ def print_keys(vault)
91
+ if bag_is_vault?(vault)
92
+ bag = Chef::DataBag.load(vault)
93
+ split_vault_keys(bag)[1].each do |item|
94
+ output item
95
+ end
96
+ else
97
+ output "data bag #{vault} is not a chef-vault"
98
+ end
99
+ end
86
100
  end
87
101
  end
88
102
  end
@@ -74,7 +74,7 @@ class Chef
74
74
  if clean
75
75
  clients = vault_item.clients().clone().sort()
76
76
  clients.each do |client|
77
- puts "Deleting #{client}"
77
+ $stdout.puts "Deleting #{client}"
78
78
  vault_item.keys.delete(client, "clients")
79
79
  end
80
80
  end
@@ -6,6 +6,12 @@ RSpec.describe ChefVault::Certificate do
6
6
  allow(ChefVault::Item).to receive(:load).with("foo", "bar"){ item }
7
7
  allow(item).to receive(:[]).with("id"){ "bar" }
8
8
  allow(item).to receive(:[]).with("contents"){ "baz" }
9
+ @orig_stdout = $stdout
10
+ $stdout = File.open(File::NULL, 'w')
11
+ end
12
+
13
+ after do
14
+ $stdout = @orig_stdout
9
15
  end
10
16
 
11
17
  describe '#new' do
@@ -22,8 +28,9 @@ RSpec.describe ChefVault::Certificate do
22
28
 
23
29
  describe 'decrypt_contents' do
24
30
  it 'echoes warning' do
25
- expect(STDOUT).to receive(:puts).with("WARNING: This method is deprecated, please switch to item['value'] calls")
26
- cert.decrypt_contents
31
+ expect { cert.decrypt_contents }
32
+ .to output("WARNING: This method is deprecated, please switch to item['value'] calls\n")
33
+ .to_stdout
27
34
  end
28
35
 
29
36
  it 'returns items contents' do
@@ -51,9 +51,84 @@ module BorkedNodeWithoutPublicKey
51
51
  end
52
52
 
53
53
  RSpec.describe ChefVault::Item do
54
+ before do
55
+ @orig_stdout = $stdout
56
+ $stdout = File.open(File::NULL, 'w')
57
+ end
58
+
59
+ after do
60
+ $stdout = @orig_stdout
61
+ end
62
+
54
63
  subject(:item) { ChefVault::Item.new("foo", "bar") }
55
64
 
56
- describe '#new' do
65
+ describe 'vault probe predicates' do
66
+ before do
67
+ # a normal data bag item
68
+ @db = { 'foo' => '...' }
69
+ @dbi = Chef::DataBagItem.new
70
+ @dbi.data_bag('normal')
71
+ @dbi.raw_data = { 'id' => 'foo', 'foo' => 'bar' }
72
+ allow(@db).to receive(:load).with('foo').and_return(@dbi)
73
+ allow(Chef::DataBag).to receive(:load).with('normal').and_return(@db)
74
+ allow(Chef::DataBagItem).to receive(:load).with('normal', 'foo').and_return(@dbi)
75
+
76
+ # an encrypted data bag item (non-vault)
77
+ @encdb = { 'foo' => '...' }
78
+ @encdbi = Chef::DataBagItem.new
79
+ @encdbi.data_bag('encrypted')
80
+ @encdbi.raw_data = {
81
+ 'id' => 'foo',
82
+ 'foo' => { 'encrypted_data' => '...' }
83
+ }
84
+ allow(@encdb).to receive(:load).with('foo').and_return(@encdbi)
85
+ allow(Chef::DataBag).to receive(:load).with('encrypted').and_return(@encdb)
86
+ allow(Chef::DataBagItem).to receive(:load).with('encrypted', 'foo').and_return(@encdbi)
87
+
88
+ # two items that make up a vault
89
+ @vaultdb = { 'foo' => '...', 'foo_keys' => '...' }
90
+ @vaultdbi = Chef::DataBagItem.new
91
+ @vaultdbi.data_bag('vault')
92
+ @vaultdbi.raw_data = {
93
+ 'id' => 'foo',
94
+ 'foo' => { 'encrypted_data' => '...' }
95
+ }
96
+ allow(@vaultdb).to receive(:load).with('foo').and_return(@vaultdbi)
97
+ @vaultdbki = Chef::DataBagItem.new
98
+ @vaultdbki.data_bag('vault')
99
+ @vaultdbki.raw_data = { 'id' => 'foo_keys' }
100
+ allow(@vaultdb).to receive(:load).with('foo_keys').and_return(@vaultdbki)
101
+ allow(Chef::DataBag).to receive(:load).with('vault').and_return(@vaultdb)
102
+ allow(Chef::DataBagItem).to receive(:load).with('vault', 'foo').and_return(@vaultdbi)
103
+ end
104
+
105
+ describe '::vault?' do
106
+ it 'should detect a vault item' do
107
+ expect(ChefVault::Item.vault?('vault', 'foo')).to be_truthy
108
+ end
109
+
110
+ it 'should detect non-vault items' do
111
+ expect(ChefVault::Item.vault?('normal', 'foo')).not_to be_truthy
112
+ expect(ChefVault::Item.vault?('encrypted', 'foo')).not_to be_truthy
113
+ end
114
+ end
115
+
116
+ describe '::data_bag_item_type' do
117
+ it 'should detect a vault item' do
118
+ expect(ChefVault::Item.data_bag_item_type('vault', 'foo')).to eq(:vault)
119
+ end
120
+
121
+ it 'should detect an encrypted data bag item' do
122
+ expect(ChefVault::Item.data_bag_item_type('encrypted', 'foo')).to eq(:encrypted)
123
+ end
124
+
125
+ it 'should detect a normal data bag item' do
126
+ expect(ChefVault::Item.data_bag_item_type('normal', 'foo')).to eq(:normal)
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '::new' do
57
132
  it { should be_an_instance_of ChefVault::Item }
58
133
 
59
134
  its(:keys) { should be_an_instance_of ChefVault::ItemKeys }
@@ -65,14 +140,80 @@ RSpec.describe ChefVault::Item do
65
140
  specify { expect(item.keys['id']).to eq 'bar_keys' }
66
141
 
67
142
  specify { expect(item.keys.data_bag).to eq 'foo' }
143
+
144
+ it 'defaults the node name' do
145
+ item = ChefVault::Item.new('foo', 'bar')
146
+ expect(item.node_name).to eq(Chef::Config[:node_name])
147
+ end
148
+
149
+ it 'defaults the client key path' do
150
+ item = ChefVault::Item.new('foo', 'bar')
151
+ expect(item.client_key_path).to eq(Chef::Config[:client_key])
152
+ end
153
+
154
+ it 'allows for a node name override' do
155
+ item = ChefVault::Item.new('foo', 'bar', node_name: 'baz')
156
+ expect(item.node_name).to eq('baz')
157
+ end
158
+
159
+ it 'allows for a client key path override' do
160
+ item = ChefVault::Item.new('foo', 'bar', client_key_path: '/foo/client.pem')
161
+ expect(item.client_key_path).to eq('/foo/client.pem')
162
+ end
163
+
164
+ it 'allows for both node name and client key overrides' do
165
+ item = ChefVault::Item.new(
166
+ 'foo', 'bar',
167
+ node_name: 'baz',
168
+ client_key_path: '/foo/client.pem'
169
+ )
170
+ expect(item.node_name).to eq('baz')
171
+ expect(item.client_key_path).to eq('/foo/client.pem')
172
+ end
173
+ end
174
+
175
+ describe '::load' do
176
+ it 'allows for both node name and client key overrides' do
177
+ keys_db = Chef::DataBagItem.new
178
+ keys_db.raw_data = {
179
+ 'id' => 'bar_keys',
180
+ 'baz' => '...'
181
+ }
182
+ allow(ChefVault::ItemKeys)
183
+ .to receive(:load)
184
+ .and_return(keys_db)
185
+ fh = double 'private key handle'
186
+ allow(fh).to receive(:read).and_return('...')
187
+ allow(File).to receive(:open).and_return(fh)
188
+ privkey = double 'private key contents'
189
+ allow(privkey).to receive(:private_decrypt).and_return('sekrit')
190
+ allow(OpenSSL::PKey::RSA).to receive(:new).and_return(privkey)
191
+ allow(Chef::EncryptedDataBagItem).to receive(:load).and_return(
192
+ 'id' => 'bar',
193
+ 'password' => '12345'
194
+ )
195
+ item = ChefVault::Item.load(
196
+ 'foo', 'bar',
197
+ node_name: 'baz',
198
+ client_key_path: '/foo/client.pem'
199
+ )
200
+ expect(item.node_name).to eq('baz')
201
+ expect(item.client_key_path).to eq('/foo/client.pem')
202
+ end
68
203
  end
69
204
 
70
205
  describe '#save' do
71
206
  context 'when item["id"] is bar.bar' do
72
207
  let(:item) { ChefVault::Item.new("foo", "bar.bar") }
73
-
74
208
  specify { expect { item.save }.to raise_error }
75
209
  end
210
+
211
+ it 'should validate that the id of the vault matches the id of the keys data bag' do
212
+ item = ChefVault::Item.new('foo', 'bar')
213
+ item['id'] = 'baz'
214
+ item.keys['clients'] = %w(admin)
215
+ expect { item.save }.to raise_error(ChefVault::Exceptions::IdMismatch)
216
+ end
76
217
  end
77
218
 
78
219
  describe '#clients' do
@@ -87,7 +228,19 @@ RSpec.describe ChefVault::Item do
87
228
  it 'should emit a warning if search returns a node without a public key' do
88
229
  # it should however emit a warning that you have a borked node
89
230
  expect { @vaultitem.clients('*:*') }
90
- .to output(/node 'bar' has no private key; skipping/).to_stderr
231
+ .to output(/node 'bar' has no private key; skipping/).to_stdout
232
+ end
233
+
234
+ it 'should accept a client object and not perform a search' do
235
+ client = Chef::ApiClient.new
236
+ client.name 'foo'
237
+ privkey = OpenSSL::PKey::RSA.new(1024)
238
+ pubkey = privkey.public_key
239
+ client.public_key(pubkey.to_pem)
240
+ expect(Chef::Search::Query).not_to receive(:new)
241
+ expect(ChefVault::ChefPatch::User).not_to receive(:load)
242
+ @vaultitem.clients(client)
243
+ expect(@vaultitem.clients).to include('foo')
91
244
  end
92
245
  end
93
246
 
@@ -99,4 +252,12 @@ RSpec.describe ChefVault::Item do
99
252
  .to raise_error(ChefVault::Exceptions::AdminNotFound)
100
253
  end
101
254
  end
255
+
256
+ describe '#raw_keys' do
257
+ it 'should return the keys of the underlying data bag item' do
258
+ item = ChefVault::Item.new('foo', 'bar')
259
+ item['foo'] = 'bar'
260
+ expect(item.raw_keys).to eq(%w(id foo))
261
+ end
262
+ end
102
263
  end