team-secrets 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +59 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +32 -0
- data/README.md +45 -0
- data/Rakefile +7 -0
- data/lib/team-secrets/file_manager.rb +31 -0
- data/lib/team-secrets/key_helper.rb +20 -0
- data/lib/team-secrets/manifest_manager.rb +72 -0
- data/lib/team-secrets/master_key.rb +73 -0
- data/lib/team-secrets/secret_manager.rb +121 -0
- data/lib/team-secrets/user_manager.rb +110 -0
- data/lib/team-secrets.rb +448 -0
- data/spec/file_manager_spec.rb +57 -0
- data/spec/manifest_manager_spec.rb +70 -0
- data/spec/master_key_spec.rb +107 -0
- data/spec/secret_manager_spec.rb +122 -0
- data/spec/support/test_key +30 -0
- data/spec/support/test_key.pub +1 -0
- data/spec/support/test_key.pub.pem +8 -0
- data/spec/user_manager_spec.rb +67 -0
- data/team-secrets.gemspec +19 -0
- metadata +72 -0
data/lib/team-secrets.rb
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'rubygems'
|
|
3
|
+
require 'gli'
|
|
4
|
+
require 'io/console'
|
|
5
|
+
|
|
6
|
+
require 'yaml'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
|
|
9
|
+
require 'digest'
|
|
10
|
+
require 'openssl'
|
|
11
|
+
|
|
12
|
+
require_relative 'team-secrets/manifest_manager'
|
|
13
|
+
require_relative 'team-secrets/user_manager'
|
|
14
|
+
require_relative 'team-secrets/secret_manager'
|
|
15
|
+
|
|
16
|
+
include GLI::App
|
|
17
|
+
|
|
18
|
+
program_desc 'Secrets - sharing secrets secretly'
|
|
19
|
+
|
|
20
|
+
pre do |global_options,command,options,args|
|
|
21
|
+
config = File.read('config.yaml') if File.exists?('config.yaml')
|
|
22
|
+
config = YAML.load(config) || {}
|
|
23
|
+
|
|
24
|
+
unless options.key? :user && !options[:user].nil?
|
|
25
|
+
if config.key? :user
|
|
26
|
+
options[:user] = config[:user]
|
|
27
|
+
else
|
|
28
|
+
puts 'Your user name was not specified. Use the `-u` flag or put it in config.yaml.'
|
|
29
|
+
next false
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
options[:user] = options[:user].strip
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
unless options.key? :private && !options[:user].nil?
|
|
36
|
+
if config.key? :private
|
|
37
|
+
options[:private] = config[:private]
|
|
38
|
+
else
|
|
39
|
+
puts 'Your private key was not specified. Use the `-p` flag or put it in config.yaml.'
|
|
40
|
+
next false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
valid_user_names = /\A[A-Z0-9_\.]+\Z/i
|
|
48
|
+
|
|
49
|
+
desc 'Start a new Secrets repository'
|
|
50
|
+
long_desc 'Create the necessary file structure to create a new Secrets repository'
|
|
51
|
+
|
|
52
|
+
skips_pre
|
|
53
|
+
command :init do |c|
|
|
54
|
+
|
|
55
|
+
c.desc 'Your username'
|
|
56
|
+
c.flag [:u,:user], type: String, must_match: valid_user_names
|
|
57
|
+
|
|
58
|
+
c.desc 'Path to public key (in PEM format)'
|
|
59
|
+
c.flag [:k,:key_file], type: String
|
|
60
|
+
|
|
61
|
+
c.action do |global_options,options,args|
|
|
62
|
+
raise 'OpenSSL must be installed and in the PATH' unless system("openssl version")
|
|
63
|
+
|
|
64
|
+
user_name = options[:user]
|
|
65
|
+
|
|
66
|
+
until user_name && (/\A.+\z/i =~ user_name)
|
|
67
|
+
default = `echo $USER`.chomp
|
|
68
|
+
print "Your username (no spaces) [#{default}]: "
|
|
69
|
+
user_name = STDIN.gets.chomp
|
|
70
|
+
user_name = default if user_name.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
key_file = options[:key_file]
|
|
74
|
+
|
|
75
|
+
until key_file && File.exists?(key_file)
|
|
76
|
+
print 'Path to public key: '
|
|
77
|
+
key_file = STDIN.gets.chomp
|
|
78
|
+
puts "File does not exist or cannot be acccessed." unless File.exists?(key_file)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
puts "Creating users directory & users.yaml..."
|
|
82
|
+
|
|
83
|
+
users_file = UserManager.new
|
|
84
|
+
users_file.add user_name, key_file
|
|
85
|
+
# New master key
|
|
86
|
+
master_key = users_file.master_key
|
|
87
|
+
users_file.writeFile 'users.yaml'
|
|
88
|
+
|
|
89
|
+
puts "Creating template secrets.yaml..."
|
|
90
|
+
|
|
91
|
+
secrets_file = SecretManager.new
|
|
92
|
+
secrets_file.writeFile 'secrets.yaml'
|
|
93
|
+
|
|
94
|
+
puts "Writing manifest.yaml..."
|
|
95
|
+
|
|
96
|
+
manifest = ManifestManager.new master_key
|
|
97
|
+
manifest.update
|
|
98
|
+
manifest.writeFile 'manifest.yaml'
|
|
99
|
+
|
|
100
|
+
puts green('Done!')
|
|
101
|
+
puts 'Now, create a new repository with these files and commit. Your new secrets repo is ready to go.'
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
desc 'Manage users for this Secrets repository'
|
|
106
|
+
long_desc 'Add and remove users or servers who will be able to manage this Secrets repository'
|
|
107
|
+
|
|
108
|
+
command :user do |c|
|
|
109
|
+
|
|
110
|
+
c.desc 'Your user name'
|
|
111
|
+
c.flag [:u,:user], type: String, must_match: valid_user_names
|
|
112
|
+
|
|
113
|
+
c.desc 'Path to your private key'
|
|
114
|
+
c.flag [:p,:private], type: String
|
|
115
|
+
|
|
116
|
+
c.desc 'Add a new user'
|
|
117
|
+
c.command :add do |add|
|
|
118
|
+
add.action do |global_options,options,args|
|
|
119
|
+
|
|
120
|
+
print 'New user\'s name: '
|
|
121
|
+
new_user = STDIN.gets.chomp
|
|
122
|
+
raise 'A user name must be specified' if new_user.empty?
|
|
123
|
+
|
|
124
|
+
print 'Path to user\'s public key: '
|
|
125
|
+
key_file = STDIN.gets.chomp
|
|
126
|
+
raise "File does not exist or cannot be acccessed." unless File.exists?(key_file)
|
|
127
|
+
|
|
128
|
+
# Check signatures
|
|
129
|
+
# Use current user's private key to get master key from lock_box
|
|
130
|
+
# Use master key to get all secrets
|
|
131
|
+
# Add new user's record & public key
|
|
132
|
+
# Generate new master key
|
|
133
|
+
# Encrypt all secrets with new master key
|
|
134
|
+
# Encrypt master key with each user's public key, placing in lock_box
|
|
135
|
+
# Update signatures
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
c.desc 'List all users'
|
|
141
|
+
c.command :list do |add|
|
|
142
|
+
add.action do
|
|
143
|
+
|
|
144
|
+
# List all users
|
|
145
|
+
users = UserManager.new
|
|
146
|
+
users.loadFile 'users.yaml'
|
|
147
|
+
|
|
148
|
+
puts "#{users.all.length} users:\n"
|
|
149
|
+
users.all.each do |user|
|
|
150
|
+
puts user+"\n"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
c.desc 'Remove a user'
|
|
157
|
+
c.command :rm do |add|
|
|
158
|
+
add.action do |global_options,options,args|
|
|
159
|
+
|
|
160
|
+
print 'User to remove: '
|
|
161
|
+
new_user = STDIN.gets.chomp
|
|
162
|
+
raise 'A user name must be specified' if new_user.empty?
|
|
163
|
+
|
|
164
|
+
# Check signatures
|
|
165
|
+
# Use current user's private key to get master key from lock_box
|
|
166
|
+
# Use master key to get all secrets
|
|
167
|
+
# Remove old user's record & public key
|
|
168
|
+
# Generate new master key
|
|
169
|
+
# Encrypt all secrets with new master key
|
|
170
|
+
# Encrypt master key with each user's public key, placing in lock_box
|
|
171
|
+
# Update signatures
|
|
172
|
+
|
|
173
|
+
master_key = load_master_key(options[:user], options[:private])
|
|
174
|
+
|
|
175
|
+
manifest = ManifestManager.new master_key
|
|
176
|
+
manifest.validate
|
|
177
|
+
|
|
178
|
+
secrets = SecretManager.new master_key
|
|
179
|
+
secrets.loadFile 'secrets.yaml'
|
|
180
|
+
|
|
181
|
+
users = UserManager.new master_key
|
|
182
|
+
|
|
183
|
+
remove_data = users.find options[:remove]
|
|
184
|
+
|
|
185
|
+
raise 'User not found for removal' if remove_data.nil?
|
|
186
|
+
|
|
187
|
+
users.remove remove_data[:user]
|
|
188
|
+
users.writeFile 'users.yaml'
|
|
189
|
+
|
|
190
|
+
master_key = users.master_key
|
|
191
|
+
|
|
192
|
+
secrets.rotateMasterKey master_key
|
|
193
|
+
secrets.writeFile 'secrets.yaml'
|
|
194
|
+
|
|
195
|
+
manifest.master_key = master_key
|
|
196
|
+
manifest.update
|
|
197
|
+
manifest.writeFile 'manifest.yaml'
|
|
198
|
+
|
|
199
|
+
print green('Success! ')
|
|
200
|
+
puts 'User removed.'
|
|
201
|
+
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
desc 'Manage the secrets in this Secrets repository'
|
|
208
|
+
long_desc 'Add, read and remove secrets that users can retrieve from this Secrets repository'
|
|
209
|
+
|
|
210
|
+
command :secret do |c|
|
|
211
|
+
|
|
212
|
+
c.desc 'Your user name'
|
|
213
|
+
c.arg_name 'user'
|
|
214
|
+
c.flag [:u,:user], type: String, must_match: valid_user_names
|
|
215
|
+
|
|
216
|
+
c.desc 'Path to your private key'
|
|
217
|
+
c.arg_name 'private'
|
|
218
|
+
c.flag [:p,:private], type: String
|
|
219
|
+
|
|
220
|
+
c.desc 'Name of this secret (e.g., SMTP_PASS)'
|
|
221
|
+
c.arg_name 'name'
|
|
222
|
+
c.flag [:n,:name], type: String
|
|
223
|
+
|
|
224
|
+
c.desc 'The optional account name (e.g., a username)'
|
|
225
|
+
c.arg_name 'account', :optional
|
|
226
|
+
c.flag [:a,:account], type: String
|
|
227
|
+
|
|
228
|
+
c.desc 'Optional tags (e.g., PROD)'
|
|
229
|
+
c.arg_name 'tags', :optional
|
|
230
|
+
c.flag [:t,:tags], type: String
|
|
231
|
+
|
|
232
|
+
c.desc 'Any optional notes'
|
|
233
|
+
c.arg_name 'notes', :optional
|
|
234
|
+
c.flag [:notes], type: String
|
|
235
|
+
|
|
236
|
+
c.desc 'Add a new secret'
|
|
237
|
+
c.command :add do |add|
|
|
238
|
+
add.action do |global_options,options,args|
|
|
239
|
+
|
|
240
|
+
if options[:name].nil?
|
|
241
|
+
print 'A name for this secret: '
|
|
242
|
+
options[:name] = STDIN.gets.chomp
|
|
243
|
+
raise 'A name must be specified' if options[:name].empty?
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if options[:account].nil?
|
|
247
|
+
print 'The secret\'s account name (optional): '
|
|
248
|
+
options[:account] = STDIN.gets.chomp
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
if options[:tags].nil?
|
|
252
|
+
print 'Tags for this secret, space-separated (optional): '
|
|
253
|
+
options[:tags] = STDIN.gets.chomp
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
tags = parse_tags(options[:tags])
|
|
257
|
+
|
|
258
|
+
if args.empty?
|
|
259
|
+
print 'Secret to encrypt: '
|
|
260
|
+
secret = STDIN.noecho(&:gets)
|
|
261
|
+
secret = secret.chomp
|
|
262
|
+
raise 'No secret given' if secret.empty?
|
|
263
|
+
else
|
|
264
|
+
secret = args[0]
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Check signatures
|
|
268
|
+
# Use current user's private key to get master key from lock_box
|
|
269
|
+
# Add new secret's record
|
|
270
|
+
# Encrypt secret with master key
|
|
271
|
+
# Update signatures
|
|
272
|
+
puts
|
|
273
|
+
|
|
274
|
+
master_key = load_master_key(options[:user], options[:private])
|
|
275
|
+
|
|
276
|
+
manifest = ManifestManager.new master_key
|
|
277
|
+
manifest.validate
|
|
278
|
+
|
|
279
|
+
secrets = SecretManager.new master_key
|
|
280
|
+
secrets.loadFile 'secrets.yaml'
|
|
281
|
+
secrets.add options[:name].strip, secret, options[:account], tags
|
|
282
|
+
secrets.writeFile 'secrets.yaml'
|
|
283
|
+
|
|
284
|
+
manifest.update
|
|
285
|
+
manifest.writeFile 'manifest.yaml'
|
|
286
|
+
|
|
287
|
+
print green('Success! ')
|
|
288
|
+
puts 'New secret has been encrypted and added.'
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
c.desc 'List all secrets'
|
|
293
|
+
c.command :list do |add|
|
|
294
|
+
add.action do |global_options,options,args|
|
|
295
|
+
|
|
296
|
+
if options[:tags].nil?
|
|
297
|
+
print 'Tags for this secret, space-separated (optional): '
|
|
298
|
+
options[:tags] = STDIN.gets.chomp
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
tags = parse_tags(options[:tags])
|
|
302
|
+
|
|
303
|
+
# Check signatures
|
|
304
|
+
# List all secrets
|
|
305
|
+
|
|
306
|
+
master_key = load_master_key(options[:user], options[:private])
|
|
307
|
+
|
|
308
|
+
manifest = ManifestManager.new master_key
|
|
309
|
+
manifest.validate
|
|
310
|
+
|
|
311
|
+
secrets = SecretManager.new master_key
|
|
312
|
+
secrets.loadFile 'secrets.yaml'
|
|
313
|
+
all = secrets.getAll tags
|
|
314
|
+
|
|
315
|
+
puts 'All secrets: '
|
|
316
|
+
|
|
317
|
+
all.each {|tag| puts tag}
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
c.desc 'Reveal a secret'
|
|
322
|
+
c.command :show do |add|
|
|
323
|
+
add.action do |global_options,options,args|
|
|
324
|
+
|
|
325
|
+
if options[:name].nil?
|
|
326
|
+
print 'The name of the secret: '
|
|
327
|
+
options[:name] = STDIN.gets.chomp
|
|
328
|
+
raise 'A name must be specified' if options[:name].empty?
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
if options[:tags].nil?
|
|
332
|
+
print 'Tags for this secret, space-separated (optional): '
|
|
333
|
+
options[:tags] = STDIN.gets.chomp
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
tags = parse_tags(options[:tags])
|
|
337
|
+
|
|
338
|
+
# Check signatures
|
|
339
|
+
# Use current user's private key to get master key from lock_box
|
|
340
|
+
# Decrypt secret with master key
|
|
341
|
+
|
|
342
|
+
master_key = load_master_key(options[:user], options[:private])
|
|
343
|
+
|
|
344
|
+
manifest = ManifestManager.new master_key
|
|
345
|
+
manifest.validate
|
|
346
|
+
|
|
347
|
+
secrets = SecretManager.new master_key
|
|
348
|
+
secrets.loadFile 'secrets.yaml'
|
|
349
|
+
secret_data = secrets.find options[:name].strip, tags
|
|
350
|
+
|
|
351
|
+
secret_data.each do |key, value|
|
|
352
|
+
next if value.nil?
|
|
353
|
+
puts key.to_s + ': ' + value.inspect
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
c.desc 'Remove a secret'
|
|
360
|
+
c.command :rm do |add|
|
|
361
|
+
add.action do |global_options,options,args|
|
|
362
|
+
|
|
363
|
+
if options[:name].nil?
|
|
364
|
+
print 'The name of the secret: '
|
|
365
|
+
options[:name] = STDIN.gets.chomp
|
|
366
|
+
raise 'A name must be specified' if options[:name].empty?
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
if options[:tags].nil?
|
|
370
|
+
print 'Tags for this secret, space-separated (optional): '
|
|
371
|
+
options[:tags] = STDIN.gets.chomp
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
tags = parse_tags(options[:tags])
|
|
375
|
+
|
|
376
|
+
# Check signatures
|
|
377
|
+
# Use current user's private key to get master key from lock_box
|
|
378
|
+
# Remove secret from listing
|
|
379
|
+
# Update signatures
|
|
380
|
+
|
|
381
|
+
master_key = load_master_key(options[:user], options[:private])
|
|
382
|
+
|
|
383
|
+
manifest = ManifestManager.new master_key
|
|
384
|
+
manifest.validate
|
|
385
|
+
|
|
386
|
+
secrets = SecretManager.new master_key
|
|
387
|
+
secrets.loadFile 'secrets.yaml'
|
|
388
|
+
removed = secrets.remove options[:name].strip, tags
|
|
389
|
+
|
|
390
|
+
if removed == 0 then
|
|
391
|
+
puts 'No secrets matched criteria'
|
|
392
|
+
next
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
secrets.writeFile 'secrets.yaml'
|
|
396
|
+
|
|
397
|
+
manifest.update
|
|
398
|
+
manifest.writeFile 'manifest.yaml'
|
|
399
|
+
|
|
400
|
+
print green('Success! ')
|
|
401
|
+
|
|
402
|
+
if (removed == 1)
|
|
403
|
+
puts removed +' matching secret has been removed.'
|
|
404
|
+
else
|
|
405
|
+
puts removed +' matching secrets have been removed.'
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
on_error do |exception|
|
|
414
|
+
# Use GLI error handling for GLI exception
|
|
415
|
+
next true if exception.class.name.split("::").first == 'GLI'
|
|
416
|
+
|
|
417
|
+
$stderr.puts red(exception.message)
|
|
418
|
+
false # skip GLI's error handling
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def load_master_key(user, private_key_file)
|
|
422
|
+
users = UserManager.new
|
|
423
|
+
users.loadFile 'users.yaml'
|
|
424
|
+
user_data = users.find user
|
|
425
|
+
|
|
426
|
+
raise "Your user account (#{user}) could not be found" if user_data.nil?
|
|
427
|
+
|
|
428
|
+
master_key = MasterKey.new MasterKey.hex_to_bin(user_data[:lock_box])
|
|
429
|
+
master_key.decryptWithPrivateKey File.read(private_key_file)
|
|
430
|
+
master_key
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def parse_tags(tags)
|
|
434
|
+
return [] if tags.empty?
|
|
435
|
+
tags = tags.split
|
|
436
|
+
tags.keep_if {|tag| !tag.empty?}
|
|
437
|
+
tags.map(&:to_sym)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def green(string)
|
|
441
|
+
"\e[32m#{string}\e[0m"
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def red(string)
|
|
445
|
+
"\e[31m#{string}\e[0m"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
exit run(ARGV)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'team-secrets/file_manager'
|
|
2
|
+
|
|
3
|
+
describe FileManager do
|
|
4
|
+
|
|
5
|
+
it 'can load a file' do
|
|
6
|
+
fm = FileManager.new
|
|
7
|
+
fm.loadFile do
|
|
8
|
+
<<~YAML
|
|
9
|
+
:YAML: YAML Ain't Markup Language
|
|
10
|
+
|
|
11
|
+
:List:
|
|
12
|
+
- Item 1
|
|
13
|
+
- Item 2
|
|
14
|
+
- Item 3
|
|
15
|
+
|
|
16
|
+
YAML
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
correct = {
|
|
20
|
+
YAML: 'YAML Ain\'t Markup Language',
|
|
21
|
+
List: [
|
|
22
|
+
'Item 1',
|
|
23
|
+
'Item 2',
|
|
24
|
+
'Item 3'
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
expect(fm.data).to eq(correct)
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'can export a file' do
|
|
33
|
+
fm = FileManager.new
|
|
34
|
+
fm.data = {
|
|
35
|
+
YAML: 'YAML Ain\'t Markup Language',
|
|
36
|
+
List: [
|
|
37
|
+
'Item 1',
|
|
38
|
+
'Item 2',
|
|
39
|
+
'Item 3'
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
correct = <<~YAML
|
|
44
|
+
---
|
|
45
|
+
:YAML: YAML Ain't Markup Language
|
|
46
|
+
:List:
|
|
47
|
+
- Item 1
|
|
48
|
+
- Item 2
|
|
49
|
+
- Item 3
|
|
50
|
+
YAML
|
|
51
|
+
|
|
52
|
+
fm.writeFile do |result|
|
|
53
|
+
expect(result).to eq(correct)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'team-secrets/manifest_manager'
|
|
2
|
+
require 'team-secrets/master_key'
|
|
3
|
+
|
|
4
|
+
describe ManifestManager do
|
|
5
|
+
|
|
6
|
+
context 'managing the manifest' do
|
|
7
|
+
master_key = MasterKey.generate
|
|
8
|
+
|
|
9
|
+
before(:all) do
|
|
10
|
+
Dir.chdir File.dirname(File.dirname(__FILE__))
|
|
11
|
+
Dir.mkdir 'tmp' unless File.exists? 'tmp'
|
|
12
|
+
Dir.chdir 'tmp'
|
|
13
|
+
|
|
14
|
+
File.delete 'manifest.yaml' if File.exists? 'manifest.yaml'
|
|
15
|
+
|
|
16
|
+
File.write('users.yaml', 'Sample 1')
|
|
17
|
+
File.write('secrets.yaml', 'Sample 2')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
after(:all) do
|
|
21
|
+
File.delete 'manifest.yaml' if File.exists? 'manifest.yaml'
|
|
22
|
+
File.delete 'users.yaml' if File.exists? 'users.yaml'
|
|
23
|
+
File.delete 'secrets.yaml' if File.exists? 'secrets.yaml'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'can update and write the data' do
|
|
27
|
+
|
|
28
|
+
manifest = ManifestManager.new(master_key)
|
|
29
|
+
|
|
30
|
+
expect(File.exists?('manifest.yaml')).to eq(false)
|
|
31
|
+
|
|
32
|
+
manifest.update
|
|
33
|
+
manifest.writeFile('manifest.yaml')
|
|
34
|
+
|
|
35
|
+
expect(manifest.validate).to eq(true)
|
|
36
|
+
expect(File.exists?('manifest.yaml')).to eq(true)
|
|
37
|
+
|
|
38
|
+
written = File.read('manifest.yaml')
|
|
39
|
+
data_written = YAML.load(written)
|
|
40
|
+
|
|
41
|
+
expect(data_written[:users_file]).to be
|
|
42
|
+
expect(data_written[:users_file].length).to eq(2)
|
|
43
|
+
expect(data_written[:users_file][:path]).to eq('users.yaml')
|
|
44
|
+
expect(data_written[:users_file][:signature]).to match(/\A[0-9a-f]{10,}\z/)
|
|
45
|
+
|
|
46
|
+
expect(data_written[:secrets_file]).to be
|
|
47
|
+
expect(data_written[:secrets_file].length).to eq(2)
|
|
48
|
+
expect(data_written[:secrets_file][:path]).to eq('secrets.yaml')
|
|
49
|
+
expect(data_written[:secrets_file][:signature]).to match(/\A[0-9a-f]{10,}\z/)
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'can validate the data' do
|
|
54
|
+
|
|
55
|
+
expect(File.exists?('manifest.yaml')).to eq(true)
|
|
56
|
+
|
|
57
|
+
manifest = ManifestManager.new(master_key)
|
|
58
|
+
manifest.loadFile('manifest.yaml')
|
|
59
|
+
|
|
60
|
+
expect(manifest.validate).to eq(true)
|
|
61
|
+
|
|
62
|
+
File.write('users.yaml', 'Sample 3')
|
|
63
|
+
|
|
64
|
+
expect { manifest.validate }.to raise_error(/signature does not match/)
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'team-secrets/master_key'
|
|
2
|
+
|
|
3
|
+
describe MasterKey do
|
|
4
|
+
|
|
5
|
+
context 'doing hexidecimal conversion' do
|
|
6
|
+
it 'can convert a string to hexidecimal string' do
|
|
7
|
+
res = MasterKey.bin_to_hex('Hello!')
|
|
8
|
+
expect(res).to eq('48656c6c6f21')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'can convert a hexidecimal string to binary' do
|
|
12
|
+
res = MasterKey.hex_to_bin('576f726c6421')
|
|
13
|
+
expect(res).to eq('World!')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'can go to hexidecimal and back' do
|
|
17
|
+
res = MasterKey.bin_to_hex('Hello World!!')
|
|
18
|
+
back = MasterKey.hex_to_bin(res)
|
|
19
|
+
|
|
20
|
+
expect(back).to eq('Hello World!!')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'generating AES keys' do
|
|
25
|
+
it 'can generate a new AES key' do
|
|
26
|
+
|
|
27
|
+
nk = MasterKey.generate
|
|
28
|
+
|
|
29
|
+
expect(nk).to be_a_kind_of(MasterKey)
|
|
30
|
+
expect(nk.instance_variable_get(:@decrypted).length).to eq(MasterKey::CONFIG[:key_len])
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'can generate a unique AES key' do
|
|
35
|
+
|
|
36
|
+
nk = MasterKey.generate
|
|
37
|
+
nk2 = MasterKey.generate
|
|
38
|
+
|
|
39
|
+
expect(nk.instance_variable_get(:@decrypted).length).to eq(MasterKey::CONFIG[:key_len])
|
|
40
|
+
expect(nk2.instance_variable_get(:@decrypted).length).to eq(MasterKey::CONFIG[:key_len])
|
|
41
|
+
expect(nk.instance_variable_get(:@decrypted)).to_not eq(nk2.instance_variable_get(:@decrypted))
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'encryption with key pair' do
|
|
47
|
+
it 'can encrypt a new master key with a public key' do
|
|
48
|
+
|
|
49
|
+
mk = MasterKey.generate
|
|
50
|
+
|
|
51
|
+
pub_key = File.read File.expand_path('../support/test_key.pub.pem', __FILE__)
|
|
52
|
+
mk.encryptWithPublicKey(pub_key)
|
|
53
|
+
|
|
54
|
+
expect(mk.instance_variable_get(:@encrypted).length).to be > 50
|
|
55
|
+
expect(mk.instance_variable_get(:@decrypted)).to_not eq(mk.instance_variable_get(:@encrypted))
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'can decrypt a master key with a private key' do
|
|
60
|
+
|
|
61
|
+
enc = '1f1656df3d2d4f9cd2376bc95f06d64003d0ba286699fde326df091f68ed8d2d287a4f09363c18061b124963ceeaa6136803859d9eaf296cf09011e9262efa5e3950b3cd947466115e251cb547afabb52bd0896fcf93c2796a4ce20795d2a5ea7f2eff6910cf7768f2df4a32ee6d95f8d43287e8af304be2648ebaa51f6d20572084e47b39b63a644546bf73fb28bf2610aeaaa68b9385ad90ec0aaf528019ca2e41553b3f2d722d928f930a54128d74c2a6871de3af9e09ff5e26c51a0740c289b49072c424e978e97b86e983792d54c58eb1a9e9821524d443ec6d01589c46260e09a77e6138ade975c75c0d8ec82480fe19514eab861e56dc1b2f756caef7'
|
|
62
|
+
|
|
63
|
+
mk = MasterKey.new(MasterKey.hex_to_bin(enc), true)
|
|
64
|
+
|
|
65
|
+
priv_key = File.read File.expand_path('../support/test_key', __FILE__)
|
|
66
|
+
mk.decryptWithPrivateKey(priv_key, '12345')
|
|
67
|
+
|
|
68
|
+
expect(mk.instance_variable_get(:@decrypted).length).to be(MasterKey::CONFIG[:key_len])
|
|
69
|
+
expect(mk.instance_variable_get(:@decrypted)).to_not eq(mk.instance_variable_get(:@encrypted))
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context 'encryption functionality' do
|
|
75
|
+
it 'can encrypt a string' do
|
|
76
|
+
|
|
77
|
+
mk = MasterKey.new('1234'*8, false)
|
|
78
|
+
res = mk.encryptSecret('My Secret')
|
|
79
|
+
|
|
80
|
+
expect(res).to_not eq('My Secret')
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'can not decrypt a string with the wrong key' do
|
|
85
|
+
|
|
86
|
+
mk = MasterKey.new('1234'*8, false)
|
|
87
|
+
res = mk.encryptSecret('My Secret 2')
|
|
88
|
+
|
|
89
|
+
mk2 = MasterKey.new('0000'*8, false)
|
|
90
|
+
|
|
91
|
+
expect { back = mk2.decryptSecret(res) }.to raise_error(/bad decrypt/)
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'can encrypt and decrypt a string' do
|
|
96
|
+
|
|
97
|
+
mk = MasterKey.new('1234'*8, false)
|
|
98
|
+
res = mk.encryptSecret('My Secret 3')
|
|
99
|
+
|
|
100
|
+
mk2 = MasterKey.new('1234'*8, false)
|
|
101
|
+
back = mk2.decryptSecret(res)
|
|
102
|
+
|
|
103
|
+
expect(back).to eq('My Secret 3')
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|