chef-vault 4.2.5 → 4.2.9

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.
@@ -0,0 +1,293 @@
1
+ RSpec.describe ChefVault::ItemKeys do
2
+ describe "#new" do
3
+ let(:keys) { ChefVault::ItemKeys.new("foo", "bar") }
4
+ let(:shared_secret) { "super_secret" }
5
+ let(:public_key_string) do
6
+ "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyMXT9IOV9pkQsxsnhSx8\n8RX6GW3caxkjcXFfHg6E7zUVBFAsfw4B1D+eHAks3qrDB7UrUxsmCBXwU4dQHaQy\ngAn5Sv0Jc4CejDNL2EeCBLZ4TF05odHmuzyDdPkSZP6utpR7+uF7SgVQedFGySIB\nih86aM+HynhkJqgJYhoxkrdo/JcWjpk7YEmWb6p4esnvPWOpbcjIoFs4OjavWBOF\niTfpkS0SkygpLi/iQu9RQfd4hDMWCc6yh3Th/1nVMUd+xQCdUK5wxluAWSv8U0zu\nhiIlZNazpCGHp+3QdP3f6rebmQA8pRM8qT5SlOvCYPk79j+IMUVSYrR4/DTZ+VM+\naQIDAQAB\n-----END PUBLIC KEY-----\n"
7
+ end
8
+
9
+ it "'foo' is assigned to @data_bag" do
10
+ expect(keys.data_bag).to eq "foo"
11
+ end
12
+
13
+ it "sets the keys id to 'bar'" do
14
+ expect(keys["id"]).to eq "bar"
15
+ end
16
+
17
+ it "initializes the keys[admin] to an empty array" do
18
+ expect(keys["admins"]).to eq []
19
+ end
20
+
21
+ it "initializes the keys[clients] to an empty array" do
22
+ expect(keys["admins"]).to eq []
23
+ end
24
+
25
+ shared_context "key mgmt operations" do
26
+
27
+ shared_examples_for "proper key management" do
28
+ let(:chef_key) { ChefVault::Actor.new(type, name) }
29
+ before do
30
+ allow(chef_key).to receive(:key) { public_key_string }
31
+ keys.add(chef_key, shared_secret)
32
+ end
33
+
34
+ describe "#add" do
35
+ after do
36
+ keys.delete(chef_key)
37
+ end
38
+
39
+ context "when key is already there" do
40
+ context "when skip_reencryption is not specified (default to false)" do
41
+ it "encodes key in the data bag item under the actor's name and the name in the raw data" do
42
+ expect(described_class).to receive(:encode_key).with(public_key_string, shared_secret).and_return("encrypted_result")
43
+ keys.add(chef_key, shared_secret)
44
+ expect(keys[name]).to eq("encrypted_result")
45
+ expect(keys[type].include?(name)).to eq(true)
46
+ expect(keys.include?(name)).to eq(true)
47
+ end
48
+ end
49
+
50
+ context "when skip_reencryption is true" do
51
+ it "keeps the encoded key in the data bag item under the actor's name and the name in the raw data" do
52
+ expect(described_class).not_to receive(:encode_key).with(public_key_string, shared_secret)
53
+ keys.skip_reencryption = true
54
+ keys.add(chef_key, shared_secret)
55
+ expect(keys[name]).not_to be_empty
56
+ expect(keys[type].include?(name)).to eq(true)
57
+ expect(keys.include?(name)).to eq(true)
58
+ end
59
+ end
60
+ end
61
+
62
+ context "when keys not already there" do
63
+ before do
64
+ keys.delete(chef_key)
65
+ end
66
+ it "stores the encoded key in the data bag item under the actor's name and the name in the raw data" do
67
+ expect(described_class).to receive(:encode_key).with(public_key_string, shared_secret).and_return("encrypted_result")
68
+ keys.add(chef_key, shared_secret)
69
+ expect(keys[name]).to eq("encrypted_result")
70
+ expect(keys[type].include?(name)).to eq(true)
71
+ expect(keys.include?(name)).to eq(true)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "#delete" do
77
+ before do
78
+ keys.add(chef_key, shared_secret)
79
+ end
80
+
81
+ it "removes the actor's name from the data bag and from the array for the actor's type" do
82
+ keys.delete(chef_key)
83
+ expect(keys.has_key?(chef_key.name)).to eq(false)
84
+ expect(keys[type].include?(name)).to eq(false)
85
+ expect(keys.include?(name)).to eq(false)
86
+ end
87
+ end
88
+ end
89
+
90
+ context "when a client is added" do
91
+ let(:name) { "client_name" }
92
+ let(:type) { "clients" }
93
+ it_should_behave_like "proper key management"
94
+ end
95
+
96
+ context "when a admin is added" do
97
+ let(:name) { "admin_name" }
98
+ let(:type) { "admins" }
99
+ it_should_behave_like "proper key management"
100
+ end
101
+ end
102
+
103
+ context "when running with chef-zero" do
104
+ let(:server) { chef_zero }
105
+ before { server.start_background }
106
+ after { server.stop }
107
+
108
+ include_context "key mgmt operations"
109
+
110
+ describe "#save" do
111
+ let(:client_name) { "client_name" }
112
+ let(:chef_key) { ChefVault::Actor.new("clients", client_name) }
113
+ before { allow(chef_key).to receive(:key) { public_key_string } }
114
+
115
+ it "should save the key data" do
116
+ keys.add(chef_key, shared_secret)
117
+ keys.save("bar")
118
+ expect(Chef::DataBagItem.load("foo", "bar").to_hash).to include("id" => "bar")
119
+ expect(keys[client_name]).not_to be_empty
120
+ keys.delete(chef_key)
121
+ keys.save("bar")
122
+ expect(keys[client_name]).to be_nil
123
+ end
124
+
125
+ it "should save the key data in sparse mode" do
126
+ keys.add(chef_key, shared_secret)
127
+ keys.mode("sparse")
128
+ keys.save("bar")
129
+ expect(Chef::DataBagItem.load("foo", "bar").to_hash).to include("id" => "bar")
130
+ expect(Chef::DataBagItem.load("foo", "bar_key_client_name").to_hash).to include("id" => "bar_key_client_name")
131
+ expect(keys[client_name]).not_to be_empty
132
+ keys.delete(chef_key)
133
+ keys.save("bar")
134
+ expect(keys[client_name]).to be_nil
135
+ keys.mode("default")
136
+ end
137
+
138
+ it "should remove key data in sparse mode" do
139
+ keys.add(chef_key, shared_secret)
140
+ keys.save("bar")
141
+ expect(keys[client_name]).not_to be_empty
142
+ keys.mode("sparse")
143
+ keys.save("bar")
144
+ expect(Chef::DataBagItem.load("foo", "bar").to_hash[client_name]).to be_nil
145
+ expect(Chef::DataBagItem.load("foo", "bar_key_client_name").to_hash).to include("id" => "bar_key_client_name")
146
+ expect(Chef::DataBagItem.load("foo", "bar_key_client_name").to_hash["client_name"]).not_to be_empty
147
+ keys.delete(chef_key)
148
+ keys.save("bar")
149
+ expect(keys[client_name]).to be_nil
150
+ keys.mode("default")
151
+ end
152
+ end
153
+
154
+ describe "#destroy" do
155
+ let(:client_name) { "client_name" }
156
+ let(:chef_key) { ChefVault::Actor.new("clients", client_name) }
157
+ before { allow(chef_key).to receive(:key) { public_key_string } }
158
+
159
+ it "should destroy the keys" do
160
+ keys.add(chef_key, shared_secret)
161
+ keys.save("bar")
162
+ keys.destroy
163
+ expect { Chef::DataBagItem.load("foo", "bar") }.to raise_error(Net::HTTPClientException)
164
+ end
165
+
166
+ it "should destroy the keys in sparse mode" do
167
+ keys.add(chef_key, shared_secret)
168
+ keys.mode("sparse")
169
+ keys.save("bar")
170
+ keys.destroy
171
+ expect { Chef::DataBagItem.load("foo", "bar") }.to raise_error(Net::HTTPClientException)
172
+ expect { Chef::DataBagItem.load("foo", "bar_key_client_name") }.to raise_error(Net::HTTPClientException)
173
+ keys.mode("default")
174
+ end
175
+ end
176
+ end
177
+
178
+ context "when running with chef-solo" do
179
+ before { Chef::Config[:solo_legacy_mode] = true }
180
+ after { Chef::Config[:solo_legacy_mode] = false }
181
+
182
+ include_context "key mgmt operations"
183
+
184
+ describe "#find_solo_path" do
185
+ context "when data_bag_path is an array" do
186
+ before do
187
+ allow(File).to receive(:exist?)
188
+ Chef::Config[:data_bag_path] = ["/tmp/data_bag", "/tmp/site_data_bag"]
189
+ end
190
+
191
+ it "should return an existing item" do
192
+ allow(File).to receive(:exist?).with("/tmp/site_data_bag/foo/bar.json").and_return(true)
193
+ dbp, dbi = keys.find_solo_path("bar")
194
+ expect(dbp).to eql("/tmp/site_data_bag/foo").or eq("C:/tmp/data_bag/foo")
195
+ expect(dbi).to eql("/tmp/site_data_bag/foo/bar.json").or eq("C:/tmp/data_bag/foo/bar.json")
196
+ end
197
+
198
+ it "should return the first item if none exist" do
199
+ dbp, dbi = keys.find_solo_path("bar")
200
+ expect(dbp).to eql("/tmp/data_bag/foo").or eq("C:/tmp/data_bag/foo")
201
+ expect(dbi).to eql("/tmp/data_bag/foo/bar.json").or eq("C:/tmp/data_bag/foo/bar.json")
202
+ end
203
+ end
204
+
205
+ context "when data_bag_path is a string" do
206
+ it "should return the path to the bag and the item" do
207
+ Chef::Config[:data_bag_path] = "/tmp/data_bag"
208
+ dbp, dbi = keys.find_solo_path("bar")
209
+ expect(dbp).to eql("/tmp/data_bag/foo").or eq("C:/tmp/data_bag/foo")
210
+ expect(dbi).to eql("/tmp/data_bag/foo/bar.json").or eq("C:/tmp/data_bag/foo/bar.json")
211
+ end
212
+ end
213
+ end
214
+
215
+ describe "#save" do
216
+ let(:client_name) { "client_name" }
217
+ let(:chef_key) { ChefVault::Actor.new("clients", client_name) }
218
+ let(:data_bag_path) { Dir.mktmpdir("vault_item_keys") }
219
+
220
+ before do
221
+ Chef::Config[:data_bag_path] = data_bag_path
222
+ allow(chef_key).to receive(:key) { public_key_string }
223
+ end
224
+
225
+ it "should save the key data" do
226
+ keys.add(chef_key, shared_secret)
227
+ keys.save("bar")
228
+ expect(File.read(File.join(data_bag_path, "foo", "bar.json"))).to match(/"id":.*"bar"/)
229
+ expect(keys[client_name]).not_to be_empty
230
+ keys.delete(chef_key)
231
+ keys.save("bar")
232
+ expect(keys[client_name]).to be_nil
233
+ end
234
+
235
+ it "should save the key data in sparse mode" do
236
+ keys.add(chef_key, shared_secret)
237
+ keys.mode("sparse")
238
+ keys.save("bar")
239
+ expect(File.read(File.join(data_bag_path, "foo", "bar.json"))).to match(/"id":.*"bar"/)
240
+ expect(File.read(File.join(data_bag_path, "foo", "bar_key_client_name.json"))).to match(/"id":.*"bar_key_client_name"/)
241
+ expect(keys[client_name]).not_to be_empty
242
+ keys.delete(chef_key)
243
+ keys.save("bar")
244
+ expect(keys[client_name]).to be_nil
245
+ keys.mode("default")
246
+ end
247
+
248
+ it "should remove key data in sparse mode" do
249
+ keys.add(chef_key, shared_secret)
250
+ keys.save("bar")
251
+ expect(keys[client_name]).not_to be_empty
252
+ keys.mode("sparse")
253
+ keys.save("bar")
254
+ expect(File.read(File.join(data_bag_path, "foo", "bar.json"))).to match(/"id":.*"bar"/)
255
+ expect(File.read(File.join(data_bag_path, "foo", "bar_key_client_name.json"))).to match(/"id":.*"bar_key_client_name"/)
256
+ expect(File.read(File.join(data_bag_path, "foo", "bar_key_client_name.json"))).to match(/"client_name": ".*"/)
257
+ keys.delete(chef_key)
258
+ keys.save("bar")
259
+ expect(keys[client_name]).to be_nil
260
+ keys.mode("default")
261
+ end
262
+ end
263
+
264
+ describe "#destroy" do
265
+ let(:client_name) { "client_name" }
266
+ let(:chef_key) { ChefVault::Actor.new("clients", client_name) }
267
+ let(:data_bag_path) { Dir.mktmpdir("vault_item_keys") }
268
+
269
+ before do
270
+ Chef::Config[:data_bag_path] = data_bag_path
271
+ allow(chef_key).to receive(:key) { public_key_string }
272
+ end
273
+
274
+ it "should destroy the keys" do
275
+ keys.add(chef_key, shared_secret)
276
+ keys.save("bar")
277
+ keys.destroy
278
+ expect(File.exist?(File.join(data_bag_path, "foo", "bar.json"))).to be(false)
279
+ end
280
+
281
+ it "should destroy the keys in sparse mode" do
282
+ keys.add(chef_key, shared_secret)
283
+ keys.mode("sparse")
284
+ keys.save("bar")
285
+ keys.destroy
286
+ expect(File.exist?(File.join(data_bag_path, "foo", "bar.json"))).to be(false)
287
+ expect(File.exist?(File.join(data_bag_path, "foo", "bar_key_client_name.json"))).to be(false)
288
+ keys.mode("default")
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,394 @@
1
+ require "openssl"
2
+
3
+ RSpec.describe ChefVault::Item do
4
+ subject(:item) { ChefVault::Item.new("foo", "bar") }
5
+ let(:options) { { chef: "bar", values: "foo" } }
6
+
7
+ before do
8
+ item["foo"] = "bar"
9
+ http_response = double("http_response")
10
+ allow(http_response).to receive(:code).and_return("404")
11
+ non_existing = Net::HTTPClientException.new("http error message", http_response)
12
+
13
+ allow(Chef::DataBagItem).to receive(:load).with(anything, /_key_/).and_raise(non_existing)
14
+ end
15
+
16
+ describe "vault probe predicates" do
17
+ before do
18
+ # a normal data bag item
19
+ @db = { "foo" => "..." }
20
+ @dbi = Chef::DataBagItem.new
21
+ @dbi.data_bag("normal")
22
+ @dbi.raw_data = { "id" => "foo", "foo" => "bar" }
23
+ allow(@db).to receive(:load).with("foo").and_return(@dbi)
24
+ allow(Chef::DataBag).to receive(:load).with("normal").and_return(@db)
25
+ allow(Chef::DataBagItem).to receive(:load).with("normal", "foo").and_return(@dbi)
26
+
27
+ # an encrypted data bag item (non-vault)
28
+ @encdb = { "foo" => "..." }
29
+ @encdbi = Chef::DataBagItem.new
30
+ @encdbi.data_bag("encrypted")
31
+ @encdbi.raw_data = {
32
+ "id" => "foo",
33
+ "foo" => { "encrypted_data" => "..." },
34
+ }
35
+ allow(@encdb).to receive(:load).with("foo").and_return(@encdbi)
36
+ allow(Chef::DataBag).to receive(:load).with("encrypted").and_return(@encdb)
37
+ allow(Chef::DataBagItem).to receive(:load).with("encrypted", "foo").and_return(@encdbi)
38
+
39
+ # two items that make up a vault
40
+ @vaultdb = { "foo" => "...", "foo_keys" => "..." }
41
+ @vaultdbi = Chef::DataBagItem.new
42
+ @vaultdbi.data_bag("vault")
43
+ @vaultdbi.raw_data = {
44
+ "id" => "foo",
45
+ "foo" => { "encrypted_data" => "..." },
46
+ }
47
+ allow(@vaultdb).to receive(:load).with("foo").and_return(@vaultdbi)
48
+ @vaultdbki = Chef::DataBagItem.new
49
+ @vaultdbki.data_bag("vault")
50
+ @vaultdbki.raw_data = { "id" => "foo_keys" }
51
+
52
+ allow(@vaultdb).to receive(:load).with("foo_keys").and_return(@vaultdbki)
53
+ allow(Chef::DataBag).to receive(:load).with("vault").and_return(@vaultdb)
54
+ allow(Chef::DataBagItem).to receive(:load).with("vault", "foo").and_return(@vaultdbi)
55
+ @orig_stdout = $stdout
56
+ $stdout = File.open(File::NULL, "w")
57
+ end
58
+
59
+ after do
60
+ $stdout = @orig_stdout
61
+ end
62
+
63
+ describe "::vault?" do
64
+ it "should detect a vault item" do
65
+ expect(ChefVault::Item.vault?("vault", "foo")).to be_truthy
66
+ end
67
+
68
+ it "should detect non-vault items" do
69
+ expect(ChefVault::Item.vault?("normal", "foo")).not_to be_truthy
70
+ expect(ChefVault::Item.vault?("encrypted", "foo")).not_to be_truthy
71
+ end
72
+ end
73
+
74
+ describe "::data_bag_item_type" do
75
+ it "should detect a vault item" do
76
+ expect(ChefVault::Item.data_bag_item_type("vault", "foo")).to eq(:vault)
77
+ end
78
+
79
+ it "should detect an encrypted data bag item" do
80
+ expect(ChefVault::Item.data_bag_item_type("encrypted", "foo")).to eq(:encrypted)
81
+ end
82
+
83
+ it "should detect a normal data bag item" do
84
+ expect(ChefVault::Item.data_bag_item_type("normal", "foo")).to eq(:normal)
85
+ end
86
+
87
+ it "outputs the loaded item credentials on the stdout" do
88
+ expect { ChefVault::Item.format_output(options[:values], @dbi) }.to output("foo: bar\n").to_stdout
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "::new" do
94
+ it "item[keys] is an instance of ChefVault::ItemKeys" do
95
+ expect(item.keys).to be_an_instance_of(ChefVault::ItemKeys)
96
+ end
97
+
98
+ it "the item's 'vault' parameter is assigned to data_bag" do
99
+ expect(item.data_bag).to eq "foo"
100
+ end
101
+
102
+ it "the vault item name is assiged to the data bag ['id']" do
103
+ expect(item["id"]).to eq "bar"
104
+ end
105
+
106
+ it "creates a corresponding 'keys' data bag with an '_keys' id" do
107
+ expect(item.keys["id"]).to eq "bar_keys"
108
+ end
109
+
110
+ it "sets the item keys data bag to 'foo'" do
111
+ expect(item.keys.data_bag).to eq "foo"
112
+ end
113
+
114
+ it "defaults the node name" do
115
+ item = ChefVault::Item.new("foo", "bar")
116
+ expect(item.node_name).to eq(Chef::Config[:node_name])
117
+ end
118
+
119
+ it "defaults the client key path" do
120
+ item = ChefVault::Item.new("foo", "bar")
121
+ expect(item.client_key_path).to eq(Chef::Config[:client_key])
122
+ end
123
+
124
+ it "defaults the client key contents" do
125
+ item = ChefVault::Item.new("foo", "bar")
126
+ expect(item.client_key_contents).to eq(Chef::Config[:client_key_contents])
127
+ end
128
+
129
+ it "allows for a node name override" do
130
+ item = ChefVault::Item.new("foo", "bar", node_name: "baz")
131
+ expect(item.node_name).to eq("baz")
132
+ end
133
+
134
+ it "allows for a client key path override" do
135
+ item = ChefVault::Item.new("foo", "bar", client_key_path: "/foo/client.pem")
136
+ expect(item.client_key_path).to eq("/foo/client.pem")
137
+ end
138
+
139
+ it "allows for a client key contents override" do
140
+ item = ChefVault::Item.new("foo", "bar", client_key_contents: "baz")
141
+ expect(item.client_key_contents).to eq("baz")
142
+ end
143
+
144
+ it "allows for both node name and client key overrides" do
145
+ item = ChefVault::Item.new(
146
+ "foo", "bar",
147
+ node_name: "baz",
148
+ client_key_path: "/foo/client.pem"
149
+ )
150
+ expect(item.node_name).to eq("baz")
151
+ expect(item.client_key_path).to eq("/foo/client.pem")
152
+ end
153
+
154
+ it "allows for node name, client key and client key contents overrides" do
155
+ item = ChefVault::Item.new(
156
+ "foo", "bar",
157
+ node_name: "baz",
158
+ client_key_path: "/foo/client.pem",
159
+ client_key_contents: "qux"
160
+ )
161
+ expect(item.node_name).to eq("baz")
162
+ expect(item.client_key_path).to eq("/foo/client.pem")
163
+ expect(item.client_key_contents).to eq("qux")
164
+ end
165
+ end
166
+
167
+ describe "::load" do
168
+ it "allows for both node name and client key overrides" do
169
+ keys_db = Chef::DataBagItem.new
170
+ keys_db.raw_data = {
171
+ "id" => "bar_keys",
172
+ "baz" => "...",
173
+ }
174
+ allow(ChefVault::ItemKeys)
175
+ .to receive(:load)
176
+ .and_return(keys_db)
177
+ fh = double "private key handle"
178
+ allow(fh).to receive(:read).and_return("...")
179
+ allow(File).to receive(:open).and_return(fh)
180
+ privkey = double "private key contents"
181
+ allow(privkey).to receive(:private_decrypt).and_return("sekrit")
182
+ allow(OpenSSL::PKey::RSA).to receive(:new).and_return(privkey)
183
+ allow(Chef::EncryptedDataBagItem).to receive(:load).and_return(
184
+ "id" => "bar",
185
+ "password" => "12345"
186
+ )
187
+ item = ChefVault::Item.load(
188
+ "foo", "bar",
189
+ node_name: "baz",
190
+ client_key_path: "/foo/client.pem"
191
+ )
192
+ expect(item.node_name).to eq("baz")
193
+ expect(item.client_key_path).to eq("/foo/client.pem")
194
+ end
195
+ end
196
+
197
+ describe "#save" do
198
+ context 'when item["id"] is bar.bar' do
199
+ let(:item) { ChefVault::Item.new("foo", "bar.bar") }
200
+ it "raises an error on save with an invalid item['id']" do
201
+ expect { item.save }.to raise_error(RuntimeError)
202
+ end
203
+ end
204
+
205
+ it "validates that the id of the vault matches the id of the keys data bag" do
206
+ item = ChefVault::Item.new("foo", "bar")
207
+ item["id"] = "baz"
208
+ item.keys["clients"] = %w{admin}
209
+ expect { item.save }.to raise_error(ChefVault::Exceptions::IdMismatch)
210
+ end
211
+ end
212
+
213
+ describe "#refresh" do
214
+ let(:node) { { "name" => "testnode" } }
215
+
216
+ it "saves only the keys" do
217
+ keys = double("keys",
218
+ search_query: "*:*",
219
+ add: nil,
220
+ admins: [],
221
+ clients: ["testnode"])
222
+ allow(keys).to receive(:[]).with("id").and_return("bar_keys")
223
+ allow(ChefVault::ItemKeys).to receive(:new).and_return(keys)
224
+
225
+ item = ChefVault::Item.new("foo", "bar")
226
+
227
+ query = double("query")
228
+ allow(Chef::Search::Query).to receive(:new).and_return(query)
229
+ allow(query).to receive(:search).and_yield(node)
230
+
231
+ client_key = double("client_key",
232
+ name: "testnode",
233
+ public_key: OpenSSL::PKey::RSA.new(1024).public_key)
234
+ allow(item).to receive(:load_actor).with("testnode", "clients").and_return(client_key)
235
+
236
+ expect(item).not_to receive(:save)
237
+ expect(keys).to receive(:save)
238
+ item.refresh
239
+ end
240
+ end
241
+
242
+ describe "#clients" do
243
+ context "when search returns a node with a valid client backing it and one without a valid client" do
244
+ let(:node_with_valid_client) { { "name" => "foo" } }
245
+ let(:node_without_valid_client) { { "name" => "bar" } }
246
+ let(:query_result) { double("chef search results") }
247
+ let(:client_key) { double("chef key") }
248
+
249
+ before do
250
+ # node with valid client proper loads client key
251
+ allow(item).to receive(:load_actor).with("foo", "clients").and_return(client_key)
252
+ privkey = OpenSSL::PKey::RSA.new(1024)
253
+ pubkey = privkey.public_key
254
+ allow(client_key).to receive(:key).and_return(pubkey.to_pem)
255
+ allow(client_key).to receive(:name).and_return("foo")
256
+ allow(client_key).to receive(:type).and_return("clients")
257
+
258
+ # node without client throws relevant error on key load
259
+ allow(item).to receive(:load_actor).with("bar", "clients").and_raise(ChefVault::Exceptions::ClientNotFound)
260
+
261
+ allow(query_result)
262
+ .to receive(:search)
263
+ .with(Symbol, String, Hash)
264
+ .and_yield(node_with_valid_client).and_yield(node_without_valid_client)
265
+ allow(Chef::Search::Query)
266
+ .to receive(:new)
267
+ .and_return(query_result)
268
+ end
269
+
270
+ it "should not blow up when search returns a node without a public key" do
271
+ # try to set clients when we know a node is missing a public key
272
+ # this should not die as of v2.4.1
273
+ expect { item.clients("*:*") }.not_to raise_error
274
+ end
275
+
276
+ it "should emit a warning if search returns a node without a public key" do
277
+ # it should however emit a warning that you have a borked node
278
+ expect(ChefVault::Log).to receive(:warn).with(/node 'bar' has no 'default' public key; skipping/)
279
+ item.clients("*:*")
280
+ end
281
+ end
282
+
283
+ context "when a Chef::ApiClient is passed" do
284
+ let(:client) { Chef::ApiClient.new }
285
+ let(:client_name) { "foo" }
286
+ let(:client_key) { double("chef key") }
287
+
288
+ before do
289
+ client.name client_name
290
+ privkey = OpenSSL::PKey::RSA.new(1024)
291
+ pubkey = privkey.public_key
292
+ allow(item).to receive(:load_actor).with(client_name, "clients").and_return(client_key)
293
+ allow(client_key).to receive(:key).and_return(pubkey.to_pem)
294
+ allow(client_key).to receive(:name).and_return(client_name)
295
+ allow(client_key).to receive(:type).and_return("clients")
296
+ end
297
+
298
+ context "when no action is passed" do
299
+ it "default to add and properly add the client" do
300
+ item.clients(client)
301
+ expect(item.get_clients).to include(client_name)
302
+ end
303
+
304
+ it "does not perform a query" do
305
+ expect(Chef::Search::Query).not_to receive(:new)
306
+ item.clients(client)
307
+ end
308
+ end
309
+
310
+ context "when the delete action is passed on an existing client" do
311
+ before do
312
+ # add the client
313
+ item.clients(client)
314
+ end
315
+
316
+ it "properly deletes the client" do
317
+ item.clients(client, :delete)
318
+ expect(item.get_clients).to_not include(client_name)
319
+ end
320
+
321
+ it "does not perform a query" do
322
+ expect(Chef::Search::Query).not_to receive(:new)
323
+ item.clients(client, :delete)
324
+ end
325
+ end
326
+ end
327
+
328
+ context "when an Array with named clients is passed" do
329
+ let(:client) { Chef::ApiClient.new }
330
+ let(:clients) { [] }
331
+ let(:client_name) { "foo" }
332
+ let(:client_key) { double("chef key") }
333
+
334
+ before do
335
+ clients << client_name
336
+ client.name client_name
337
+ privkey = OpenSSL::PKey::RSA.new(1024)
338
+ pubkey = privkey.public_key
339
+ allow(item).to receive(:load_actor).with(client_name, "clients").and_return(client_key)
340
+ allow(client_key).to receive(:key).and_return(pubkey.to_pem)
341
+ allow(client_key).to receive(:name).and_return(client_name)
342
+ allow(client_key).to receive(:type).and_return("clients")
343
+ end
344
+
345
+ context "when no action is passed" do
346
+ it "defaults to add and properly adds the client" do
347
+ item.clients(clients)
348
+ expect(item.get_clients).to include(client_name)
349
+ end
350
+
351
+ it "does not perform a query" do
352
+ expect(Chef::Search::Query).not_to receive(:new)
353
+ item.clients(clients)
354
+ end
355
+ end
356
+
357
+ context "when the delete action is passed on an existing client" do
358
+ before do
359
+ # add the client
360
+ item.clients(clients)
361
+ end
362
+
363
+ it "properly deletes the client" do
364
+ item.clients(clients, :delete)
365
+ expect(item.get_clients).to_not include(client_name)
366
+ end
367
+
368
+ it "does not perform a query" do
369
+ expect(Chef::Search::Query).not_to receive(:new)
370
+ item.clients(clients, :delete)
371
+ end
372
+ end
373
+ end
374
+ end
375
+
376
+ describe "#admins" do
377
+ before do
378
+ allow(item).to receive(:load_actor).with("foo", "admins").and_raise(ChefVault::Exceptions::AdminNotFound)
379
+ end
380
+
381
+ it "should blow up if you try to use a node without a public key as an admin" do
382
+ expect { item.admins("foo,bar") }
383
+ .to raise_error(ChefVault::Exceptions::AdminNotFound)
384
+ end
385
+ end
386
+
387
+ describe "#raw_keys" do
388
+ it "should return the keys of the underlying data bag item" do
389
+ item = ChefVault::Item.new("foo", "bar")
390
+ item["foo"] = "bar"
391
+ expect(item.raw_keys).to eq(%w{id foo})
392
+ end
393
+ end
394
+ end