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