chef-vault 1.2.5 → 2.0.1.pre

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.
@@ -1,185 +0,0 @@
1
- # Description: Chef-Vault EncryptCert 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'
17
-
18
- class EncryptCert < Chef::Knife
19
- deps do
20
- require 'chef/search/query'
21
- require File.expand_path('../compat', __FILE__)
22
- include ChefVault::Compat
23
- end
24
-
25
- banner "knife encrypt cert --search SEARCH --cert CERT --password PASSWORD --name NAME --admins ADMINS"
26
-
27
- option :search,
28
- :short => '-S SEARCH',
29
- :long => '--search SEARCH',
30
- :description => 'node search for nodes to encrypt to'
31
-
32
- option :cert,
33
- :short => '-C CERT',
34
- :long => '--cert CERT',
35
- :description => 'cert with contents to encrypt'
36
-
37
- option :admins,
38
- :short => '-A ADMINS',
39
- :long => '--admins ADMINS',
40
- :description => 'administrators who can decrypt certificate'
41
-
42
- option :password,
43
- :short => '-P PASSWORD',
44
- :long => '--password PASSWORD',
45
- :description => 'optional pfx password'
46
-
47
- option :name,
48
- :short => '-N NAME',
49
- :long => '--name NAME',
50
- :description => 'optional data bag name'
51
-
52
- def run
53
- unless config[:search]
54
- puts("You must supply either -S or --search")
55
- exit 1
56
- end
57
- unless config[:cert]
58
- puts("You must supply either -C or --cert")
59
- exit 1
60
- end
61
- unless config[:admins]
62
- puts("You must supply either -A or --admins")
63
- exit 1
64
- end
65
- extend_context_object(self)
66
-
67
- data_bag = "certs"
68
- data_bag_path = "./data_bags/#{data_bag}"
69
-
70
- unless ::File.exists?(data_bag_path)
71
- require 'fileutils'
72
-
73
- puts("INFO: #{data_bag_path} does not exist, creating...")
74
- FileUtils.mkdir_p(data_bag_path)
75
- end
76
-
77
- node_search = config[:search]
78
- admins = config[:admins]
79
- file_to_encrypt = config[:cert]
80
- contents = open(file_to_encrypt, "rb").read
81
- name = config[:name] ? config[:name].gsub(".", "_") : File.basename(file_to_encrypt, ".*").gsub(".", "_")
82
-
83
- current_dbi = Hash.new
84
- current_dbi_keys = Hash.new
85
- if File.exists?("#{data_bag_path}/#{name}_keys.json") && File.exists?("#{data_bag_path}/#{name}.json")
86
- current_dbi_keys = JSON.parse(File.open("#{data_bag_path}/#{name}_keys.json"){ |file| file.read() })
87
- current_dbi = JSON.parse(File.open("#{data_bag_path}/#{name}_keys.json"){ |file| file.read() })
88
-
89
- unless equal?(data_bag, name, "contents", contents)
90
- puts("FATAL: Content in #{data_bag_path}/#{name}.json does not match content in file supplied!")
91
- exit 1
92
- end
93
- else
94
- puts("INFO: Existing data bag #{data_bag}/#{name} does not exist in #{data_bag_path}, continuing as fresh build...")
95
- end
96
-
97
- # Get the public keys for all of the nodes to encrypt for. Skipping the nodes that are already in
98
- # the data bag
99
- keyfob = Hash.new
100
- public_keys = search(:node, node_search).map(&:name).map do |client|
101
- begin
102
- if current_dbi_keys[client]
103
- puts("INFO: Skipping #{client} as it is already in the data bag...")
104
- else
105
- puts("INFO: Adding #{client} to public_key array...")
106
- keyfob[client] = get_client_public_key(client)
107
- end
108
- rescue Exception => node_error
109
- puts("WARNING: Caught exception: #{node_error.message} while processing #{client}, so skipping...")
110
- end
111
- end
112
-
113
- # Get the public keys for the admin users, skipping users already in the data bag
114
- public_keys << admins.split(/[\s,]+/).map do |user|
115
- begin
116
- if current_dbi_keys[user]
117
- puts("INFO: Skipping #{user} as it is already in the data bag")
118
- else
119
- puts("INFO: Adding #{user} to public_key array...")
120
- keyfob[user] = get_user_public_key(user)
121
- end
122
- rescue Exception => user_error
123
- puts("WARNING: Caught exception: #{user_error.message} while processing #{user}, so skipping...")
124
- end
125
- end
126
-
127
- if public_keys.length == 0
128
- puts "A node search for #{node_search} returned no results"
129
- exit 1
130
- end
131
-
132
- # Get the current secret, is nil if current secret does not exist yet
133
- current_secret = get_shared_secret(data_bag, name)
134
- data_bag_shared_key = current_secret ? current_secret : OpenSSL::PKey::RSA.new(245).to_pem.lines.to_a[1..-2].join
135
- enc_db_key_dbi = current_dbi_keys.empty? ? Mash.new({id: "#{name}_keys"}) : current_dbi_keys
136
-
137
- # Encrypt for every new node not already in the data bag
138
- keyfob.each do |node,pkey|
139
- puts("INFO: Encrypting for #{node}...")
140
- enc_db_key_dbi[node] = Base64.encode64(pkey.public_encrypt(data_bag_shared_key))
141
- end unless keyfob.empty?
142
-
143
- # Delete existing keys data bag and rewrite the whole bag from memory
144
- puts("INFO: Writing #{data_bag_path}/#{name}_keys.json...")
145
- File.delete("#{data_bag_path}/#{name}_keys.json") if File.exists?("#{data_bag_path}/#{name}_keys.json")
146
- File.open("#{data_bag_path}/#{name}_keys.json",'w'){ |file| file.write(JSON.pretty_generate(enc_db_key_dbi)) }
147
-
148
- # If the existing certificate bag does not exist, write it out with the correct certificate
149
- # Otherwise leave the existing bag alone
150
- if current_dbi.empty?
151
- dbi_mash = Mash.new({id: name, contents: contents})
152
- dbi_mash.merge!({password: config[:password]}) if config[:password]
153
- dbi = Chef::DataBagItem.from_hash(dbi_mash)
154
- edbi = Chef::EncryptedDataBagItem.encrypt_data_bag_item(dbi, data_bag_shared_key)
155
-
156
- puts("INFO: Writing #{data_bag_path}/#{name}.json...")
157
- File.open("#{data_bag_path}/#{name}.json",'w'){ |file| file.write(JSON.pretty_generate(edbi)) }
158
- end
159
-
160
- puts("INFO: Successfully wrote #{data_bag_path}/#{name}.json & #{data_bag_path}/#{name}_keys.json!")
161
- end
162
-
163
- def equal?(db, dbi, key, value)
164
- data_bag_path = "./data_bags/#{db}"
165
-
166
- shared_secret = get_shared_secret(db, dbi)
167
- dbi = JSON.parse(File.open("#{data_bag_path}/#{dbi}.json") { |file| file.read() })
168
- dbi = Chef::EncryptedDataBagItem.new dbi, shared_secret
169
-
170
- dbi[key] == value
171
- end
172
-
173
- def get_shared_secret(db, dbi)
174
- data_bag_path = "./data_bags/#{db}"
175
-
176
- private_key = OpenSSL::PKey::RSA.new(open(Chef::Config[:client_key]).read())
177
- key = File.exists?("#{data_bag_path}/#{dbi}_keys.json") ? JSON.parse(File.open("#{data_bag_path}/#{dbi}_keys.json"){ |file| file.read() }) : nil
178
-
179
- begin
180
- private_key.private_decrypt(Base64.decode64(key[Chef::Config[:node_name]]))
181
- rescue
182
- nil
183
- end
184
- end
185
- end
@@ -1,182 +0,0 @@
1
- # Description: Chef-Vault EncryptPassword 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'
17
-
18
- class EncryptPassword < Chef::Knife
19
- deps do
20
- require 'chef/search/query'
21
- require File.expand_path('../compat', __FILE__)
22
- include ChefVault::Compat
23
- end
24
-
25
- banner "knife encrypt password --search SEARCH --username USERNAME --password PASSWORD --admins ADMINS"
26
-
27
- option :search,
28
- :short => '-S SEARCH',
29
- :long => '--search SEARCH',
30
- :description => 'node search for nodes to encrypt for'
31
-
32
- option :username,
33
- :short => '-U USERNAME',
34
- :long => '--username USERNAME',
35
- :description => 'username of account to encrypt'
36
-
37
- option :password,
38
- :short => '-P PASSWORD',
39
- :long => '--password PASSWORD',
40
- :description => 'password of account to encrypt'
41
-
42
- option :admins,
43
- :short => '-A ADMINS',
44
- :long => '--admins ADMINS',
45
- :description => 'administrators who can decrypt password'
46
-
47
- def run
48
- unless config[:search]
49
- puts("You must supply either -S or --search")
50
- exit 1
51
- end
52
- unless config[:username]
53
- puts("You must supply either -U or --username")
54
- exit 1
55
- end
56
- unless config[:password]
57
- puts("You must supply either -P or --password")
58
- exit 1
59
- end
60
- unless config[:admins]
61
- puts("You must supply either -A or --admins")
62
- exit 1
63
- end
64
-
65
- extend_context_object(self)
66
-
67
- data_bag = "passwords"
68
- data_bag_path = "./data_bags/#{data_bag}"
69
-
70
- unless ::File.exists?(data_bag_path)
71
- require 'fileutils'
72
-
73
- puts("INFO: #{data_bag_path} does not exist, creating...")
74
- FileUtils.mkdir_p(data_bag_path)
75
- end
76
-
77
- node_search = config[:search]
78
- admins = config[:admins]
79
- username = config[:username]
80
- password = config[:password]
81
- current_dbi = Hash.new
82
- current_dbi_keys = Hash.new
83
- if File.exists?("#{data_bag_path}/#{username}_keys.json") && File.exists?("#{data_bag_path}/#{username}.json")
84
- current_dbi_keys = JSON.parse(File.open("#{data_bag_path}/#{username}_keys.json"){ |file| file.read() })
85
- current_dbi = JSON.parse(File.open("#{data_bag_path}/#{username}.json"){ |file| file.read() })
86
-
87
- unless equal?(data_bag, username, "password", password)
88
- puts("FATAL: Password in #{data_bag_path}/#{username}.json does not match password supplied!")
89
- exit 1
90
- end
91
- else
92
- puts("INFO: Existing data bag #{data_bag}/#{username} does not exist in #{data_bag_path}, continuing as fresh build...")
93
- end
94
-
95
- # Get the public keys for all of the nodes to encrypt for. Skipping the nodes that are already in
96
- # the data bag
97
- keyfob = Hash.new
98
- public_keys = search(:node, node_search).map(&:name).map do |client|
99
- begin
100
- if current_dbi_keys[client]
101
- puts("INFO: Skipping #{client} as it is already in the data bag...")
102
- else
103
- puts("INFO: Adding #{client} to public_key array...")
104
- keyfob[client] = get_client_public_key(client)
105
- end
106
- rescue Exception => node_error
107
- puts("WARNING: Caught exception: #{node_error.message} while processing #{client}, so skipping...")
108
- end
109
- end
110
-
111
- # Get the public keys for the admin users, skipping users already in the data bag
112
- public_keys << admins.split(/[\s,]+/).map do |user|
113
- begin
114
- if current_dbi_keys[user]
115
- puts("INFO: Skipping #{user} as it is already in the data bag")
116
- else
117
- puts("INFO: Adding #{user} to public_key array...")
118
- keyfob[user] = get_user_public_key(user)
119
- end
120
- rescue Exception => user_error
121
- puts("WARNING: Caught exception: #{user_error.message} while processing #{user}, so skipping...")
122
- end
123
- end
124
-
125
- if public_keys.length == 0
126
- puts "A node search for #{node_search} returned no results"
127
- exit 1
128
- end
129
-
130
- # Get the current secret, is nil if current secret does not exist yet
131
- current_secret = get_shared_secret(data_bag, username)
132
- data_bag_shared_key = current_secret ? current_secret : OpenSSL::PKey::RSA.new(245).to_pem.lines.to_a[1..-2].join
133
- enc_db_key_dbi = current_dbi_keys.empty? ? Mash.new({id: "#{username}_keys"}) : current_dbi_keys
134
-
135
- # Encrypt for every new node not already in the data bag
136
- keyfob.each do |node,pkey|
137
- puts("INFO: Encrypting for #{node}...")
138
- enc_db_key_dbi[node] = Base64.encode64(pkey.public_encrypt(data_bag_shared_key))
139
- end unless keyfob.empty?
140
-
141
- # Delete existing keys data bag and rewrite the whole bag from memory
142
- puts("INFO: Writing #{data_bag_path}/#{username}_keys.json...")
143
- File.delete("#{data_bag_path}/#{username}_keys.json") if File.exists?("#{data_bag_path}/#{username}_keys.json")
144
- File.open("#{data_bag_path}/#{username}_keys.json",'w'){ |file| file.write(JSON.pretty_generate(enc_db_key_dbi)) }
145
-
146
- # If the existing password bag does not exist, write it out with the correct password
147
- # Otherwise leave the existing bag alone
148
- if current_dbi.empty?
149
- dbi_mash = Mash.new({id: username, username: username, password: password})
150
- dbi = Chef::DataBagItem.from_hash(dbi_mash)
151
- edbi = Chef::EncryptedDataBagItem.encrypt_data_bag_item(dbi, data_bag_shared_key)
152
-
153
- puts("INFO: Writing #{data_bag_path}/#{username}.json...")
154
- File.open("#{data_bag_path}/#{username}.json",'w'){ |file| file.write(JSON.pretty_generate(edbi)) }
155
- end
156
-
157
- puts("INFO: Successfully wrote #{data_bag_path}/#{username}.json & #{data_bag_path}/#{username}_keys.json!")
158
- end
159
-
160
- def equal?(db, dbi, key, value)
161
- data_bag_path = "./data_bags/#{db}"
162
-
163
- shared_secret = get_shared_secret(db, dbi)
164
- dbi = JSON.parse(open("#{data_bag_path}/#{dbi}.json").read())
165
- dbi = Chef::EncryptedDataBagItem.new dbi, shared_secret
166
-
167
- dbi[key] == value
168
- end
169
-
170
- def get_shared_secret(db, dbi)
171
- data_bag_path = "./data_bags/#{db}"
172
-
173
- private_key = OpenSSL::PKey::RSA.new(open(Chef::Config[:client_key]).read())
174
- key = File.exists?("#{data_bag_path}/#{dbi}_keys.json") ? JSON.parse(File.open("#{data_bag_path}/#{dbi}_keys.json"){ |file| file.read() }) : nil
175
-
176
- begin
177
- private_key.private_decrypt(Base64.decode64(key[Chef::Config[:node_name]]))
178
- rescue
179
- nil
180
- end
181
- end
182
- end
@@ -1,71 +0,0 @@
1
- # Description: ChefVault::Compat module
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
- # Make a wraper to chef10/11 "shef/shell" changes
17
-
18
- class ChefVault
19
- module Compat
20
- require 'chef/version'
21
- def extend_context_object(obj)
22
- if Chef::VERSION.to_i >= 11
23
- require "chef/shell/ext"
24
- Shell::Extensions.extend_context_object(obj)
25
- else
26
- require 'chef/shef/ext'
27
- Shef::Extensions.extend_context_object(obj)
28
- end
29
- end
30
-
31
- def get_client_public_key(client)
32
- get_public_key(api.get("clients/#{client}"))
33
- end
34
-
35
- def get_user_public_key(user)
36
- begin
37
- user = api.get("users/#{user}")
38
- rescue Exception
39
- puts("INFO: Could not locate user #{user}, searching for client key instead")
40
- user = api.get("clients/#{user}")
41
- end
42
- get_public_key(user)
43
- end
44
-
45
- def get_public_key(client)
46
- # Check the response back from the api call to see if
47
- # we get 'certificate' which is Chef 10 or just
48
- # 'public_key' which is Chef 11
49
- unless client.is_a?(Chef::ApiClient)
50
- name = client['name']
51
- certificate = client['certificate']
52
- public_key = client['public_key']
53
-
54
- client = Chef::ApiClient.new
55
- client.name name
56
- client.admin false
57
-
58
- if certificate
59
- cert_der = OpenSSL::X509::Certificate.new certificate
60
- client.public_key cert_der.public_key.to_s
61
- else
62
- client.public_key public_key
63
- end
64
- end
65
-
66
- public_key = OpenSSL::PKey::RSA.new client.public_key
67
-
68
- public_key
69
- end
70
- end
71
- end