hiera-eyaml 1.1.2 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -4
- data/bin/eyaml +246 -56
- data/lib/hiera/backend/version.rb +1 -1
- metadata +1 -1
data/README.md
CHANGED
@@ -47,9 +47,9 @@ This creates a public and private key with default names in the default location
|
|
47
47
|
|
48
48
|
To encrypt something, you only need the public_key, so distribute that to people creating hiera properties
|
49
49
|
|
50
|
-
$ eyaml -e
|
50
|
+
$ eyaml -e filename # Encrypt a file
|
51
|
+
$ eyaml -e -s text # Encrypt some text
|
51
52
|
$ eyaml -e -p # Encrypt a password (prompt for it)
|
52
|
-
$ eyaml -e -f filename # Encrypt a file
|
53
53
|
|
54
54
|
### Decryption
|
55
55
|
|
@@ -57,8 +57,8 @@ This creates a public and private key with default names in the default location
|
|
57
57
|
|
58
58
|
To test decryption you can also use the eyaml tool if you have both keys
|
59
59
|
|
60
|
-
$ eyaml -d
|
61
|
-
$ eyaml -d -
|
60
|
+
$ eyaml -d filename # Decrypt a file (PEM format)
|
61
|
+
$ eyaml -d -s SOME-ENCRYPTED-TEXT # Decrypt some text
|
62
62
|
|
63
63
|
Change the permissions so that the private key is only readable by the user that hiera (puppet) is
|
64
64
|
running as.
|
data/bin/eyaml
CHANGED
@@ -5,8 +5,14 @@ require 'base64'
|
|
5
5
|
require 'trollop'
|
6
6
|
require 'highline/import'
|
7
7
|
require 'hiera/backend/version'
|
8
|
+
require 'tempfile'
|
8
9
|
|
9
|
-
|
10
|
+
ENCRYPTED_BLOCK = />\n( *)ENC\[([a-zA-Z0-9+\/ \n]+)\]/
|
11
|
+
ENCRYPTED_STRING = /ENC\[([a-zA-Z0-9+\/]+)\]/
|
12
|
+
DECRYPTED_BLOCK = />\n( *)ENC!\[(.+)\]!ENC/
|
13
|
+
DECRYPTED_STRING = /ENC!\[(.+)\]!ENC/
|
14
|
+
|
15
|
+
def ensure_key_dir_exists(key_file)
|
10
16
|
key_dir = File.dirname(key_file)
|
11
17
|
|
12
18
|
if !File.directory?(key_dir)
|
@@ -15,6 +21,73 @@ def ensureKeyDirExists(key_file)
|
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
24
|
+
def get_file_input(options)
|
25
|
+
if options[:file]
|
26
|
+
File.read options[:file]
|
27
|
+
else
|
28
|
+
STDIN.read
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_input(options)
|
33
|
+
return options[:string] if options[:string]
|
34
|
+
|
35
|
+
if options[:password]
|
36
|
+
ask("Enter password: ") {|q| q.echo = "*" }
|
37
|
+
end
|
38
|
+
|
39
|
+
get_file_input(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def encrypt(public_key, plaintext)
|
43
|
+
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
|
44
|
+
ciphertext_binary = OpenSSL::PKCS7::encrypt([public_key], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der
|
45
|
+
Base64.encode64(ciphertext_binary).strip
|
46
|
+
end
|
47
|
+
|
48
|
+
def decrypt(public_key, private_key, ciphertext)
|
49
|
+
ciphertext_decoded = Base64.decode64(ciphertext)
|
50
|
+
pkcs7 = OpenSSL::PKCS7.new( ciphertext_decoded )
|
51
|
+
pkcs7.decrypt(private_key, public_key)
|
52
|
+
end
|
53
|
+
|
54
|
+
def decrypt_eyaml(public_key, private_key, cipher_eyaml)
|
55
|
+
# decrypt blocks first
|
56
|
+
plain_block_eyaml = cipher_eyaml.gsub(ENCRYPTED_BLOCK) { |match|
|
57
|
+
indentation = $1
|
58
|
+
ciphertext = $2.gsub(/[ \n]/, '')
|
59
|
+
plaintext = decrypt(public_key, private_key, ciphertext)
|
60
|
+
">\n"+indentation+"ENC![" + plaintext + "]!ENC"
|
61
|
+
}
|
62
|
+
# then decrypt strings
|
63
|
+
plain_block_eyaml.gsub(ENCRYPTED_STRING) { |match|
|
64
|
+
plaintext = decrypt(public_key, private_key, $1)
|
65
|
+
"ENC![" + plaintext + "]!ENC"
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def encrypt_eyaml(public_key, plain_eyaml)
|
70
|
+
# encrypt blocks
|
71
|
+
cipher_block_eyaml = plain_eyaml.gsub(DECRYPTED_BLOCK) { |match|
|
72
|
+
indentation = $1
|
73
|
+
ciphertext = encrypt(public_key, $2).gsub(/\n/,"\n"+indentation)
|
74
|
+
">\n" + indentation + "ENC[" + ciphertext + "]"
|
75
|
+
}
|
76
|
+
# encrypt strings
|
77
|
+
cipher_block_eyaml.gsub(DECRYPTED_STRING) { |match|
|
78
|
+
ciphertext = encrypt(public_key, $1).gsub(/\n/,'')
|
79
|
+
"ENC[" + ciphertext + "]"
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def shred(file, clean_size)
|
84
|
+
[0xff, 0x55, 0xaa, 0x00].each do |byte|
|
85
|
+
file.seek(0, IO::SEEK_SET)
|
86
|
+
clean_size.times { file.print(byte.chr) }
|
87
|
+
file.fsync
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
18
91
|
options = Trollop::options do
|
19
92
|
|
20
93
|
version "Hiera-eyaml version " + Hiera::Backend::Eyaml::VERSION.to_s
|
@@ -22,28 +95,33 @@ options = Trollop::options do
|
|
22
95
|
Hiera-eyaml is a backend for Hiera which provides OpenSSL encryption/decryption for Hiera properties
|
23
96
|
|
24
97
|
Usage:
|
25
|
-
eyaml [options] [
|
98
|
+
eyaml [options] [file-to-encrypt]
|
26
99
|
EOS
|
27
100
|
|
28
101
|
opt :createkeys, "Create public and private keys for use encrypting properties", :short => 'c'
|
102
|
+
opt :encrypt, "Encrypt a string, password, file or stdin"
|
103
|
+
opt :decrypt, "Decrypt a string, file or stdin"
|
104
|
+
opt :eyaml, "Assume input is eyaml format"
|
105
|
+
opt :edit, "Edit an encrypted file inplace, implies --eyaml"
|
29
106
|
opt :password, "Encrypt a password entered on the terminal", :short => 'p'
|
30
|
-
opt :
|
31
|
-
opt :private_key, "Filename of the private_key", :type => :string
|
32
|
-
opt :public_key, "Filename of the public_key", :type => :string
|
33
|
-
opt :
|
34
|
-
opt :decrypt, "Decrypt something"
|
107
|
+
opt :string, "Encrypt a string provided on the command line", :short => 's', :type => :string
|
108
|
+
opt :private_key, "Filename of the private_key", :type => :string, :default => "/etc/hiera/keys/private_key.pem"
|
109
|
+
opt :public_key, "Filename of the public_key", :type => :string, :default => "/etc/hiera/keys/public_key.pem"
|
110
|
+
opt :output, "Output mode to use when encrypting (examples, block or string)", :type => :string, :default => "examples"
|
35
111
|
end
|
36
112
|
|
37
|
-
|
113
|
+
main_option_count = 0
|
114
|
+
main_option_count += 1 if options[:createkeys]
|
115
|
+
main_option_count += 1 if options[:encrypt]
|
116
|
+
main_option_count += 1 if options[:decrypt]
|
117
|
+
main_option_count += 1 if options[:edit]
|
38
118
|
|
39
|
-
|
40
|
-
options[:
|
41
|
-
options[:public_key] ||= "/etc/hiera/keys/public_key.pem"
|
42
|
-
options[:string] = ARGV.join(' ')
|
119
|
+
Trollop::die "You can only specify one main action" if main_option_count > 1
|
120
|
+
Trollop::die "You cannot specify --password and --string" if options[:password] and options[:string]
|
43
121
|
|
44
|
-
|
45
|
-
|
46
|
-
|
122
|
+
options[:file] = ARGV[0]
|
123
|
+
if options[:file] and (options[:password] or options[:string])
|
124
|
+
$stderr.puts "WARN: file supplied but will be shadowed by string or password"
|
47
125
|
end
|
48
126
|
|
49
127
|
if options[:createkeys]
|
@@ -51,15 +129,15 @@ if options[:createkeys]
|
|
51
129
|
# Try to do equivalent of:
|
52
130
|
# openssl req -x509 -nodes -days 100000 -newkey rsa:2048 -keyout privatekey.pem -out publickey.pem -subj '/'
|
53
131
|
|
54
|
-
|
55
|
-
|
132
|
+
ensure_key_dir_exists(options[:private_key])
|
133
|
+
ensure_key_dir_exists(options[:public_key])
|
56
134
|
|
57
135
|
key = OpenSSL::PKey::RSA.new(2048)
|
58
136
|
open( options[:private_key], "w" ) do |io|
|
59
137
|
io.write(key.to_pem)
|
60
138
|
end
|
61
139
|
|
62
|
-
puts "#{options[:private_key]} created."
|
140
|
+
$stderr.puts "#{options[:private_key]} created."
|
63
141
|
|
64
142
|
name = OpenSSL::X509::Name.parse("/")
|
65
143
|
cert = OpenSSL::X509::Certificate.new()
|
@@ -85,68 +163,180 @@ if options[:createkeys]
|
|
85
163
|
open( options[:public_key], "w" ) do |io|
|
86
164
|
io.write(cert.to_pem)
|
87
165
|
end
|
88
|
-
puts "#{options[:public_key]} created."
|
166
|
+
$stderr.puts "#{options[:public_key]} created."
|
89
167
|
exit
|
90
168
|
end
|
91
169
|
|
92
|
-
if options[:
|
170
|
+
if options[:eyaml]
|
93
171
|
|
94
|
-
|
95
|
-
|
96
|
-
|
172
|
+
if options[:decrypt]
|
173
|
+
# prepare to decrypt blocks
|
174
|
+
private_key_pem = File.read( options[:private_key] )
|
175
|
+
private_key = OpenSSL::PKey::RSA.new( private_key_pem )
|
97
176
|
|
98
|
-
|
99
|
-
|
177
|
+
public_key_pem = File.read( options[:public_key] )
|
178
|
+
public_key = OpenSSL::X509::Certificate.new( public_key_pem )
|
179
|
+
|
180
|
+
eyaml = get_file_input options
|
181
|
+
plain_eyaml = decrypt_eyaml(public_key, private_key, eyaml)
|
182
|
+
|
183
|
+
puts plain_eyaml
|
100
184
|
exit
|
101
185
|
end
|
102
186
|
|
103
|
-
|
104
|
-
|
187
|
+
if options[:encrypt]
|
188
|
+
# prepare to encrypt blocks
|
189
|
+
public_key_pem = File.read( options[:public_key] )
|
190
|
+
public_key = OpenSSL::X509::Certificate.new( public_key_pem )
|
105
191
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
192
|
+
eyaml = get_file_input options
|
193
|
+
cipher_eyaml = encrypt_yaml(public_key, eyaml)
|
194
|
+
|
195
|
+
puts eyaml
|
196
|
+
exit
|
197
|
+
end
|
110
198
|
|
111
|
-
|
112
|
-
puts "block: >"
|
113
|
-
puts " ENC[" + ciphertext_as_block.gsub(/\n/, "\n ") + "]"
|
114
|
-
exit
|
199
|
+
else
|
115
200
|
|
116
|
-
|
201
|
+
if options[:encrypt]
|
202
|
+
plaintext = get_input options
|
117
203
|
|
118
|
-
if
|
204
|
+
if plaintext.nil? or plaintext.length == 0
|
205
|
+
$stderr.puts "Specify a string or --file to encrypt something. See --help for more usage instructions."
|
206
|
+
exit
|
207
|
+
end
|
119
208
|
|
120
|
-
|
121
|
-
|
122
|
-
|
209
|
+
public_key_pem = File.read( options[:public_key] )
|
210
|
+
public_key = OpenSSL::X509::Certificate.new( public_key_pem )
|
211
|
+
|
212
|
+
ciphertext_as_block = encrypt(public_key, plaintext)
|
213
|
+
ciphertext_as_string = ciphertext_as_block.split("\n").join('')
|
214
|
+
|
215
|
+
case options[:output]
|
216
|
+
when "examples"
|
217
|
+
puts "string: ENC[#{ciphertext_as_string}]\n\nOR\n\n"
|
218
|
+
puts "block: >"
|
219
|
+
puts " ENC[" + ciphertext_as_block.gsub(/\n/, "\n ") + "]"
|
220
|
+
when "block"
|
221
|
+
puts "ENC[" + ciphertext_as_block + "]"
|
222
|
+
when "string"
|
223
|
+
puts "ENC[#{ciphertext_as_string}]"
|
224
|
+
else
|
225
|
+
$stderr.puts "Unknown output option: " + options[:output]
|
226
|
+
exit 1
|
227
|
+
end
|
228
|
+
exit
|
123
229
|
|
124
|
-
|
230
|
+
end
|
125
231
|
|
126
|
-
|
127
|
-
ciphertext_decoded = Base64.decode64(ciphertext)
|
232
|
+
if options[:decrypt]
|
128
233
|
|
129
|
-
|
130
|
-
|
131
|
-
|
234
|
+
ciphertext = get_input options
|
235
|
+
if ciphertext.nil? or ciphertext.length == 0
|
236
|
+
$stderr.puts "Specify a string or --file to decrypt something. See --help for more usage instructions."
|
237
|
+
exit 1
|
132
238
|
end
|
133
239
|
|
134
|
-
|
135
|
-
private_key = OpenSSL::PKey::RSA.new( private_key_pem )
|
240
|
+
if ciphertext.start_with? "ENC["
|
136
241
|
|
137
|
-
|
138
|
-
public_key = OpenSSL::X509::Certificate.new( public_key_pem )
|
242
|
+
ciphertext = ciphertext[4..-2]
|
139
243
|
|
140
|
-
|
244
|
+
private_key_pem = File.read( options[:private_key] )
|
245
|
+
private_key = OpenSSL::PKey::RSA.new( private_key_pem )
|
246
|
+
|
247
|
+
public_key_pem = File.read( options[:public_key] )
|
248
|
+
public_key = OpenSSL::X509::Certificate.new( public_key_pem )
|
249
|
+
|
250
|
+
plaintext = decrypt(public_key, private_key, ciphertext)
|
251
|
+
puts "#{plaintext}"
|
252
|
+
exit
|
253
|
+
|
254
|
+
else
|
255
|
+
|
256
|
+
$stderr.puts "Ciphertext is not an eyaml encrypted string (Does not start with ENC[...])"
|
257
|
+
exit 1
|
258
|
+
|
259
|
+
end
|
141
260
|
|
142
|
-
plaintext = pkcs7.decrypt(private_key, public_key)
|
143
|
-
puts "#{plaintext}"
|
144
261
|
exit
|
145
262
|
|
146
|
-
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
147
266
|
|
148
|
-
|
267
|
+
if options[:edit]
|
268
|
+
$stderr.puts "Launching edit mode"
|
269
|
+
# prepare to edit blocks
|
270
|
+
private_key_pem = File.read( options[:private_key] )
|
271
|
+
private_key = OpenSSL::PKey::RSA.new( private_key_pem )
|
149
272
|
|
273
|
+
public_key_pem = File.read( options[:public_key] )
|
274
|
+
public_key = OpenSSL::X509::Certificate.new( public_key_pem )
|
275
|
+
|
276
|
+
original_eyaml = File.read( options[:file] )
|
277
|
+
original_size = original_eyaml.length
|
278
|
+
plain_eyaml = decrypt_eyaml(public_key, private_key, original_eyaml)
|
279
|
+
|
280
|
+
# write temp file
|
281
|
+
plain_file = Tempfile.open('eyaml_edit')
|
282
|
+
plain_file.puts plain_eyaml
|
283
|
+
plain_file.flush
|
284
|
+
|
285
|
+
# open editor
|
286
|
+
$editor = ENV['EDITOR']
|
287
|
+
if $editor == nil
|
288
|
+
%w{/usr/bin/sensible-editor /usr/bin/editor /usr/bin/vim /usr/bin/vi}.each do |editor|
|
289
|
+
if FileTest.executable?(editor)
|
290
|
+
$editor = editor
|
291
|
+
break
|
292
|
+
end
|
293
|
+
end
|
150
294
|
end
|
295
|
+
$stderr.puts "Opening decrypted file for editing in " + $editor
|
296
|
+
system($editor, plain_file.path)
|
297
|
+
status = $?
|
151
298
|
|
152
|
-
|
299
|
+
throw "Process has not exited!?" unless status.exited?
|
300
|
+
|
301
|
+
unless status.exitstatus == 0
|
302
|
+
$syserr.puts "Editor did not exit successfully (exit code #{status.exitstatus}), aborting"
|
303
|
+
exit 1
|
304
|
+
end
|
305
|
+
|
306
|
+
# some editors do not write new content in place, but instead
|
307
|
+
# make a new file and more it in the old file's place.
|
308
|
+
begin
|
309
|
+
reopened_plain_file = File.open(plain_file.path, "r+")
|
310
|
+
rescue Exception => e
|
311
|
+
STDERR.puts e
|
312
|
+
exit 1
|
313
|
+
end
|
314
|
+
new_eyaml = reopened_plain_file.read
|
315
|
+
|
316
|
+
# make sure nothing is left on disk
|
317
|
+
new_size = new_eyaml.length
|
318
|
+
old_size = plain_eyaml.length
|
319
|
+
clear_size = (new_size > old_size) ? new_size : old_size
|
320
|
+
shred(plain_file, clear_size)
|
321
|
+
plain_file.close true
|
322
|
+
shred(reopened_plain_file, clear_size)
|
323
|
+
reopened_plain_file.close
|
324
|
+
|
325
|
+
if new_eyaml.length == 0
|
326
|
+
$stderr.puts "Replacement content is empty, aborting"
|
327
|
+
exit 1
|
328
|
+
end
|
329
|
+
|
330
|
+
if (new_eyaml == plain_eyaml)
|
331
|
+
$stderr.puts "No changes made, aborting"
|
332
|
+
exit 1
|
333
|
+
end
|
334
|
+
|
335
|
+
# re-encrypt the file again, currently we re-encrypt indiscriminately
|
336
|
+
cipher_eyaml = encrypt_eyaml(public_key, new_eyaml)
|
337
|
+
|
338
|
+
File.open(options[:file], 'w') { |file|
|
339
|
+
file.write(cipher_eyaml)
|
340
|
+
}
|
341
|
+
exit
|
342
|
+
end
|