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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/.rubocop_todo.yml +97 -0
  4. data/Changelog.md +34 -0
  5. data/DEMO.md +13 -6
  6. data/README.md +9 -4
  7. data/Rakefile +47 -10
  8. data/THEORY.md +361 -0
  9. data/bin/chef-vault +5 -5
  10. data/chef-vault.gemspec +11 -2
  11. data/features/clean_unknown_clients.feature +28 -3
  12. data/features/step_definitions/chef-databag.rb +5 -0
  13. data/features/step_definitions/chef-repo.rb +46 -8
  14. data/features/step_definitions/chef-vault.rb +69 -15
  15. data/features/support/env.rb +2 -2
  16. data/features/vault_create.feature +54 -0
  17. data/features/vault_list.feature +26 -0
  18. data/features/vault_show.feature +46 -0
  19. data/features/vault_update.feature +17 -0
  20. data/features/wrong_private_key.feature +14 -0
  21. data/lib/chef-vault.rb +0 -1
  22. data/lib/chef-vault/certificate.rb +1 -1
  23. data/lib/chef-vault/chef_patch/api_client.rb +1 -1
  24. data/lib/chef-vault/chef_patch/user.rb +1 -1
  25. data/lib/chef-vault/exceptions.rb +33 -12
  26. data/lib/chef-vault/item.rb +262 -209
  27. data/lib/chef-vault/item_keys.rb +90 -88
  28. data/lib/chef-vault/user.rb +1 -1
  29. data/lib/chef-vault/version.rb +2 -2
  30. data/lib/chef/knife/decrypt.rb +1 -2
  31. data/lib/chef/knife/encrypt_create.rb +1 -2
  32. data/lib/chef/knife/encrypt_delete.rb +1 -2
  33. data/lib/chef/knife/encrypt_remove.rb +1 -2
  34. data/lib/chef/knife/encrypt_rotate_keys.rb +1 -2
  35. data/lib/chef/knife/encrypt_update.rb +1 -2
  36. data/lib/chef/knife/mixin/compat.rb +3 -3
  37. data/lib/chef/knife/mixin/helper.rb +6 -8
  38. data/lib/chef/knife/vault_admins.rb +1 -2
  39. data/lib/chef/knife/vault_base.rb +2 -2
  40. data/lib/chef/knife/vault_create.rb +3 -4
  41. data/lib/chef/knife/vault_decrypt.rb +3 -4
  42. data/lib/chef/knife/vault_delete.rb +2 -4
  43. data/lib/chef/knife/vault_download.rb +1 -2
  44. data/lib/chef/knife/vault_edit.rb +3 -6
  45. data/lib/chef/knife/vault_list.rb +53 -0
  46. data/lib/chef/knife/vault_refresh.rb +1 -2
  47. data/lib/chef/knife/vault_remove.rb +3 -7
  48. data/lib/chef/knife/vault_rotate_all_keys.rb +2 -4
  49. data/lib/chef/knife/vault_rotate_keys.rb +2 -4
  50. data/lib/chef/knife/vault_show.rb +4 -5
  51. data/lib/chef/knife/vault_update.rb +7 -9
  52. data/spec/chef-vault/certificate_spec.rb +0 -2
  53. data/spec/chef-vault/item_spec.rb +77 -1
  54. data/spec/chef-vault/user_spec.rb +0 -2
  55. data/spec/chef-vault_spec.rb +1 -1
  56. data/spec/spec_helper.rb +1 -3
  57. 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
- ChefVault::Exceptions::ItemNotFound
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, value|
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
- ChefVault::Exceptions::ItemNotFound
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, value|
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
- ChefVault::Exceptions::ItemNotFound
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.inject([]) do |array, key|
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
- ChefVault::Exceptions::ItemNotFound
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 = %W( id )
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,v| included_values.include?(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
- output(output_data)
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
- clients = vault_item.clients().clone().sort()
77
- clients.each do |client|
78
- print "Deleting #{client}\n"
79
- vault_item.keys.delete(client, "clients")
80
- end
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
- ChefVault::Exceptions::ItemNotFound
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
@@ -19,7 +19,7 @@ RSpec.describe ChefVault do
19
19
 
20
20
  its(:vault) { should eq "foo" }
21
21
 
22
- specify { expect { Chef::Config[:node_name ].should eq "bar" } }
22
+ specify { expect { Chef::Config[:node_name].should eq "bar" } }
23
23
  end
24
24
 
25
25
  describe '#version' do
@@ -1,6 +1,4 @@
1
- if ENV['COVERAGE']
2
- require 'simplecov'
3
- end
1
+ require 'simplecov' if ENV['COVERAGE']
4
2
 
5
3
  require_relative '../lib/chef-vault'
6
4