chef-vault 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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