chef-vault 2.4.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +97 -0
- data/Changelog.md +34 -0
- data/DEMO.md +13 -6
- data/README.md +9 -4
- data/Rakefile +47 -10
- data/THEORY.md +361 -0
- data/bin/chef-vault +5 -5
- data/chef-vault.gemspec +11 -2
- data/features/clean_unknown_clients.feature +28 -3
- data/features/step_definitions/chef-databag.rb +5 -0
- data/features/step_definitions/chef-repo.rb +46 -8
- data/features/step_definitions/chef-vault.rb +69 -15
- data/features/support/env.rb +2 -2
- data/features/vault_create.feature +54 -0
- data/features/vault_list.feature +26 -0
- data/features/vault_show.feature +46 -0
- data/features/vault_update.feature +17 -0
- data/features/wrong_private_key.feature +14 -0
- data/lib/chef-vault.rb +0 -1
- data/lib/chef-vault/certificate.rb +1 -1
- data/lib/chef-vault/chef_patch/api_client.rb +1 -1
- data/lib/chef-vault/chef_patch/user.rb +1 -1
- data/lib/chef-vault/exceptions.rb +33 -12
- data/lib/chef-vault/item.rb +262 -209
- data/lib/chef-vault/item_keys.rb +90 -88
- data/lib/chef-vault/user.rb +1 -1
- data/lib/chef-vault/version.rb +2 -2
- data/lib/chef/knife/decrypt.rb +1 -2
- data/lib/chef/knife/encrypt_create.rb +1 -2
- data/lib/chef/knife/encrypt_delete.rb +1 -2
- data/lib/chef/knife/encrypt_remove.rb +1 -2
- data/lib/chef/knife/encrypt_rotate_keys.rb +1 -2
- data/lib/chef/knife/encrypt_update.rb +1 -2
- data/lib/chef/knife/mixin/compat.rb +3 -3
- data/lib/chef/knife/mixin/helper.rb +6 -8
- data/lib/chef/knife/vault_admins.rb +1 -2
- data/lib/chef/knife/vault_base.rb +2 -2
- data/lib/chef/knife/vault_create.rb +3 -4
- data/lib/chef/knife/vault_decrypt.rb +3 -4
- data/lib/chef/knife/vault_delete.rb +2 -4
- data/lib/chef/knife/vault_download.rb +1 -2
- data/lib/chef/knife/vault_edit.rb +3 -6
- data/lib/chef/knife/vault_list.rb +53 -0
- data/lib/chef/knife/vault_refresh.rb +1 -2
- data/lib/chef/knife/vault_remove.rb +3 -7
- data/lib/chef/knife/vault_rotate_all_keys.rb +2 -4
- data/lib/chef/knife/vault_rotate_keys.rb +2 -4
- data/lib/chef/knife/vault_show.rb +4 -5
- data/lib/chef/knife/vault_update.rb +7 -9
- data/spec/chef-vault/certificate_spec.rb +0 -2
- data/spec/chef-vault/item_spec.rb +77 -1
- data/spec/chef-vault/user_spec.rb +0 -2
- data/spec/chef-vault_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -3
- metadata +38 -14
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultDelete class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultDelete < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault delete VAULT ITEM (options)"
|
@@ -34,8 +33,7 @@ class Chef
|
|
34
33
|
begin
|
35
34
|
ChefVault::Item.load(vault, item).destroy
|
36
35
|
rescue ChefVault::Exceptions::KeysNotFound,
|
37
|
-
|
38
|
-
|
36
|
+
ChefVault::Exceptions::ItemNotFound
|
39
37
|
raise ChefVault::Exceptions::ItemNotFound,
|
40
38
|
"#{vault}/#{item} not found."
|
41
39
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultDownload class
|
2
|
-
# Copyright 2014, Nordstrom, Inc.
|
2
|
+
# Copyright 2014-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultDownload < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault download VAULT ITEM PATH (options)"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultEdit class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultEdit < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault edit VAULT ITEM (options)"
|
@@ -43,7 +42,7 @@ class Chef
|
|
43
42
|
updated_vault_json = edit_data(filtered_vault_data)
|
44
43
|
|
45
44
|
# Clean out contents of existing local vault_item
|
46
|
-
vault_item.raw_data.each do |key,
|
45
|
+
vault_item.raw_data.each do |key, _|
|
47
46
|
vault_item.remove(key) unless key == 'id'
|
48
47
|
end
|
49
48
|
|
@@ -54,8 +53,7 @@ class Chef
|
|
54
53
|
|
55
54
|
vault_item.save
|
56
55
|
rescue ChefVault::Exceptions::KeysNotFound,
|
57
|
-
|
58
|
-
|
56
|
+
ChefVault::Exceptions::ItemNotFound
|
59
57
|
raise ChefVault::Exceptions::ItemNotFound,
|
60
58
|
"#{vault}/#{item} does not exist, "\
|
61
59
|
"use 'knife vault create' to create."
|
@@ -67,4 +65,3 @@ class Chef
|
|
67
65
|
end
|
68
66
|
end
|
69
67
|
end
|
70
|
-
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Description: Chef-Vault VaultShow 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 VaultList < Knife
|
21
|
+
include Chef::Knife::VaultBase
|
22
|
+
|
23
|
+
banner "knife vault list (options)"
|
24
|
+
|
25
|
+
def run
|
26
|
+
vaultbags = []
|
27
|
+
# iterate over all the data bags
|
28
|
+
bags = Chef::DataBag.list
|
29
|
+
bags.each_key do |bagname|
|
30
|
+
vaultbags.push(bagname) if bag_is_vault?(bagname)
|
31
|
+
end
|
32
|
+
output vaultbags.join("\n")
|
33
|
+
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
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultReapply class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultRefresh < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault refresh VAULT ITEM"
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultRemove class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultRemove < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault remove VAULT ITEM VALUES (options)"
|
@@ -56,13 +55,11 @@ class Chef
|
|
56
55
|
if values || json_file
|
57
56
|
begin
|
58
57
|
json = JSON.parse(values)
|
59
|
-
json.each do |key,
|
58
|
+
json.each do |key, _|
|
60
59
|
remove_items << key
|
61
60
|
end
|
62
61
|
rescue JSON::ParserError
|
63
62
|
remove_items = values.split(",")
|
64
|
-
rescue Exception => e
|
65
|
-
raise e
|
66
63
|
end
|
67
64
|
|
68
65
|
remove_items.each do |key|
|
@@ -76,8 +73,7 @@ class Chef
|
|
76
73
|
|
77
74
|
vault_item.rotate_keys!(clean_unknown_clients)
|
78
75
|
rescue ChefVault::Exceptions::KeysNotFound,
|
79
|
-
|
80
|
-
|
76
|
+
ChefVault::Exceptions::ItemNotFound
|
81
77
|
raise ChefVault::Exceptions::ItemNotFound,
|
82
78
|
"#{vault}/#{item} does not exist, "\
|
83
79
|
"use 'knife vault create' to create."
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultRotateAllKeys class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultRotateAllKeys < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault rotate all keys"
|
@@ -47,9 +46,8 @@ class Chef
|
|
47
46
|
end
|
48
47
|
|
49
48
|
def vault_items(vault)
|
50
|
-
Chef::DataBag.load(vault).keys.
|
49
|
+
Chef::DataBag.load(vault).keys.each_with_object([]) do |key, array|
|
51
50
|
array << key.sub('_keys', '') if key.match(/.+_keys$/)
|
52
|
-
array
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultRotateKeys class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultRotateKeys < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault rotate keys VAULT ITEM (options)"
|
@@ -39,8 +38,7 @@ class Chef
|
|
39
38
|
item = ChefVault::Item.load(vault, item)
|
40
39
|
item.rotate_keys!(clean_unknown_clients)
|
41
40
|
rescue ChefVault::Exceptions::KeysNotFound,
|
42
|
-
|
43
|
-
|
41
|
+
ChefVault::Exceptions::ItemNotFound
|
44
42
|
raise ChefVault::Exceptions::ItemNotFound,
|
45
43
|
"#{vault}/#{item} does not exist, "\
|
46
44
|
"use 'knife vault create' to create."
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultShow class
|
2
|
-
# Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -18,7 +18,6 @@ require 'chef/knife/vault_base'
|
|
18
18
|
class Chef
|
19
19
|
class Knife
|
20
20
|
class VaultShow < Knife
|
21
|
-
|
22
21
|
include Chef::Knife::VaultBase
|
23
22
|
|
24
23
|
banner "knife vault show VAULT ITEM [VALUES] (options)"
|
@@ -67,14 +66,14 @@ class Chef
|
|
67
66
|
end
|
68
67
|
|
69
68
|
if values
|
70
|
-
included_values = %
|
69
|
+
included_values = %w(id)
|
71
70
|
|
72
71
|
values.split(",").each do |value|
|
73
72
|
value.strip! # remove white space
|
74
73
|
included_values << value
|
75
74
|
end
|
76
75
|
|
77
|
-
filtered_data = Hash[vault_item.raw_data.find_all{|k,
|
76
|
+
filtered_data = Hash[vault_item.raw_data.find_all{|k, _| included_values.include?(k)}]
|
78
77
|
|
79
78
|
output_data = filtered_data.merge(extra_data)
|
80
79
|
else
|
@@ -82,7 +81,7 @@ class Chef
|
|
82
81
|
|
83
82
|
output_data = all_data.merge(extra_data)
|
84
83
|
end
|
85
|
-
|
84
|
+
output(output_data)
|
86
85
|
end
|
87
86
|
end
|
88
87
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# Description: Chef-Vault VaultUpdate class
|
2
|
-
# Copyright 2014, Nordstrom, Inc.
|
2
|
+
# Copyright 2014-15, Nordstrom, Inc.
|
3
3
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -19,7 +19,6 @@ require 'chef/knife/vault_admins'
|
|
19
19
|
class Chef
|
20
20
|
class Knife
|
21
21
|
class VaultUpdate < Knife
|
22
|
-
|
23
22
|
include Chef::Knife::VaultBase
|
24
23
|
include Chef::Knife::VaultAdmins
|
25
24
|
|
@@ -73,11 +72,11 @@ class Chef
|
|
73
72
|
end
|
74
73
|
|
75
74
|
if clean
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
75
|
+
clients = vault_item.clients().clone().sort()
|
76
|
+
clients.each do |client|
|
77
|
+
puts "Deleting #{client}"
|
78
|
+
vault_item.keys.delete(client, "clients")
|
79
|
+
end
|
81
80
|
end
|
82
81
|
vault_item.search(search) if search
|
83
82
|
vault_item.clients(search) if search
|
@@ -85,8 +84,7 @@ class Chef
|
|
85
84
|
|
86
85
|
vault_item.save
|
87
86
|
rescue ChefVault::Exceptions::KeysNotFound,
|
88
|
-
|
89
|
-
|
87
|
+
ChefVault::Exceptions::ItemNotFound
|
90
88
|
raise ChefVault::Exceptions::ItemNotFound,
|
91
89
|
"#{vault}/#{item} does not exist, "\
|
92
90
|
"use 'knife vault create' to create."
|
@@ -21,7 +21,6 @@ RSpec.describe ChefVault::Certificate do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
describe 'decrypt_contents' do
|
24
|
-
|
25
24
|
it 'echoes warning' do
|
26
25
|
expect(STDOUT).to receive(:puts).with("WARNING: This method is deprecated, please switch to item['value'] calls")
|
27
26
|
cert.decrypt_contents
|
@@ -33,5 +32,4 @@ RSpec.describe ChefVault::Certificate do
|
|
33
32
|
expect(cert.decrypt_contents).to eq 'baz'
|
34
33
|
end
|
35
34
|
end
|
36
|
-
|
37
35
|
end
|
@@ -1,8 +1,59 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'rspec/core/shared_context'
|
3
|
+
|
4
|
+
# it turns out that simulating a node that doesn't have a public
|
5
|
+
# key is not a simple thing
|
6
|
+
module BorkedNodeWithoutPublicKey
|
7
|
+
extend RSpec::Core::SharedContext
|
8
|
+
|
9
|
+
before do
|
10
|
+
# a node 'foo' with a public key
|
11
|
+
node_foo = double('chef node foo')
|
12
|
+
allow(node_foo).to receive(:name).and_return('foo')
|
13
|
+
client_foo = double('chef apiclient foo')
|
14
|
+
allow(client_foo).to receive(:name).and_return('foo')
|
15
|
+
privkey = OpenSSL::PKey::RSA.new(1024)
|
16
|
+
pubkey = privkey.public_key
|
17
|
+
allow(client_foo).to receive(:public_key).and_return(pubkey.to_pem)
|
18
|
+
# a node 'bar' without a public key
|
19
|
+
node_bar = double('chef node bar')
|
20
|
+
allow(node_bar).to receive(:name).and_return('bar')
|
21
|
+
# fake out searches to return both of our nodes
|
22
|
+
query_result = double('chef search results')
|
23
|
+
allow(query_result)
|
24
|
+
.to receive(:search)
|
25
|
+
.with(Symbol, String)
|
26
|
+
.and_return([[node_foo, node_bar]])
|
27
|
+
allow(Chef::Search::Query)
|
28
|
+
.to receive(:new)
|
29
|
+
.and_return(query_result)
|
30
|
+
# create a new vault item
|
31
|
+
@vaultitem = ChefVault::Item.new('foo', 'bar')
|
32
|
+
@vaultitem['foo'] = 'bar'
|
33
|
+
# make the vault item return the apiclient for foo but raise for bar
|
34
|
+
allow(@vaultitem).to receive(:load_client).with('foo')
|
35
|
+
.and_return(client_foo)
|
36
|
+
allow(@vaultitem).to receive(:load_client).with('bar')
|
37
|
+
.and_raise(ChefVault::Exceptions::ClientNotFound)
|
38
|
+
# make the vault item fall back to 'load-admin-as-client' behaviour
|
39
|
+
http_response = double('http not found')
|
40
|
+
allow(http_response).to receive(:code).and_return('404')
|
41
|
+
http_not_found = Net::HTTPServerException.new('not found', http_response)
|
42
|
+
allow(ChefVault::ChefPatch::User)
|
43
|
+
.to receive(:load)
|
44
|
+
.with('foo')
|
45
|
+
.and_return(client_foo)
|
46
|
+
allow(ChefVault::ChefPatch::User)
|
47
|
+
.to receive(:load)
|
48
|
+
.with('bar')
|
49
|
+
.and_raise(http_not_found)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
1
53
|
RSpec.describe ChefVault::Item do
|
2
54
|
subject(:item) { ChefVault::Item.new("foo", "bar") }
|
3
55
|
|
4
56
|
describe '#new' do
|
5
|
-
|
6
57
|
it { should be_an_instance_of ChefVault::Item }
|
7
58
|
|
8
59
|
its(:keys) { should be_an_instance_of ChefVault::ItemKeys }
|
@@ -23,4 +74,29 @@ RSpec.describe ChefVault::Item do
|
|
23
74
|
specify { expect { item.save }.to raise_error }
|
24
75
|
end
|
25
76
|
end
|
77
|
+
|
78
|
+
describe '#clients' do
|
79
|
+
include BorkedNodeWithoutPublicKey
|
80
|
+
|
81
|
+
it 'should not blow up when search returns a node without a public key' do
|
82
|
+
# try to set clients when we know a node is missing a public key
|
83
|
+
# this should not die as of v2.4.1
|
84
|
+
expect { @vaultitem.clients('*:*') }.not_to raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should emit a warning if search returns a node without a public key' do
|
88
|
+
# it should however emit a warning that you have a borked node
|
89
|
+
expect { @vaultitem.clients('*:*') }
|
90
|
+
.to output(/node 'bar' has no private key; skipping/).to_stderr
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#admins' do
|
95
|
+
include BorkedNodeWithoutPublicKey
|
96
|
+
|
97
|
+
it 'should blow up if you try to use a node without a public key as an admin' do
|
98
|
+
expect { @vaultitem.admins('foo,bar') }
|
99
|
+
.to raise_error(ChefVault::Exceptions::AdminNotFound)
|
100
|
+
end
|
101
|
+
end
|
26
102
|
end
|
@@ -21,7 +21,6 @@ RSpec.describe ChefVault::User do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
describe 'decrypt_password' do
|
24
|
-
|
25
24
|
it 'echoes warning' do
|
26
25
|
expect(STDOUT).to receive(:puts).with("WARNING: This method is deprecated, please switch to item['value'] calls")
|
27
26
|
user.decrypt_password
|
@@ -32,5 +31,4 @@ RSpec.describe ChefVault::User do
|
|
32
31
|
expect(user.decrypt_password).to eq "baz"
|
33
32
|
end
|
34
33
|
end
|
35
|
-
|
36
34
|
end
|
data/spec/chef-vault_spec.rb
CHANGED