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.
- checksums.yaml +4 -4
- data/Gemfile +7 -3
- data/chef-vault.gemspec +5 -2
- data/lib/chef-vault/actor.rb +1 -1
- data/lib/chef-vault/chef_api.rb +1 -1
- data/lib/chef-vault/item.rb +1 -1
- data/lib/chef-vault/version.rb +2 -2
- data/spec/chef/helper_spec.rb +37 -0
- data/spec/chef-vault/actor_spec.rb +247 -0
- data/spec/chef-vault/certificate_spec.rb +37 -0
- data/spec/chef-vault/chef_api_spec.rb +39 -0
- data/spec/chef-vault/item_keys_spec.rb +293 -0
- data/spec/chef-vault/item_spec.rb +394 -0
- data/spec/chef-vault/user_spec.rb +36 -0
- data/spec/chef-vault_spec.rb +65 -0
- data/spec/spec_helper.rb +92 -0
- metadata +11 -2
|
@@ -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
|