veil 0.2.0 → 0.3.9
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 +5 -5
- data/bin/veil-dump-secrets +12 -0
- data/bin/veil-env-helper +75 -18
- data/lib/veil/cipher/v1.rb +16 -0
- data/lib/veil/cipher/v2.rb +41 -0
- data/lib/veil/cipher.rb +32 -0
- data/lib/veil/credential_collection/base.rb +49 -17
- data/lib/veil/credential_collection/chef_secrets_env.rb +43 -0
- data/lib/veil/credential_collection/chef_secrets_fd.rb +57 -0
- data/lib/veil/credential_collection/chef_secrets_file.rb +18 -29
- data/lib/veil/credential_collection.rb +6 -0
- data/lib/veil/exceptions.rb +4 -1
- data/lib/veil/version.rb +1 -1
- data/spec/cipher/v1_spec.rb +15 -0
- data/spec/cipher/v2_spec.rb +46 -0
- data/spec/cipher_spec.rb +23 -0
- data/spec/credential_collection/base_spec.rb +4 -3
- data/spec/credential_collection/chef_secrets_env_spec.rb +68 -0
- data/spec/credential_collection/chef_secrets_fd_spec.rb +64 -0
- data/spec/credential_collection/chef_secrets_file_spec.rb +159 -35
- data/spec/credential_collection_spec.rb +9 -0
- metadata +22 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 90b6d3458208ed72b315d558278ff129d88328b61440ef8be942df67737e821c
|
4
|
+
data.tar.gz: 452932d08b0483b6153ea3e7d0eace13acbdcf41b53ed0fecb5ea97aef518dc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16deb81b28bb2dbd252cbd05269f2488296e24689786c037043a2b5ce7d3af0cef583ffe912fdfd22b70c5d9ef4b22acdfa4ee57bdf90865438cfbef2ab35f5e
|
7
|
+
data.tar.gz: 7a20341b3e3061f9907d419fbb37f94b4b0690786b1d90dc943865ce235e620619fef290a315e769d96e6b4ab132b5be2620e970faa41d1f7b6d26bf60e42f73
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'veil'
|
3
|
+
require 'json'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = "Usage: veil-dump-secrets SECRETS_FILE_PATH"
|
8
|
+
end.parse!
|
9
|
+
|
10
|
+
secrets_file = ARGV[0]
|
11
|
+
veil = Veil::CredentialCollection::ChefSecretsFile.from_file(secrets_file)
|
12
|
+
puts veil.credentials_for_export.to_json
|
data/bin/veil-env-helper
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'veil'
|
4
|
+
require 'json'
|
4
5
|
require 'optparse'
|
5
6
|
|
6
7
|
options = {
|
7
|
-
secrets_file: "/etc/opscode/private-chef-secrets.json"
|
8
|
+
secrets_file: "/etc/opscode/private-chef-secrets.json",
|
9
|
+
pack: false,
|
10
|
+
use_file: false,
|
11
|
+
debug: false,
|
12
|
+
secrets: [],
|
13
|
+
optional_secrets: []
|
8
14
|
}
|
9
15
|
|
10
16
|
OptionParser.new do |opts|
|
@@ -14,8 +20,20 @@ OptionParser.new do |opts|
|
|
14
20
|
options[:debug] = d
|
15
21
|
end
|
16
22
|
|
17
|
-
opts.on("
|
18
|
-
options[:
|
23
|
+
opts.on("--pack", "Pass secrets in a single CHEF_SECRETS_DATA environment variable") do |p|
|
24
|
+
options[:pack] = p
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("--use-file", "Pass secrets via a unlinked file available at the FD specified in CHEF_SECRETS_FD") do |fd|
|
28
|
+
options[:use_file] = fd
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-s SECRET_SPEC", "--secret SECRET_SPEC", "Secret to put in the environment") do |spec|
|
32
|
+
options[:secrets] << spec
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-o SECRET_SPEC", "--optional-secret SECRET_SPEC", "Optional secrets to put in the environment (if it exists)") do |spec|
|
36
|
+
options[:optional_secrets] << spec
|
19
37
|
end
|
20
38
|
|
21
39
|
opts.on("-f SECRETS_FILE", "--secrets-file SECRETS_FILE", "Location of veil-managed secrets file. Default: /etc/opscode/private-chef-secrets.json") do |file|
|
@@ -23,25 +41,64 @@ OptionParser.new do |opts|
|
|
23
41
|
end
|
24
42
|
end.parse!
|
25
43
|
|
26
|
-
def
|
44
|
+
def from_secret_spec(secret, start = {})
|
27
45
|
parts = secret.split("=")
|
28
|
-
case parts.length
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
46
|
+
env_name, secret_name = case parts.length
|
47
|
+
when 1
|
48
|
+
["CHEF_SECRET_#{parts[0].upcase}", parts[0]]
|
49
|
+
when 2
|
50
|
+
[parts[0].upcase, parts[1]]
|
51
|
+
else
|
52
|
+
raise "Bad secret spec: #{secret}"
|
53
|
+
end
|
54
|
+
|
55
|
+
start.merge({ args: secret_name.split("."),
|
56
|
+
env_name: env_name,
|
57
|
+
name: secret_name })
|
58
|
+
end
|
59
|
+
|
60
|
+
veil = Veil::CredentialCollection::ChefSecretsFile.from_file(options[:secrets_file])
|
61
|
+
packed_data = Hash.new()
|
62
|
+
|
63
|
+
secrets = options[:secrets].map { |spec| from_secret_spec(spec) }
|
64
|
+
secrets += options[:optional_secrets].map { |spec| from_secret_spec(spec, { optional: true }) }
|
65
|
+
|
66
|
+
secrets.each do |secret|
|
67
|
+
veil_args = secret[:args]
|
68
|
+
|
69
|
+
begin
|
70
|
+
secret_value = veil.get(*veil_args)
|
71
|
+
rescue
|
72
|
+
raise unless secret[:optional]
|
73
|
+
next
|
74
|
+
end
|
75
|
+
|
76
|
+
if options[:pack] || options[:use_file]
|
77
|
+
STDERR.puts "Packing data using #{veil_args.inspect} and #{secret_value}" if options[:debug]
|
78
|
+
if veil_args.length == 2
|
79
|
+
packed_data[veil_args[0]] ||= {}
|
80
|
+
packed_data[veil_args[0]][veil_args[1]] = secret_value
|
81
|
+
elsif veil_args.length == 1
|
82
|
+
packed_data[veil_args[0]] = secret_value
|
83
|
+
elsif !secret[:optional]
|
84
|
+
raise "Invalid secrets name: #{secret[:name]}"
|
85
|
+
end
|
33
86
|
else
|
34
|
-
|
87
|
+
STDERR.puts "Setting #{secret[:env_name]}=#{secret_value}" if options[:debug]
|
88
|
+
ENV[secret[:env_name]] = secret_value
|
35
89
|
end
|
36
90
|
end
|
37
91
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
92
|
+
if options[:pack] && !options[:use_file]
|
93
|
+
ENV['CHEF_SECRETS_DATA'] = packed_data.to_json
|
94
|
+
end
|
95
|
+
|
96
|
+
if options[:use_file]
|
97
|
+
rd, wd = IO.pipe
|
98
|
+
wd.puts packed_data.to_json
|
99
|
+
wd.close
|
100
|
+
rd.close_on_exec = false
|
101
|
+
ENV['CHEF_SECRETS_FD'] = rd.to_i.to_s
|
45
102
|
end
|
46
103
|
|
47
|
-
exec(*ARGV)
|
104
|
+
exec(*ARGV, close_others: false)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "openssl"
|
3
|
+
|
4
|
+
module Veil
|
5
|
+
class Cipher
|
6
|
+
class V2
|
7
|
+
attr_reader :key, :iv, :cipher
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
@cipher = OpenSSL::Cipher.new("aes-256-cbc")
|
11
|
+
@key = opts[:key] ? Base64.strict_decode64(opts[:key]) : cipher.random_key
|
12
|
+
@iv = opts[:iv] ? Base64.strict_decode64(opts[:iv]) : cipher.random_iv
|
13
|
+
end
|
14
|
+
|
15
|
+
def encrypt(plaintext)
|
16
|
+
c = cipher
|
17
|
+
c.encrypt
|
18
|
+
c.key = key
|
19
|
+
c.iv = iv
|
20
|
+
Base64.strict_encode64(c.update(plaintext) + c.final)
|
21
|
+
end
|
22
|
+
|
23
|
+
def decrypt(ciphertext)
|
24
|
+
c = cipher
|
25
|
+
c.decrypt
|
26
|
+
c.key = key
|
27
|
+
c.iv = iv
|
28
|
+
JSON.parse(c.update(Base64.strict_decode64(ciphertext)) + c.final, symbolize_names: true)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
{
|
33
|
+
type: self.class.name,
|
34
|
+
key: Base64.strict_encode64(key),
|
35
|
+
iv: Base64.strict_encode64(iv)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
alias_method :to_h, :to_hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/veil/cipher.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "veil/cipher/v1"
|
2
|
+
require "veil/cipher/v2"
|
3
|
+
|
4
|
+
module Veil
|
5
|
+
class Cipher
|
6
|
+
DEFAULT_DECRYPTOR = Veil::Cipher::V1
|
7
|
+
DEFAULT_ENCRYPTOR = Veil::Cipher::V2
|
8
|
+
|
9
|
+
class << self
|
10
|
+
#
|
11
|
+
# Create a new Cipher instance
|
12
|
+
#
|
13
|
+
# Defaults to using v1 for decryption (noop), v2 for encryption.
|
14
|
+
# If invoked as default, v2 will generate key and iv.
|
15
|
+
#
|
16
|
+
# @param opts Hash<Symbol> a hash of options to pass to the constructor
|
17
|
+
#
|
18
|
+
# @example Veil::Cipher.create(type: "V1")
|
19
|
+
# @example Veil::Cipher.create(type: "V2", key: "blah", iv: "vi")
|
20
|
+
#
|
21
|
+
def create(opts = {})
|
22
|
+
case opts
|
23
|
+
when {}, nil
|
24
|
+
[ DEFAULT_DECRYPTOR.new({}), DEFAULT_ENCRYPTOR.new({}) ]
|
25
|
+
else
|
26
|
+
cipher = const_get(opts[:type])
|
27
|
+
[ cipher.new(opts), cipher.new(opts) ]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
require "veil/
|
1
|
+
require "veil/cipher"
|
2
2
|
require "veil/credential"
|
3
|
+
require "veil/hasher"
|
3
4
|
require "forwardable"
|
4
5
|
|
5
6
|
module Veil
|
@@ -13,13 +14,14 @@ module Veil
|
|
13
14
|
|
14
15
|
extend Forwardable
|
15
16
|
|
16
|
-
attr_reader :credentials, :hasher, :version
|
17
|
+
attr_reader :credentials, :hasher, :version, :decryptor, :encryptor
|
17
18
|
|
18
19
|
def_delegators :@credentials, :size, :length, :find, :map, :select, :each, :[], :keys
|
19
20
|
|
20
21
|
def initialize(opts = {})
|
21
22
|
@hasher = Veil::Hasher.create(opts[:hasher] || {})
|
22
|
-
@
|
23
|
+
@decryptor, @encryptor = Veil::Cipher.create(opts[:cipher] || {})
|
24
|
+
@credentials = expand_credentials_hash(decryptor.decrypt(opts[:credentials]) || {})
|
23
25
|
@version = opts[:version] || 1
|
24
26
|
end
|
25
27
|
|
@@ -28,7 +30,8 @@ module Veil
|
|
28
30
|
type: self.class.name,
|
29
31
|
version: version,
|
30
32
|
hasher: hasher.to_h,
|
31
|
-
|
33
|
+
cipher: encryptor.to_h,
|
34
|
+
credentials: encryptor.encrypt(credentials_as_hash.to_json)
|
32
35
|
}
|
33
36
|
end
|
34
37
|
alias_method :to_h, :to_hash
|
@@ -173,6 +176,39 @@ module Veil
|
|
173
176
|
end
|
174
177
|
end
|
175
178
|
|
179
|
+
def credentials_as_hash
|
180
|
+
hash = Hash.new
|
181
|
+
|
182
|
+
credentials.each do |cred_or_group_name, cred_or_group_attrs|
|
183
|
+
if cred_or_group_attrs.is_a?(Hash)
|
184
|
+
cred_or_group_attrs.each do |name, cred|
|
185
|
+
hash[cred_or_group_name] ||= Hash.new
|
186
|
+
hash[cred_or_group_name][name] = cred.to_hash
|
187
|
+
end
|
188
|
+
else
|
189
|
+
hash[cred_or_group_name] = cred_or_group_attrs.to_hash
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
hash
|
194
|
+
end
|
195
|
+
|
196
|
+
def credentials_for_export
|
197
|
+
hash = Hash.new
|
198
|
+
|
199
|
+
credentials.each do |namespace, cred_or_creds|
|
200
|
+
if cred_or_creds.is_a?(Veil::Credential)
|
201
|
+
hash[namespace] = cred_or_creds.value
|
202
|
+
else
|
203
|
+
hash[namespace] = {}
|
204
|
+
cred_or_creds.each { |name, cred| hash[namespace][name] = cred.value }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
hash
|
209
|
+
end
|
210
|
+
alias_method :legacy_credentials_hash, :credentials_for_export
|
211
|
+
|
176
212
|
private
|
177
213
|
|
178
214
|
def add_from_params(params)
|
@@ -219,21 +255,17 @@ module Veil
|
|
219
255
|
expanded
|
220
256
|
end
|
221
257
|
|
222
|
-
def
|
223
|
-
hash
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
else
|
232
|
-
hash[cred_or_group_name] = cred_or_group_attrs.to_hash
|
258
|
+
def import_credentials_hash(hash)
|
259
|
+
hash.each do |namespace, creds_hash|
|
260
|
+
credentials[namespace.to_s] ||= Hash.new
|
261
|
+
creds_hash.each do |cred, value|
|
262
|
+
credentials[namespace.to_s][cred.to_s] = Veil::Credential.new(
|
263
|
+
name: cred.to_s,
|
264
|
+
value: value,
|
265
|
+
length: value.length
|
266
|
+
)
|
233
267
|
end
|
234
268
|
end
|
235
|
-
|
236
|
-
hash
|
237
269
|
end
|
238
270
|
end
|
239
271
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "veil/credential_collection/base"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Veil
|
5
|
+
class CredentialCollection
|
6
|
+
class ChefSecretsEnv < Base
|
7
|
+
|
8
|
+
# Create a new ChefSecretsEnv
|
9
|
+
#
|
10
|
+
# @param [Hash] opts
|
11
|
+
# a hash of options to pass to the constructor
|
12
|
+
def initialize(opts = {})
|
13
|
+
var_name = opts[:var_name] || 'CHEF_SECRETS_DATA'
|
14
|
+
|
15
|
+
@credentials = {}
|
16
|
+
import_credentials_hash(inflate_secrets_from_environment(var_name))
|
17
|
+
end
|
18
|
+
|
19
|
+
# Unsupported methods
|
20
|
+
def rotate
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
alias_method :rotate_credentials, :rotate
|
24
|
+
alias_method :save, :rotate
|
25
|
+
|
26
|
+
def inflate_secrets_from_environment(var_name)
|
27
|
+
value = ENV[var_name]
|
28
|
+
unless value
|
29
|
+
msg = "Env var #{var_name} has not been set. This should by done by "\
|
30
|
+
"launching this application via veil-env-wrapper."
|
31
|
+
raise InvalidCredentialCollectionEnv.new(msg)
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
JSON.parse(value)
|
36
|
+
rescue JSON::ParserError => e
|
37
|
+
msg = "Env var #{var_name} could not be parsed: #{e.message}"
|
38
|
+
raise InvalidCredentialCollectionEnv.new(msg)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "veil/exceptions"
|
2
|
+
require "veil/credential_collection/base"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Veil
|
6
|
+
class CredentialCollection
|
7
|
+
class ChefSecretsFd < Base
|
8
|
+
|
9
|
+
# Create a new ChefSecretsFd
|
10
|
+
#
|
11
|
+
# @param [Hash] opts
|
12
|
+
# ignored
|
13
|
+
def initialize(opts = {})
|
14
|
+
@credentials = {}
|
15
|
+
import_credentials_hash(inflate_secrets_from_fd)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Unsupported methods
|
19
|
+
def rotate
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
alias_method :rotate_credentials, :rotate
|
23
|
+
alias_method :save, :rotate
|
24
|
+
|
25
|
+
def inflate_secrets_from_fd
|
26
|
+
if ENV['CHEF_SECRETS_FD'].nil?
|
27
|
+
raise InvalidCredentialCollectionFd.new("CHEF_SECRETS_FD not found in environment")
|
28
|
+
end
|
29
|
+
|
30
|
+
fd = ENV['CHEF_SECRETS_FD'].to_i
|
31
|
+
value = nil
|
32
|
+
|
33
|
+
begin
|
34
|
+
file = IO.new(fd, "r")
|
35
|
+
value = file.gets
|
36
|
+
rescue StandardError => e
|
37
|
+
msg = "A problem occured trying to read passed file descriptor: #{e}"
|
38
|
+
raise InvalidCredentialCollectionFd.new(msg)
|
39
|
+
ensure
|
40
|
+
file.close if file
|
41
|
+
end
|
42
|
+
|
43
|
+
if !value
|
44
|
+
msg = "File at CHEF_SECRETS_FD (#{fd}) did not contain any data!"
|
45
|
+
raise InvalidCredentialCollectionFd.new(msg)
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
JSON.parse(value)
|
50
|
+
rescue JSON::ParserError => e
|
51
|
+
msg = "Chef secrets data could not be parsed: #{e.message}"
|
52
|
+
raise InvalidCredentialCollectionFd.new(msg)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -16,7 +16,9 @@ module Veil
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
CURRENT_VERSION = 2.freeze
|
20
|
+
|
21
|
+
attr_reader :path, :user, :group, :key
|
20
22
|
|
21
23
|
# Create a new ChefSecretsFile
|
22
24
|
#
|
@@ -43,10 +45,10 @@ module Veil
|
|
43
45
|
|
44
46
|
@user = opts[:user]
|
45
47
|
@group = opts[:group] || @user
|
46
|
-
|
48
|
+
opts[:version] = CURRENT_VERSION
|
47
49
|
super(opts)
|
48
50
|
|
49
|
-
|
51
|
+
import_credentials_hash(hash) if import_existing && legacy
|
50
52
|
end
|
51
53
|
|
52
54
|
# Set the secrets file path
|
@@ -57,11 +59,17 @@ module Veil
|
|
57
59
|
@path = File.expand_path(path)
|
58
60
|
end
|
59
61
|
|
60
|
-
# Save the CredentialCollection to file
|
62
|
+
# Save the CredentialCollection to file, encrypt it
|
61
63
|
def save
|
62
|
-
FileUtils.mkdir_p(File.dirname(path))
|
64
|
+
FileUtils.mkdir_p(File.dirname(path))
|
63
65
|
|
64
66
|
f = Tempfile.new("veil") # defaults to mode 0600
|
67
|
+
|
68
|
+
if existing
|
69
|
+
@user ||= existing.uid
|
70
|
+
@group ||= existing.gid
|
71
|
+
end
|
72
|
+
|
65
73
|
FileUtils.chown(user, group, f.path) if user
|
66
74
|
f.puts(JSON.pretty_generate(secrets_hash))
|
67
75
|
f.flush
|
@@ -73,32 +81,13 @@ module Veil
|
|
73
81
|
|
74
82
|
# Return the instance as a secrets style hash
|
75
83
|
def secrets_hash
|
76
|
-
{ "veil" => to_h }
|
84
|
+
{ "veil" => to_h }
|
77
85
|
end
|
78
86
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
to_h[:credentials].each do |namespace, creds|
|
84
|
-
hash[namespace] = {}
|
85
|
-
creds.each { |name, cred| hash[namespace][name] = cred[:value] }
|
86
|
-
end
|
87
|
-
|
88
|
-
hash
|
89
|
-
end
|
90
|
-
|
91
|
-
def import_legacy_credentials(hash)
|
92
|
-
hash.each do |namespace, creds_hash|
|
93
|
-
credentials[namespace.to_s] ||= Hash.new
|
94
|
-
creds_hash.each do |cred, value|
|
95
|
-
credentials[namespace.to_s][cred.to_s] = Veil::Credential.new(
|
96
|
-
name: cred.to_s,
|
97
|
-
value: value,
|
98
|
-
length: value.length
|
99
|
-
)
|
100
|
-
end
|
101
|
-
end
|
87
|
+
def existing
|
88
|
+
@existing ||= File.stat(path)
|
89
|
+
rescue Errno::ENOENT
|
90
|
+
nil
|
102
91
|
end
|
103
92
|
end
|
104
93
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require "veil/credential_collection/base"
|
2
|
+
require "veil/credential_collection/chef_secrets_fd"
|
2
3
|
require "veil/credential_collection/chef_secrets_file"
|
4
|
+
require "veil/credential_collection/chef_secrets_env"
|
3
5
|
|
4
6
|
module Veil
|
5
7
|
class CredentialCollection
|
@@ -8,6 +10,10 @@ module Veil
|
|
8
10
|
klass = case opts[:provider]
|
9
11
|
when 'chef-secrets-file'
|
10
12
|
ChefSecretsFile
|
13
|
+
when 'chef-secrets-env'
|
14
|
+
ChefSecretsEnv
|
15
|
+
when 'chef-secrets-fd'
|
16
|
+
ChefSecretsFd
|
11
17
|
else
|
12
18
|
raise UnknownProvider, "Unknown provider: #{opts[:provider]}"
|
13
19
|
end
|
data/lib/veil/exceptions.rb
CHANGED
@@ -3,7 +3,10 @@ module Veil
|
|
3
3
|
class InvalidSecret < StandardError; end
|
4
4
|
class InvalidParameter < StandardError; end
|
5
5
|
class InvalidHasher < StandardError; end
|
6
|
-
class
|
6
|
+
class InvalidCredentialCollection < StandardError; end
|
7
|
+
class InvalidCredentialCollectionFile < InvalidCredentialCollection; end
|
8
|
+
class InvalidCredentialCollectionEnv < InvalidCredentialCollection; end
|
9
|
+
class InvalidCredentialCollectionFd < InvalidCredentialCollection; end
|
7
10
|
class MissingParameter < StandardError; end
|
8
11
|
class NotImplmented < StandardError; end
|
9
12
|
class InvalidCredentialHash < StandardError; end
|
data/lib/veil/version.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Veil::Cipher::V1 do
|
4
|
+
describe "#decrypt" do
|
5
|
+
it "does nothing" do
|
6
|
+
expect(described_class.new().decrypt("hi")).to eq("hi")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#encrypt" do
|
11
|
+
it "raises a RuntimeError" do
|
12
|
+
expect { described_class.new().encrypt("hi") }.to raise_error(RuntimeError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "openssl"
|
3
|
+
|
4
|
+
describe Veil::Cipher::V2 do
|
5
|
+
let(:iv64) { Base64.strict_encode64("mondaytue16bytes") }
|
6
|
+
let(:key64) { Base64.strict_encode64("thursdayfridaysaturdaysun32bytes") }
|
7
|
+
let(:ciphertext64) { "qS6SRmgOgSta2jSdD60sJmu83cRgy4DUJ2nKZwStrqs=" }
|
8
|
+
let(:plainhash) { { "chef-server": "test-value" } }
|
9
|
+
|
10
|
+
describe "#new" do
|
11
|
+
it "accepts passed key and iv base64-encoded data" do
|
12
|
+
cipher = described_class.new(iv: iv64, key: key64)
|
13
|
+
|
14
|
+
expect(cipher.iv).to eq("mondaytue16bytes")
|
15
|
+
expect(cipher.key).to eq("thursdayfridaysaturdaysun32bytes")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "generates key and iv data if none was passed" do
|
19
|
+
openssl = double(OpenSSL::Cipher)
|
20
|
+
expect(OpenSSL::Cipher).to receive(:new).and_return(openssl)
|
21
|
+
expect(openssl).to receive(:random_iv).and_return("random iv")
|
22
|
+
expect(openssl).to receive(:random_key).and_return("random key")
|
23
|
+
cipher = described_class.new()
|
24
|
+
|
25
|
+
expect(cipher.iv).to eq("random iv")
|
26
|
+
expect(cipher.key).to eq("random key")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#decrypt" do
|
31
|
+
it "parses decrypted json data" do
|
32
|
+
expect(described_class.new(iv: iv64, key: key64).decrypt(ciphertext64)).to eq(plainhash)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#to_hash" do
|
37
|
+
it "base64-encodes iv and key" do
|
38
|
+
expected = {
|
39
|
+
iv: "bW9uZGF5dHVlMTZieXRlcw==",
|
40
|
+
key: "dGh1cnNkYXlmcmlkYXlzYXR1cmRheXN1bjMyYnl0ZXM=",
|
41
|
+
type: "Veil::Cipher::V2"
|
42
|
+
}
|
43
|
+
expect(described_class.new(iv: iv64, key: key64).to_hash).to eq(expected)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/cipher_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Veil::Cipher do
|
4
|
+
describe "#self.create" do
|
5
|
+
it "defaults to noop decrypt, v2 encrypt" do
|
6
|
+
dec, enc = described_class.create()
|
7
|
+
expect(dec.class).to eq(Veil::Cipher::V1)
|
8
|
+
expect(enc.class).to eq(Veil::Cipher::V2)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses the non-namespaced type" do
|
12
|
+
dec, enc = described_class.create(type: "V2")
|
13
|
+
expect(dec.class).to eq(Veil::Cipher::V2)
|
14
|
+
expect(enc.class).to eq(Veil::Cipher::V2)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "parses the complete type" do
|
18
|
+
dec, enc = described_class.create(type: "Veil::Cipher::V2")
|
19
|
+
expect(dec.class).to eq(Veil::Cipher::V2)
|
20
|
+
expect(enc.class).to eq(Veil::Cipher::V2)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -25,10 +25,11 @@ describe Veil::CredentialCollection::Base do
|
|
25
25
|
context "with credential options" do
|
26
26
|
it "builds the credentials" do
|
27
27
|
subject.add("foo", "bar", length: 22)
|
28
|
-
creds_hash = subject.to_hash
|
28
|
+
creds_hash = subject["foo"]["bar"].to_hash
|
29
|
+
new_instance = described_class.new(credentials: { foo: { bar: creds_hash } })
|
29
30
|
|
30
|
-
|
31
|
-
expect(new_instance["foo"]["bar"].value).to eq(
|
31
|
+
expected = subject["foo"]["bar"].value
|
32
|
+
expect(new_instance["foo"]["bar"].value).to eq(expected)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Veil::CredentialCollection::ChefSecretsEnv do
|
4
|
+
describe "#new" do
|
5
|
+
context "env variable is set" do
|
6
|
+
let(:var_name) { "CHEF_SECRETS_DATA" }
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
ENV[var_name] = '{ "secret_service": { "secret_name": "secret_value" } }'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'reads the secret from the env var CHEF_SECRETS_DATA' do
|
13
|
+
expect(subject.get("secret_service", "secret_name")).to eq("secret_value")
|
14
|
+
end
|
15
|
+
|
16
|
+
context "env variable name is passed" do
|
17
|
+
let(:var_name) { "CHEF_SECRETS_DATA_2" }
|
18
|
+
let(:subject) { described_class.new(var_name: var_name) }
|
19
|
+
|
20
|
+
it 'reads the secret from the passed env var name' do
|
21
|
+
expect(subject.get("secret_service", "secret_name")).to eq("secret_value")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "env var content cannot be parsed" do
|
26
|
+
before(:each) do
|
27
|
+
ENV[var_name] = '{ "secre '
|
28
|
+
end
|
29
|
+
|
30
|
+
it "re-raises the JSON parse error" do
|
31
|
+
expect{ subject.get("secret_service", "secret_name") }.to raise_error(Veil::InvalidCredentialCollectionEnv)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "env variable is not set" do
|
37
|
+
let(:var_name) { "CHEF_SECRETS_DATA" }
|
38
|
+
|
39
|
+
before(:each) do
|
40
|
+
ENV.delete(var_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises an exception' do
|
44
|
+
expect{ described_class.new }.to raise_error(Veil::InvalidCredentialCollectionEnv)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "unsupported methods" do
|
49
|
+
let(:var_name) { "CHEF_SECRETS_DATA" }
|
50
|
+
|
51
|
+
before(:each) do
|
52
|
+
ENV[var_name] = '{}'
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not support #rotate' do
|
56
|
+
expect{ subject.rotate }.to raise_error(NotImplementedError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'does not support #save' do
|
60
|
+
expect{ subject.save }.to raise_error(NotImplementedError)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'does not support #rotate_hasher' do
|
64
|
+
expect{ subject.rotate_hasher }.to raise_error(NotImplementedError)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Veil::CredentialCollection::ChefSecretsFd do
|
4
|
+
describe "#new" do
|
5
|
+
let(:content) { '{ "secret_service": { "secret_name": "secret_value" } }' }
|
6
|
+
let(:content_file) {
|
7
|
+
rd, wr = IO.pipe
|
8
|
+
wr.puts content
|
9
|
+
wr.close
|
10
|
+
rd
|
11
|
+
}
|
12
|
+
|
13
|
+
context "env variable is set" do
|
14
|
+
before(:each) do
|
15
|
+
ENV['CHEF_SECRETS_FD'] = content_file.to_i.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'reads the secret from the passed file descriptor' do
|
19
|
+
expect(subject.get("secret_service", "secret_name")).to eq("secret_value")
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO(ssd) 2017-03-22: We wanted a test for closing the FD, but
|
23
|
+
# didn't want to fight with ruby today.
|
24
|
+
|
25
|
+
context "env var content cannot be parsed" do
|
26
|
+
let(:content) { '{ "secre ' }
|
27
|
+
|
28
|
+
it "re-raises the JSON parse error" do
|
29
|
+
expect{ subject.get("secret_service", "secret_name") }.to raise_error(Veil::InvalidCredentialCollectionFd)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "CHEF_SECRETS_FD is not set" do
|
35
|
+
before(:each) do
|
36
|
+
ENV.delete('CHEF_SECRETS_FD')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'raises an exception' do
|
40
|
+
expect{ described_class.new }.to raise_error(Veil::InvalidCredentialCollectionFd)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "unsupported methods" do
|
45
|
+
let(:content) { '{}' }
|
46
|
+
|
47
|
+
before(:each) do
|
48
|
+
ENV['CHEF_SECRETS_FD'] = content_file.to_i.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does not support #rotate' do
|
52
|
+
expect{ subject.rotate }.to raise_error(NotImplementedError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not support #save' do
|
56
|
+
expect{ subject.save }.to raise_error(NotImplementedError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'does not support #rotate_hasher' do
|
60
|
+
expect{ subject.rotate_hasher }.to raise_error(NotImplementedError)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -7,10 +7,12 @@ describe Veil::CredentialCollection::ChefSecretsFile do
|
|
7
7
|
let!(:file) { Tempfile.new("private_chef_secrets.json") }
|
8
8
|
let(:user) { "opscode_user" }
|
9
9
|
let(:group) { "opscode_group" }
|
10
|
+
let(:version) { 1 }
|
10
11
|
let(:content) do
|
11
12
|
{
|
12
13
|
"veil" => {
|
13
14
|
"type" => "Veil::CredentialCollection::ChefSecretsFile",
|
15
|
+
"version" => version,
|
14
16
|
"hasher" => {},
|
15
17
|
"credentials" => {}
|
16
18
|
}
|
@@ -66,7 +68,7 @@ describe Veil::CredentialCollection::ChefSecretsFile do
|
|
66
68
|
end
|
67
69
|
|
68
70
|
describe "#save" do
|
69
|
-
it "saves the content to a
|
71
|
+
it "saves the content to a file it can read" do
|
70
72
|
file.rewind
|
71
73
|
creds = described_class.new(path: file.path)
|
72
74
|
creds.add("redis_lb", "password")
|
@@ -80,6 +82,14 @@ describe Veil::CredentialCollection::ChefSecretsFile do
|
|
80
82
|
expect(new_creds["postgresql"]["sql_ro_password"].value).to eq(creds["postgresql"]["sql_ro_password"].value)
|
81
83
|
end
|
82
84
|
|
85
|
+
it "saves the content in an encrypted form" do
|
86
|
+
creds = described_class.new(path: file.path)
|
87
|
+
creds.add("postgresql", "sql_ro_password", value: "kneipenpathos")
|
88
|
+
creds.save
|
89
|
+
|
90
|
+
expect(IO.read(file.path)).to_not match(/kneipenpathos/)
|
91
|
+
end
|
92
|
+
|
83
93
|
context "when using ownership management" do
|
84
94
|
let(:tmpfile) do
|
85
95
|
s = StringIO.new
|
@@ -87,56 +97,170 @@ describe Veil::CredentialCollection::ChefSecretsFile do
|
|
87
97
|
s
|
88
98
|
end
|
89
99
|
|
90
|
-
context "when the
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
100
|
+
context "when the target file does not exist" do
|
101
|
+
|
102
|
+
let(:secrets_file) { double(File) }
|
103
|
+
before(:each) do
|
104
|
+
allow(File).to receive(:stat).with(file.path).and_raise(Errno::ENOENT)
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when the user is not set" do
|
108
|
+
it "does not change any permissions" do
|
109
|
+
expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
|
110
|
+
expect(FileUtils).not_to receive(:chown)
|
111
|
+
expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
|
112
|
+
|
113
|
+
creds = described_class.new(path: file.path)
|
114
|
+
creds.add("redis_lb", "password")
|
115
|
+
creds.save
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when the user is set" do
|
120
|
+
it "gives the file proper permissions" do
|
121
|
+
expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
|
122
|
+
expect(FileUtils).to receive(:chown).with(user, user, "/tmp/unguessable")
|
123
|
+
expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
|
95
124
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
125
|
+
creds = described_class.new(path: file.path,
|
126
|
+
user: user)
|
127
|
+
creds.add("redis_lb", "password")
|
128
|
+
creds.save
|
129
|
+
end
|
100
130
|
end
|
101
131
|
end
|
102
132
|
|
103
|
-
context "when
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
133
|
+
context "when the target file exists" do
|
134
|
+
|
135
|
+
let(:secrets_file) { double(File) }
|
136
|
+
let(:secrets_file_stat) { double(File, uid: 100, gid: 1000) }
|
137
|
+
before(:each) do
|
138
|
+
allow(File).to receive(:stat).with(file.path).and_return(secrets_file_stat)
|
139
|
+
end
|
140
|
+
|
141
|
+
context "when the user is not set" do
|
142
|
+
it "keeps the existing file permissions" do
|
143
|
+
expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
|
144
|
+
expect(FileUtils).to receive(:chown).with(100, 1000, "/tmp/unguessable")
|
145
|
+
expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
|
146
|
+
|
147
|
+
creds = described_class.new(path: file.path)
|
148
|
+
creds.add("redis_lb", "password")
|
149
|
+
creds.save
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "when the user is set" do
|
154
|
+
it "gives the file proper permissions" do
|
155
|
+
expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
|
156
|
+
expect(FileUtils).to receive(:chown).with(user, user, "/tmp/unguessable")
|
157
|
+
expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
|
158
|
+
|
159
|
+
creds = described_class.new(path: file.path,
|
160
|
+
user: user)
|
161
|
+
creds.add("redis_lb", "password")
|
162
|
+
creds.save
|
163
|
+
end
|
114
164
|
end
|
115
165
|
|
116
|
-
|
117
|
-
file
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
166
|
+
context "when user and group are set" do
|
167
|
+
it "gives the file proper permissions" do
|
168
|
+
expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
|
169
|
+
expect(FileUtils).to receive(:chown).with(user, group, "/tmp/unguessable")
|
170
|
+
expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
|
171
|
+
|
172
|
+
creds = described_class.new(path: file.path,
|
173
|
+
user: user,
|
174
|
+
group: group)
|
175
|
+
creds.add("redis_lb", "password")
|
176
|
+
creds.save
|
177
|
+
end
|
178
|
+
|
179
|
+
it "gives the file proper permission even when called from_file" do
|
180
|
+
file.puts("{}"); file.rewind
|
181
|
+
expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
|
182
|
+
expect(FileUtils).to receive(:chown).with(user, group, "/tmp/unguessable")
|
183
|
+
expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
|
184
|
+
|
185
|
+
creds = described_class.from_file(file.path,
|
186
|
+
user: user,
|
187
|
+
group: group)
|
188
|
+
creds.add("redis_lb", "password")
|
189
|
+
creds.save
|
190
|
+
end
|
127
191
|
end
|
128
192
|
end
|
129
193
|
end
|
130
194
|
|
131
|
-
it "saves the version number" do
|
195
|
+
it "saves the latest version number" do
|
132
196
|
allow(FileUtils).to receive(:chown)
|
133
197
|
file.rewind
|
134
|
-
creds = described_class.new(path: file.path
|
198
|
+
creds = described_class.new(path: file.path)
|
135
199
|
creds.save
|
136
200
|
|
137
201
|
file.rewind
|
138
202
|
new_creds = described_class.from_file(file.path)
|
139
|
-
expect(new_creds.version).to eq(
|
203
|
+
expect(new_creds.version).to eq(2)
|
204
|
+
end
|
205
|
+
|
206
|
+
context "reading an unencrypted file" do
|
207
|
+
let(:existing_content) { <<EOF
|
208
|
+
{
|
209
|
+
"veil": {
|
210
|
+
"type": "Veil::CredentialCollection::ChefSecretsFile",
|
211
|
+
"version": 1,
|
212
|
+
"hasher": {
|
213
|
+
"type": "Veil::Hasher::PBKDF2",
|
214
|
+
"secret": "5f6e76ae7f5b631a96f5142b2a4b837e23ab6d204dc9e635fc18783ae8d253596f9cd9b65e295c96cee0b9cd1171cae1ca2e897ca5419106785d99d4a42fe9897f2fc7f537a05d39f19b26aa05500e93d65621ed3cdbb718d3005f808fe04a2e2267c43f5100dfe1a6ab3b6462d7fe57bc3adced263fd8c2c55ddebc2f9067b475868e9fb950bf2ae37889e22c42fd686b168b25410a4fa7689a32686fab76e57cd64482ff411be69a4c9abefc5ed64227fb42db05f3b221ddc18c6bb7ca54625cd8a5426be0d9c2161f09f35a04bd9b07884f4319389801c123d0bd345d4218434d9aee87dbb115c5cc22d1c87ffc447c2a008e89edda7e25453076dd478df0f08d50206724cd055741b7521b889fbf6d12af7946ee069bdb60cf79b14e2dad910bab97ae0ff6a9c1f88556e493df26bff007728317683a14433b30a6b6537095c1a8906a08d8a067a1b2a0443ad6fcf007e6bd70c8a3bbfe571044652d3960fd200d23f046b2493463692b570f1c94dabb05933e8d6bcd7e59a708ac8b385034a32b5b1f20635cb10e8027376f496ee1e8496c3517e24bfab3a840f0994a7fde433dc942be781488e6ebbbc68872467ebd216c71e092b2adae600a50bc2ace312cc8cd7949ddb16b72555c9d511211e22383eb515dad22032e80c11f3193a1b357f1c073e4770e314a960bbeada64b36078cfc18e806c03743a0dcd1f77ef3",
|
215
|
+
"salt": "78a8140351ecbfaee137655377aa15138655dc65a49c53ca5f6ee05c7b9c3db9dbc0e2bfdf14af3a062a1cc513e4e086126cf428890df3caf11d9714729027ac93fbf8b7bd9d8fc734b7b4a84c979b45e804e573f93f5cd6dab0b1cf7e5b6191c0924e61b84e8289065918fa991846e1a0f19f357c497c9fa4c420fda0578c14",
|
216
|
+
"iterations": 10000,
|
217
|
+
"hash_function": "OpenSSL::Digest::SHA512"
|
218
|
+
},
|
219
|
+
"credentials": {
|
220
|
+
"postgresql": {
|
221
|
+
"db_superuser_password": {
|
222
|
+
"type": "Veil::Credential",
|
223
|
+
"name": "db_superuser_password",
|
224
|
+
"group": "postgresql",
|
225
|
+
"value": "37f5c43dd8bab089821e5a49fe0ac17128c6d1878fe632e687889eaaea1980b51b3e3df755d2610cffe6896c1df9f23bdda3",
|
226
|
+
"version": 1,
|
227
|
+
"length": 100,
|
228
|
+
"frozen": false
|
229
|
+
}
|
230
|
+
}
|
231
|
+
}
|
232
|
+
},
|
233
|
+
"postgresql": {
|
234
|
+
"db_superuser_password": "37f5c43dd8bab089821e5a49fe0ac17128c6d1878fe632e687889eaaea1980b51b3e3df755d2610cffe6896c1df9f23bdda3"
|
235
|
+
}
|
236
|
+
}
|
237
|
+
EOF
|
238
|
+
}
|
239
|
+
let(:tempfile) { Tempfile.new("private-chef-secrets").path }
|
240
|
+
|
241
|
+
before(:each) do
|
242
|
+
File.write(tempfile, existing_content)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "reads the secrets" do
|
246
|
+
creds = described_class.new(path: tempfile)
|
247
|
+
expect(creds["postgresql"]["db_superuser_password"].value).to eq("37f5c43dd8bab089821e5a49fe0ac17128c6d1878fe632e687889eaaea1980b51b3e3df755d2610cffe6896c1df9f23bdda3")
|
248
|
+
end
|
249
|
+
|
250
|
+
it "saves the file encrypted" do
|
251
|
+
creds = described_class.new(path: tempfile)
|
252
|
+
creds.save
|
253
|
+
|
254
|
+
expect(IO.read(tempfile)).to_not match("37f5c43dd8bab089821e5a49fe0ac17128c6d1878fe632e687889eaaea1980b51b3e3df755d2610cffe6896c1df9f23bdda3")
|
255
|
+
end
|
256
|
+
|
257
|
+
it "keeps only the veil key of the file's hash" do
|
258
|
+
creds = described_class.new(path: tempfile)
|
259
|
+
creds.save
|
260
|
+
|
261
|
+
json = JSON.parse(IO.read(tempfile))
|
262
|
+
expect(json.keys).to eq(["veil"])
|
263
|
+
end
|
140
264
|
end
|
141
265
|
end
|
142
266
|
end
|
@@ -11,6 +11,15 @@ describe Veil::CredentialCollection do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
context 'passing provider "chef-secrets-env"' do
|
15
|
+
let(:opts) { { provider: 'chef-secrets-env' } }
|
16
|
+
|
17
|
+
it 'instantiates ChefSecretsFile with all options' do
|
18
|
+
expect(Veil::CredentialCollection::ChefSecretsEnv).to receive(:new).with(opts)
|
19
|
+
described_class.from_config(opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
14
23
|
context 'passing anything else as provider' do
|
15
24
|
let(:opts) { { provider: 'vault' } }
|
16
25
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: veil
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chef Software, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bcrypt
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rake
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,10 +68,9 @@ dependencies:
|
|
82
68
|
version: '3.0'
|
83
69
|
description: Veil is a Ruby Gem for generating secure secrets from a shared secret
|
84
70
|
email:
|
85
|
-
-
|
71
|
+
- info@chef.io
|
86
72
|
executables:
|
87
|
-
-
|
88
|
-
- setup
|
73
|
+
- veil-dump-secrets
|
89
74
|
- veil-env-helper
|
90
75
|
- veil-ingest-secret
|
91
76
|
extensions: []
|
@@ -94,12 +79,18 @@ files:
|
|
94
79
|
- LICENSE
|
95
80
|
- bin/console
|
96
81
|
- bin/setup
|
82
|
+
- bin/veil-dump-secrets
|
97
83
|
- bin/veil-env-helper
|
98
84
|
- bin/veil-ingest-secret
|
99
85
|
- lib/veil.rb
|
86
|
+
- lib/veil/cipher.rb
|
87
|
+
- lib/veil/cipher/v1.rb
|
88
|
+
- lib/veil/cipher/v2.rb
|
100
89
|
- lib/veil/credential.rb
|
101
90
|
- lib/veil/credential_collection.rb
|
102
91
|
- lib/veil/credential_collection/base.rb
|
92
|
+
- lib/veil/credential_collection/chef_secrets_env.rb
|
93
|
+
- lib/veil/credential_collection/chef_secrets_fd.rb
|
103
94
|
- lib/veil/credential_collection/chef_secrets_file.rb
|
104
95
|
- lib/veil/exceptions.rb
|
105
96
|
- lib/veil/hasher.rb
|
@@ -108,7 +99,12 @@ files:
|
|
108
99
|
- lib/veil/hasher/pbkdf2.rb
|
109
100
|
- lib/veil/utils.rb
|
110
101
|
- lib/veil/version.rb
|
102
|
+
- spec/cipher/v1_spec.rb
|
103
|
+
- spec/cipher/v2_spec.rb
|
104
|
+
- spec/cipher_spec.rb
|
111
105
|
- spec/credential_collection/base_spec.rb
|
106
|
+
- spec/credential_collection/chef_secrets_env_spec.rb
|
107
|
+
- spec/credential_collection/chef_secrets_fd_spec.rb
|
112
108
|
- spec/credential_collection/chef_secrets_file_spec.rb
|
113
109
|
- spec/credential_collection_spec.rb
|
114
110
|
- spec/credential_spec.rb
|
@@ -119,7 +115,7 @@ files:
|
|
119
115
|
- spec/spec_helper.rb
|
120
116
|
- spec/utils_spec.rb
|
121
117
|
- spec/veil_spec.rb
|
122
|
-
homepage: https://github.com/chef/
|
118
|
+
homepage: https://github.com/chef/chef_secrets/
|
123
119
|
licenses:
|
124
120
|
- Apache-2.0
|
125
121
|
metadata: {}
|
@@ -138,13 +134,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
134
|
- !ruby/object:Gem::Version
|
139
135
|
version: '0'
|
140
136
|
requirements: []
|
141
|
-
|
142
|
-
rubygems_version: 2.6.10
|
137
|
+
rubygems_version: 3.1.4
|
143
138
|
signing_key:
|
144
139
|
specification_version: 4
|
145
140
|
summary: Veil is a Ruby Gem for generating secure secrets from a shared secret
|
146
141
|
test_files:
|
142
|
+
- spec/cipher/v1_spec.rb
|
143
|
+
- spec/cipher/v2_spec.rb
|
144
|
+
- spec/cipher_spec.rb
|
147
145
|
- spec/credential_collection/base_spec.rb
|
146
|
+
- spec/credential_collection/chef_secrets_env_spec.rb
|
147
|
+
- spec/credential_collection/chef_secrets_fd_spec.rb
|
148
148
|
- spec/credential_collection/chef_secrets_file_spec.rb
|
149
149
|
- spec/credential_collection_spec.rb
|
150
150
|
- spec/credential_spec.rb
|