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
@@ -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
|