chef-vault 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Wrong private key during decrypt
|
2
|
+
|
3
|
+
https://github.com/Nordstrom/chef-vault/issues/43
|
4
|
+
If a vault is encrypted for a node and then the node's private
|
5
|
+
key is regenerated, the error that comes back from chef-vault
|
6
|
+
should be informative, not a lower-level error from OpenSSL
|
7
|
+
like 'OpenSSL::PKey::RSAError: padding check failed'
|
8
|
+
|
9
|
+
Scenario: Regenerate node key and attempt decrypt
|
10
|
+
Given a local mode chef repo with nodes 'one,two'
|
11
|
+
And I create a vault item 'test/item' containing the JSON '{"foo": "bar"}' encrypted for 'one,two'
|
12
|
+
And I regenerate the client key for the node 'one'
|
13
|
+
And I try to decrypt the vault item 'test/item' as 'one'
|
14
|
+
Then the output should match /is encrypted for you, but your private key failed to decrypt the contents/
|
data/lib/chef-vault.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Author:: Kevin Moser <kevin.moser@nordstrom.com>
|
2
|
-
# Copyright:: Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright:: Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
# License:: Apache License, Version 2.0
|
4
4
|
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -14,15 +14,36 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
-
class ChefVault
|
18
|
-
class
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
class ChefVault
|
18
|
+
class Exceptions < RuntimeError
|
19
|
+
class SecretDecryption < Exceptions
|
20
|
+
end
|
21
|
+
|
22
|
+
class NoKeysDefined < Exceptions
|
23
|
+
end
|
24
|
+
|
25
|
+
class ItemNotEncrypted < Exceptions
|
26
|
+
end
|
27
|
+
|
28
|
+
class KeysActionNotValue < Exceptions
|
29
|
+
end
|
30
|
+
|
31
|
+
class AdminNotFound < Exceptions
|
32
|
+
end
|
33
|
+
|
34
|
+
class ClientNotFound < Exceptions
|
35
|
+
end
|
36
|
+
|
37
|
+
class KeysNotFound < Exceptions
|
38
|
+
end
|
39
|
+
|
40
|
+
class ItemNotFound < Exceptions
|
41
|
+
end
|
42
|
+
|
43
|
+
class ItemAlreadyExists < Exceptions
|
44
|
+
end
|
45
|
+
|
46
|
+
class SearchNotFound < Exceptions
|
47
|
+
end
|
48
|
+
end
|
28
49
|
end
|
data/lib/chef-vault/item.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Author:: Kevin Moser <kevin.moser@nordstrom.com>
|
2
|
-
# Copyright:: Copyright 2013, Nordstrom, Inc.
|
2
|
+
# Copyright:: Copyright 2013-15, Nordstrom, Inc.
|
3
3
|
# License:: Apache License, Version 2.0
|
4
4
|
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -16,276 +16,329 @@
|
|
16
16
|
|
17
17
|
require 'securerandom'
|
18
18
|
|
19
|
-
class ChefVault
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
19
|
+
class ChefVault
|
20
|
+
class Item < Chef::DataBagItem
|
21
|
+
attr_accessor :keys
|
22
|
+
attr_accessor :encrypted_data_bag_item
|
23
|
+
|
24
|
+
def initialize(vault, name)
|
25
|
+
super() # Don't pass parameters
|
26
|
+
@data_bag = vault
|
27
|
+
@raw_data["id"] = name
|
28
|
+
@keys = ChefVault::ItemKeys.new(vault, "#{name}_keys")
|
29
|
+
@secret = generate_secret
|
30
|
+
@encrypted = false
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def load_keys(vault, keys)
|
34
|
+
@keys = ChefVault::ItemKeys.load(vault, keys)
|
35
|
+
@secret = secret
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def clients(search=nil, action=:add)
|
39
|
+
if search
|
40
|
+
results_returned = false
|
41
|
+
|
42
|
+
query = Chef::Search::Query.new
|
43
|
+
query.search(:node, search)[0].each do |node|
|
44
|
+
results_returned = true
|
45
|
+
|
46
|
+
case action
|
47
|
+
when :add
|
48
|
+
begin
|
49
|
+
keys.add(load_client(node.name), @secret, "clients")
|
50
|
+
rescue ChefVault::Exceptions::ClientNotFound
|
51
|
+
$stderr.puts "node '#{node.name}' has no private key; skipping"
|
52
|
+
end
|
53
|
+
when :delete
|
54
|
+
keys.delete(node.name, "clients")
|
55
|
+
else
|
56
|
+
raise ChefVault::Exceptions::KeysActionNotValid,
|
57
|
+
"#{action} is not a valid action"
|
58
|
+
end
|
59
|
+
end
|
40
60
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
case action
|
46
|
-
when :add
|
47
|
-
keys.add(load_client(node.name), @secret, "clients")
|
48
|
-
when :delete
|
49
|
-
keys.delete(node.name, "clients")
|
50
|
-
else
|
51
|
-
raise ChefVault::Exceptions::KeysActionNotValid,
|
52
|
-
"#{action} is not a valid action"
|
61
|
+
unless results_returned
|
62
|
+
puts "WARNING: No clients were returned from search, you may not have "\
|
63
|
+
"got what you expected!!"
|
53
64
|
end
|
65
|
+
else
|
66
|
+
keys.clients
|
54
67
|
end
|
68
|
+
end
|
55
69
|
|
56
|
-
|
57
|
-
|
58
|
-
|
70
|
+
def search(search_query=nil)
|
71
|
+
if search_query
|
72
|
+
keys.search_query(search_query)
|
73
|
+
else
|
74
|
+
keys.search_query
|
59
75
|
end
|
60
|
-
else
|
61
|
-
keys.clients
|
62
76
|
end
|
63
|
-
end
|
64
77
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
when :add
|
79
|
-
keys.add(load_admin(admin), @secret, "admins")
|
80
|
-
when :delete
|
81
|
-
keys.delete(admin, "admins")
|
82
|
-
else
|
83
|
-
raise ChefVault::Exceptions::KeysActionNotValid,
|
84
|
-
"#{action} is not a valid action"
|
78
|
+
def admins(admins=nil, action=:add)
|
79
|
+
if admins
|
80
|
+
admins.split(",").each do |admin|
|
81
|
+
admin.strip!
|
82
|
+
case action
|
83
|
+
when :add
|
84
|
+
keys.add(load_admin(admin), @secret, "admins")
|
85
|
+
when :delete
|
86
|
+
keys.delete(admin, "admins")
|
87
|
+
else
|
88
|
+
raise ChefVault::Exceptions::KeysActionNotValid,
|
89
|
+
"#{action} is not a valid action"
|
90
|
+
end
|
85
91
|
end
|
92
|
+
else
|
93
|
+
keys.admins
|
86
94
|
end
|
87
|
-
else
|
88
|
-
keys.admins
|
89
95
|
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def remove(key)
|
93
|
-
@raw_data.delete(key)
|
94
|
-
end
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
private_key = OpenSSL::PKey::RSA.new(open(Chef::Config[:client_key]).read())
|
99
|
-
private_key.private_decrypt(Base64.decode64(@keys[Chef::Config[:node_name]]))
|
100
|
-
else
|
101
|
-
raise ChefVault::Exceptions::SecretDecryption,
|
102
|
-
"#{data_bag}/#{id} is not encrypted with your public key. "\
|
103
|
-
"Contact an administrator of the vault item to encrypt for you!"
|
97
|
+
def remove(key)
|
98
|
+
@raw_data.delete(key)
|
104
99
|
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def rotate_keys!(clean_unknown_clients=false)
|
108
|
-
@secret = generate_secret
|
109
100
|
|
110
|
-
|
111
|
-
if
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
120
|
-
clients_to_remove.each do |client|
|
121
|
-
puts "Removing unknown client '#{client}'"
|
122
|
-
clients("name:#{client}", :delete)
|
101
|
+
def secret
|
102
|
+
if @keys.include?(Chef::Config[:node_name])
|
103
|
+
private_key = OpenSSL::PKey::RSA.new(open(Chef::Config[:client_key]).read())
|
104
|
+
begin
|
105
|
+
private_key.private_decrypt(Base64.decode64(@keys[Chef::Config[:node_name]]))
|
106
|
+
rescue OpenSSL::PKey::RSAError
|
107
|
+
raise ChefVault::Exceptions::SecretDecryption,
|
108
|
+
"#{data_bag}/#{id} is encrypted for you, but your private key failed to decrypt the contents. "\
|
109
|
+
"(if you regenerated your client key, have an administrator of the vault run 'knife vault refresh')"
|
123
110
|
end
|
124
111
|
else
|
112
|
+
raise ChefVault::Exceptions::SecretDecryption,
|
113
|
+
"#{data_bag}/#{id} is not encrypted with your public key. "\
|
114
|
+
"Contact an administrator of the vault item to encrypt for you!"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def rotate_keys!(clean_unknown_clients = false)
|
119
|
+
@secret = generate_secret
|
120
|
+
|
121
|
+
unless clients.empty?
|
122
|
+
# a bit of a misnomer; this doesn't remove unknown
|
123
|
+
# admins, just clients which are nodes
|
124
|
+
remove_unknown_nodes if clean_unknown_clients
|
125
|
+
# re-encrypt the new shared secret for all remaining clients
|
125
126
|
clients.each do |client|
|
126
127
|
clients("name:#{client}")
|
127
128
|
end
|
128
129
|
end
|
129
|
-
end
|
130
130
|
|
131
|
-
|
132
|
-
|
133
|
-
admins
|
131
|
+
unless admins.empty?
|
132
|
+
# re-encrypt the new shared secret for all admins
|
133
|
+
admins.each do |admin|
|
134
|
+
admins(admin)
|
135
|
+
end
|
134
136
|
end
|
137
|
+
|
138
|
+
save
|
139
|
+
reload_raw_data
|
135
140
|
end
|
136
141
|
|
137
|
-
|
138
|
-
|
139
|
-
|
142
|
+
def generate_secret(key_size=32)
|
143
|
+
# Defaults to 32 bytes, as this is the size that a Chef
|
144
|
+
# Encrypted Data Bag Item will digest all secrets down to anyway
|
145
|
+
SecureRandom.random_bytes(key_size)
|
146
|
+
end
|
140
147
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
end
|
148
|
+
def []=(key, value)
|
149
|
+
reload_raw_data if @encrypted
|
150
|
+
super
|
151
|
+
end
|
146
152
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
153
|
+
def [](key)
|
154
|
+
reload_raw_data if @encrypted
|
155
|
+
super
|
156
|
+
end
|
151
157
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
158
|
+
def save(item_id=@raw_data['id'])
|
159
|
+
# validate the format of the id before attempting to save
|
160
|
+
validate_id!(item_id)
|
161
|
+
|
162
|
+
# save the keys first, raising an error if no keys were defined
|
163
|
+
if keys.admins.empty? && keys.clients.empty?
|
164
|
+
raise ChefVault::Exceptions::NoKeysDefined,
|
165
|
+
"No keys defined for #{item_id}"
|
166
|
+
end
|
167
|
+
|
168
|
+
keys.save
|
156
169
|
|
157
|
-
|
170
|
+
# Make sure the item is encrypted before saving
|
171
|
+
encrypt! unless @encrypted
|
172
|
+
|
173
|
+
# Now save the encrypted data
|
174
|
+
if Chef::Config[:solo]
|
175
|
+
data_bag_path = File.join(Chef::Config[:data_bag_path],
|
176
|
+
data_bag)
|
177
|
+
data_bag_item_path = File.join(data_bag_path, item_id)
|
178
|
+
|
179
|
+
FileUtils.mkdir(data_bag_path) unless File.exist?(data_bag_path)
|
180
|
+
File.open("#{data_bag_item_path}.json", 'w') do |file|
|
181
|
+
file.write(JSON.pretty_generate(raw_data))
|
182
|
+
end
|
183
|
+
|
184
|
+
raw_data
|
185
|
+
else
|
186
|
+
begin
|
187
|
+
Chef::DataBag.load(data_bag)
|
188
|
+
rescue Net::HTTPServerException => http_error
|
189
|
+
if http_error.response.code == "404"
|
190
|
+
chef_data_bag = Chef::DataBag.new
|
191
|
+
chef_data_bag.name data_bag
|
192
|
+
chef_data_bag.create
|
193
|
+
end
|
194
|
+
end
|
158
195
|
|
159
|
-
|
160
|
-
|
196
|
+
super
|
197
|
+
end
|
198
|
+
end
|
161
199
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
"No keys defined for #{item_id}"
|
200
|
+
def to_json(*a)
|
201
|
+
json = super
|
202
|
+
json.gsub(self.class.name, self.class.superclass.name)
|
166
203
|
end
|
167
204
|
|
168
|
-
|
205
|
+
def destroy
|
206
|
+
keys.destroy
|
169
207
|
|
170
|
-
|
171
|
-
|
208
|
+
if Chef::Config[:solo]
|
209
|
+
data_bag_path = File.join(Chef::Config[:data_bag_path],
|
210
|
+
data_bag)
|
211
|
+
data_bag_item_path = File.join(data_bag_path, @raw_data["id"])
|
172
212
|
|
173
|
-
|
174
|
-
if Chef::Config[:solo]
|
175
|
-
data_bag_path = File.join(Chef::Config[:data_bag_path],
|
176
|
-
data_bag)
|
177
|
-
data_bag_item_path = File.join(data_bag_path, item_id)
|
213
|
+
FileUtils.rm("#{data_bag_item_path}.json")
|
178
214
|
|
179
|
-
|
180
|
-
|
181
|
-
|
215
|
+
nil
|
216
|
+
else
|
217
|
+
super(data_bag, id)
|
182
218
|
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.load(vault, name)
|
222
|
+
item = new(vault, name)
|
223
|
+
item.load_keys(vault, "#{name}_keys")
|
183
224
|
|
184
|
-
self.raw_data
|
185
|
-
else
|
186
225
|
begin
|
187
|
-
|
226
|
+
item.raw_data =
|
227
|
+
Chef::EncryptedDataBagItem.load(vault, name, item.secret).to_hash
|
188
228
|
rescue Net::HTTPServerException => http_error
|
189
229
|
if http_error.response.code == "404"
|
190
|
-
|
191
|
-
|
192
|
-
|
230
|
+
raise ChefVault::Exceptions::ItemNotFound,
|
231
|
+
"#{vault}/#{name} could not be found"
|
232
|
+
else
|
233
|
+
raise http_error
|
193
234
|
end
|
235
|
+
rescue Chef::Exceptions::ValidationFailed
|
236
|
+
raise ChefVault::Exceptions::ItemNotFound,
|
237
|
+
"#{vault}/#{name} could not be found"
|
194
238
|
end
|
195
239
|
|
196
|
-
|
240
|
+
item
|
197
241
|
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def to_json(*a)
|
201
|
-
json = super
|
202
|
-
json.gsub(self.class.name, self.class.superclass.name)
|
203
|
-
end
|
204
242
|
|
205
|
-
|
206
|
-
keys.destroy
|
243
|
+
private
|
207
244
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
data_bag_item_path = File.join(data_bag_path, @raw_data["id"])
|
212
|
-
|
213
|
-
FileUtils.rm("#{data_bag_item_path}.json")
|
214
|
-
|
215
|
-
nil
|
216
|
-
else
|
217
|
-
super(data_bag, id)
|
245
|
+
def encrypt!
|
246
|
+
@raw_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(self, @secret)
|
247
|
+
@encrypted = true
|
218
248
|
end
|
219
|
-
end
|
220
249
|
|
221
|
-
|
222
|
-
|
223
|
-
|
250
|
+
def reload_raw_data
|
251
|
+
@raw_data =
|
252
|
+
Chef::EncryptedDataBagItem.load(@data_bag, @raw_data["id"], secret).to_hash
|
253
|
+
@encrypted = false
|
224
254
|
|
225
|
-
|
226
|
-
item.raw_data =
|
227
|
-
Chef::EncryptedDataBagItem.load(vault, name, item.secret).to_hash
|
228
|
-
rescue Net::HTTPServerException => http_error
|
229
|
-
if http_error.response.code == "404"
|
230
|
-
raise ChefVault::Exceptions::ItemNotFound,
|
231
|
-
"#{vault}/#{name} could not be found"
|
232
|
-
else
|
233
|
-
raise http_error
|
234
|
-
end
|
235
|
-
rescue Chef::Exceptions::ValidationFailed
|
236
|
-
raise ChefVault::Exceptions::ItemNotFound,
|
237
|
-
"#{vault}/#{name} could not be found"
|
255
|
+
@raw_data
|
238
256
|
end
|
239
257
|
|
240
|
-
|
241
|
-
|
258
|
+
def load_admin(admin)
|
259
|
+
begin
|
260
|
+
admin = ChefVault::ChefPatch::User.load(admin)
|
261
|
+
rescue Net::HTTPServerException => http_error
|
262
|
+
if http_error.response.code == "404"
|
263
|
+
begin
|
264
|
+
puts "WARNING: #{admin} not found in users, trying clients."
|
265
|
+
admin = load_client(admin)
|
266
|
+
rescue ChefVault::Exceptions::ClientNotFound
|
267
|
+
raise ChefVault::Exceptions::AdminNotFound,
|
268
|
+
"FATAL: Could not find #{admin} in users or clients!"
|
269
|
+
end
|
270
|
+
else
|
271
|
+
raise http_error
|
272
|
+
end
|
273
|
+
end
|
242
274
|
|
243
|
-
|
244
|
-
|
245
|
-
@raw_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(self, @secret)
|
246
|
-
@encrypted = true
|
247
|
-
end
|
275
|
+
admin
|
276
|
+
end
|
248
277
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
278
|
+
def load_client(client)
|
279
|
+
begin
|
280
|
+
client = ChefVault::ChefPatch::ApiClient.load(client)
|
281
|
+
rescue Net::HTTPServerException => http_error
|
282
|
+
if http_error.response.code == "404"
|
283
|
+
raise ChefVault::Exceptions::ClientNotFound,
|
284
|
+
"#{client} is not a valid chef client and/or node"
|
285
|
+
else
|
286
|
+
raise http_error
|
287
|
+
end
|
288
|
+
end
|
253
289
|
|
254
|
-
|
255
|
-
|
290
|
+
client
|
291
|
+
end
|
256
292
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
293
|
+
# removes unknown nodes by performing a node search
|
294
|
+
# for each of the existing nodclientses. If the search
|
295
|
+
# returns nothing or the client cannot be loaded, then
|
296
|
+
# we remove that client from the vault
|
297
|
+
# @return [void]
|
298
|
+
def remove_unknown_nodes
|
299
|
+
# build a list of clients to remove so we don't
|
300
|
+
# mutate the clients while iterating over search results
|
301
|
+
clients_to_remove = []
|
302
|
+
clients.each do |nodename|
|
303
|
+
clients_to_remove.push(nodename) unless node_exists?(nodename)
|
304
|
+
end
|
305
|
+
# now delete any flagged clients from the keys data bag
|
306
|
+
clients_to_remove.each do |client|
|
307
|
+
puts "Removing unknown client '#{client}'"
|
308
|
+
keys.delete(client, "clients")
|
271
309
|
end
|
272
310
|
end
|
273
311
|
|
274
|
-
|
275
|
-
|
312
|
+
# checks if a node exists on the Chef server by performing
|
313
|
+
# a search against the node index. If the search returns no
|
314
|
+
# results, the node does not exist. If it does return results,
|
315
|
+
# check if there is a matching client
|
316
|
+
# @param nodename [String] the name of the node
|
317
|
+
# @return [Boolean] whether the node exists or not
|
318
|
+
def node_exists?(nodename)
|
319
|
+
# the node does not exist if a search for the node with that
|
320
|
+
# name returns no results
|
321
|
+
query = Chef::Search::Query.new
|
322
|
+
numresults = query.search(:node, "name:#{nodename}")[2]
|
323
|
+
return false unless numresults > 0
|
324
|
+
# if the node search does return results, predicate node
|
325
|
+
# existence on the existence of a like-named client
|
326
|
+
client_exists?(nodename)
|
327
|
+
end
|
276
328
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
329
|
+
# checks if a client exists on the Chef server. If we get back
|
330
|
+
# a 404, the client does not exist. Any other HTTP errors are
|
331
|
+
# re-raised. Otherwise, the client exists
|
332
|
+
# @param clientname [String] the name of the client
|
333
|
+
# @return [Boolean] whether the client exists or not
|
334
|
+
def client_exists?(clientname)
|
335
|
+
begin
|
336
|
+
ChefVault::ChefPatch::ApiClient.load(clientname)
|
337
|
+
rescue Net::HTTPServerException => http_error
|
338
|
+
return false if http_error.response.code == "404"
|
285
339
|
raise http_error
|
286
340
|
end
|
341
|
+
true
|
287
342
|
end
|
288
|
-
|
289
|
-
client
|
290
343
|
end
|
291
344
|
end
|