veil 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,142 @@
1
+ require "spec_helper"
2
+ require "tempfile"
3
+ require 'stringio'
4
+
5
+ describe Veil::CredentialCollection::ChefSecretsFile do
6
+ let(:hasher) { Veil::Hasher.create }
7
+ let!(:file) { Tempfile.new("private_chef_secrets.json") }
8
+ let(:user) { "opscode_user" }
9
+ let(:group) { "opscode_group" }
10
+ let(:content) do
11
+ {
12
+ "veil" => {
13
+ "type" => "Veil::CredentialCollection::ChefSecretsFile",
14
+ "hasher" => {},
15
+ "credentials" => {}
16
+ }
17
+ }
18
+ end
19
+ let(:legacy_content) do
20
+ {
21
+ "redis_lb" => {
22
+ "password" => "f1ad3e8b1e47bc81720742a2572b9ff"
23
+ },
24
+ "rabbitmq" => {
25
+ "password" => "f5031e56ae018a7b71ce153086fc88f",
26
+ "actions_password" => "92609a68d03b50afcc597d7"
27
+ }
28
+ }
29
+ end
30
+
31
+ describe "#self.from_file" do
32
+ context "when the file exists" do
33
+ context "when it's a valid credential store" do
34
+ it "returns an instance" do
35
+ file.write(JSON.pretty_generate(content))
36
+ file.rewind
37
+ expect(described_class.from_file(file.path)).to be_instance_of(described_class)
38
+ end
39
+ end
40
+
41
+ context "when it's not a valid credential store" do
42
+ it "raises an error" do
43
+ file.write("not a json chef secrets file")
44
+ file.rewind
45
+ expect { described_class.from_file(file.path) }.to raise_error(Veil::InvalidCredentialCollectionFile)
46
+ end
47
+ end
48
+
49
+ context "when it's a legacy secrets file" do
50
+ it "imports the legacy file" do
51
+ file.write(JSON.pretty_generate(legacy_content))
52
+ file.rewind
53
+ instance = described_class.from_file(file.path)
54
+ expect(instance["redis_lb"]["password"].value).to eq("f1ad3e8b1e47bc81720742a2572b9ff")
55
+ expect(instance["redis_lb"]["password"].version).to eq(0)
56
+ expect(instance["redis_lb"]["password"].length).to eq(31)
57
+ end
58
+ end
59
+ end
60
+
61
+ context "when the file doesn't exist" do
62
+ it "raises an error" do
63
+ expect { described_class.from_file("not_a_file") }.to raise_error(Veil::InvalidCredentialCollectionFile)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#save" do
69
+ it "saves the content to a machine loadable file" do
70
+ file.rewind
71
+ creds = described_class.new(path: file.path)
72
+ creds.add("redis_lb", "password")
73
+ creds.add("postgresql", "sql_ro_password")
74
+ creds.save
75
+
76
+ file.rewind
77
+ new_creds = described_class.from_file(file.path)
78
+
79
+ expect(new_creds["redis_lb"]["password"].value).to eq(creds["redis_lb"]["password"].value)
80
+ expect(new_creds["postgresql"]["sql_ro_password"].value).to eq(creds["postgresql"]["sql_ro_password"].value)
81
+ end
82
+
83
+ context "when using ownership management" do
84
+ let(:tmpfile) do
85
+ s = StringIO.new
86
+ allow(s).to receive(:path).and_return("/tmp/unguessable")
87
+ s
88
+ end
89
+
90
+ context "when the user is set" do
91
+ it "gives the file proper permissions" do
92
+ expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
93
+ expect(FileUtils).to receive(:chown).with(user, user, "/tmp/unguessable")
94
+ expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
95
+
96
+ creds = described_class.new(path: file.path,
97
+ user: user)
98
+ creds.add("redis_lb", "password")
99
+ creds.save
100
+ end
101
+ end
102
+
103
+ context "when user and group are set" do
104
+ it "gives the file proper permissions" do
105
+ expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
106
+ expect(FileUtils).to receive(:chown).with(user, group, "/tmp/unguessable")
107
+ expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
108
+
109
+ creds = described_class.new(path: file.path,
110
+ user: user,
111
+ group: group)
112
+ creds.add("redis_lb", "password")
113
+ creds.save
114
+ end
115
+
116
+ it "gives the file proper permission even when called from_file" do
117
+ file.puts("{}"); file.rewind
118
+ expect(Tempfile).to receive(:new).with("veil").and_return(tmpfile)
119
+ expect(FileUtils).to receive(:chown).with(user, group, "/tmp/unguessable")
120
+ expect(FileUtils).to receive(:mv).with("/tmp/unguessable", file.path)
121
+
122
+ creds = described_class.from_file(file.path,
123
+ user: user,
124
+ group: group)
125
+ creds.add("redis_lb", "password")
126
+ creds.save
127
+ end
128
+ end
129
+ end
130
+
131
+ it "saves the version number" do
132
+ allow(FileUtils).to receive(:chown)
133
+ file.rewind
134
+ creds = described_class.new(path: file.path, version: 12)
135
+ creds.save
136
+
137
+ file.rewind
138
+ new_creds = described_class.from_file(file.path)
139
+ expect(new_creds.version).to eq(12)
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe Veil::CredentialCollection do
4
+ describe 'from_config' do
5
+ context 'passing provider "chef-secrets-file"' do
6
+ let(:opts) { { provider: 'chef-secrets-file', something_else: 'config' } }
7
+
8
+ it 'instantiates ChefSecretsFile with all options' do
9
+ expect(Veil::CredentialCollection::ChefSecretsFile).to receive(:new).with(opts)
10
+ described_class.from_config(opts)
11
+ end
12
+ end
13
+
14
+ context 'passing anything else as provider' do
15
+ let(:opts) { { provider: 'vault' } }
16
+
17
+ it 'raises an exception' do
18
+ expect { described_class.from_config(opts) }.to raise_error(Veil::UnknownProvider)
19
+ end
20
+ end
21
+
22
+ context 'passing an options hash that has no provider' do
23
+ let(:opts) { { something: 'else' } }
24
+
25
+ it 'raises an exception' do
26
+ expect { described_class.from_config(opts) }.to raise_error(Veil::UnknownProvider)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,94 @@
1
+ require "spec_helper"
2
+
3
+ describe Veil::Credential do
4
+ let(:data) { "heart and lungs" }
5
+ let(:salt) { "NaCL" }
6
+ let(:secret) { "Black Eagle" }
7
+ let(:hasher_hash) do
8
+ {
9
+ data: data,
10
+ salt: salt,
11
+ secret: secret
12
+ }
13
+ end
14
+
15
+ let(:credential_hash) do
16
+ {
17
+ name: "eros",
18
+ version: 23,
19
+ value: "some crazy secret",
20
+ length: 17,
21
+ group: "portals"
22
+ }
23
+ end
24
+
25
+ let(:hasher) { Veil::Hasher.create(hasher_hash) }
26
+
27
+ subject { described_class.new(name: "eros", value: "thing") }
28
+
29
+ describe "self.create" do
30
+ it "creates an instance from the hash" do
31
+ cred = described_class.create(credential_hash)
32
+ expect(cred.name).to eq(credential_hash[:name])
33
+ expect(cred.version).to eq(credential_hash[:version])
34
+ expect(cred.value).to eq(credential_hash[:value])
35
+ expect(cred.length).to eq(credential_hash[:length])
36
+ expect(cred.group).to eq(credential_hash[:group])
37
+ end
38
+ end
39
+
40
+ describe "#new" do
41
+ it "sets a default version to 0" do
42
+ expect(subject.version).to eq(0)
43
+ end
44
+ end
45
+
46
+ describe "#rotate!" do
47
+ it "increments the version and hashes a new value" do
48
+ cred = described_class.new(group: "foo", name: "bar", version: 2, value: "thing")
49
+ cred.rotate!(hasher)
50
+ expect(cred.version).to eq(3)
51
+ expect(cred.value).to_not eq("thing")
52
+ end
53
+
54
+ context "when the hasher is invalid" do
55
+ it "raises an invalid hasher error" do
56
+ expect { subject.rotate!(1) }.to raise_error(Veil::InvalidHasher)
57
+ end
58
+ end
59
+
60
+ context "when the credential is frozen" do
61
+ it "raises a runtime error" do
62
+ cred = described_class.new(group: "foo", name: "bar", version: 3, value: "thing", frozen: true)
63
+ expect { cred.rotate!(1) }.to raise_error(RuntimeError)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#rotate" do
69
+ it "increments the version and hashes a new value" do
70
+ cred = described_class.new(group: "foo", name: "bar", version: 2, value: "thing")
71
+ cred.rotate(hasher)
72
+ expect(cred.version).to eq(3)
73
+ expect(cred.value).to_not eq("thing")
74
+ end
75
+
76
+ context "when the hasher is invalid" do
77
+ it "does not rotate the credential" do
78
+ cred = described_class.new(group: "foo", name: "bar", version: 3, value: "thing", frozen: true)
79
+ expect(cred.rotate(1)).to eq(false)
80
+ expect(cred.version).to eq(3)
81
+ expect(cred.value).to eq("thing")
82
+ end
83
+ end
84
+
85
+ context "when the credential is frozen" do
86
+ it "does not rotate the credential" do
87
+ cred = described_class.new(group: "foo", name: "bar", version: 3, value: "thing", frozen: true)
88
+ expect(cred.rotate(hasher)).to eq(false)
89
+ expect(cred.version).to eq(3)
90
+ expect(cred.value).to eq("thing")
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+ require "digest"
3
+
4
+ describe Veil::Hasher::Base do
5
+ let(:data) { "let him enter" }
6
+
7
+ subject { described_class.new }
8
+
9
+ describe "#hex_digest" do
10
+ it "returns a SHA512 hex digest of the data" do
11
+ expect(subject.send(:hex_digest, data)).to eq(OpenSSL::Digest::SHA512.hexdigest(data))
12
+ end
13
+
14
+ it "does not use Digest, which uses low level APIs prohibited by FIPS" do
15
+ expect(Digest::SHA512).not_to receive(:hexdigest)
16
+ subject.send(:hex_digest, data)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe Veil::Hasher::BCrypt do
4
+ let(:group) { "postgresql" }
5
+ let(:name) { "sql_ro_password" }
6
+ let(:version) { "5" }
7
+ let(:cost) { 11 }
8
+ let(:salt) { "$2a$11$4xS0IHHxU5sOYZ0Z5X53Qe" }
9
+ let(:secret) { "only friends tell each other" }
10
+
11
+ subject { described_class.new(secret: secret, salt: salt) }
12
+
13
+ describe "#new" do
14
+ it "builds an instance" do
15
+ expect(described_class.new.class).to eq(described_class)
16
+ end
17
+
18
+ context "from a hash" do
19
+ it "builds an identical instance" do
20
+ new_instance = described_class.new(subject.to_hash)
21
+ expect(new_instance.encrypt("slow", "forever", 1)).to eq(subject.encrypt("slow", "forever", 1))
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#encrypt" do
27
+ it "deterministically encrypts data" do
28
+ encrypted_data = subject.encrypt(group, name, version)
29
+
30
+ new_instance = described_class.new(
31
+ secret: secret,
32
+ salt: salt,
33
+ )
34
+
35
+ expect(new_instance.encrypt(group, name, version)).to eq(encrypted_data)
36
+ end
37
+ end
38
+
39
+ describe "#to_hash" do
40
+ it "returns itself as a hash" do
41
+ expect(subject.to_hash).to eq({
42
+ type: "Veil::Hasher::BCrypt",
43
+ secret: secret,
44
+ salt: salt,
45
+ })
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe Veil::Hasher::PBKDF2 do
4
+ let(:group) { "android" }
5
+ let(:name) { "artoo" }
6
+ let(:version) { 2 }
7
+ let(:salt) { "nacl" }
8
+ let(:secret) { "sauce" }
9
+ let(:iterations) { 100 }
10
+ let(:digest) { "SHA256" }
11
+
12
+ subject do
13
+ described_class.new(
14
+ secret: secret,
15
+ salt: salt,
16
+ iterations: iterations,
17
+ hash_function: digest
18
+ )
19
+ end
20
+
21
+ describe "#new" do
22
+ it "builds an instance" do
23
+ expect(described_class.new.class).to eq(described_class)
24
+ end
25
+
26
+ context "from a hash" do
27
+ it "builds an identical instance" do
28
+ new_instance = described_class.new(subject.to_hash)
29
+ expect(new_instance.encrypt("slow", "forever", 5)).to eq(subject.encrypt("slow", "forever", 5))
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "#encrypt" do
35
+ it "deterministically encrypts data" do
36
+ encrypted_data = subject.encrypt(group, name, version)
37
+
38
+ new_instance = described_class.new(
39
+ secret: secret,
40
+ salt: salt,
41
+ iterations: iterations,
42
+ hash_function: digest
43
+ )
44
+
45
+ expect(new_instance.encrypt(group, name, version)).to eq(encrypted_data)
46
+ end
47
+ end
48
+
49
+ describe "#to_hash" do
50
+ it "returns itself as a hash" do
51
+ expect(subject.to_hash).to eq({
52
+ type: "Veil::Hasher::PBKDF2",
53
+ secret: secret,
54
+ salt: salt,
55
+ iterations: iterations,
56
+ hash_function: "OpenSSL::Digest::SHA256"
57
+ })
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe Veil::Hasher do
4
+ let(:data) { "let him enter" }
5
+ let(:cost) { 11 }
6
+ let(:salt) { "$2a$11$4xS0IHHxU5sOYZ0Z5X53Qe" }
7
+ let(:secret) { "super duper secret" }
8
+ let(:hash) do
9
+ { type: "Veil::Hasher::BCrypt",
10
+ secret: secret,
11
+ salt: salt
12
+ }
13
+ end
14
+
15
+ describe "#self.create" do
16
+ context "with opts" do
17
+ it "returns an instance of the class" do
18
+ instance = described_class.create(hash)
19
+ expect(instance.class.name).to eq("Veil::Hasher::BCrypt")
20
+ expect(instance.secret).to eq(secret)
21
+ expect(instance.salt).to eq(salt)
22
+ end
23
+ end
24
+
25
+ context "without opts" do
26
+ it "returns an instance of the default class" do
27
+ instance = described_class.create
28
+ expect(instance.class.name).to eq("Veil::Hasher::PBKDF2")
29
+ expect(instance.iterations).to eq(10_000)
30
+ expect(instance.hash_function.class.name).to eq("OpenSSL::Digest::SHA512")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+ require "veil"
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe Veil::Utils do
4
+ let(:mixed_hash) do
5
+ {
6
+ :foo => "bar",
7
+ bar: "baz",
8
+ "fizz" => :buzz
9
+ }
10
+ end
11
+
12
+ let(:symbolized_hash) do
13
+ {
14
+ foo: "bar",
15
+ bar: "baz",
16
+ fizz: :buzz
17
+ }
18
+ end
19
+
20
+ describe "#symbolize_keys" do
21
+ it "symbolizes a hashes keys" do
22
+ expect(described_class.symbolize_keys(mixed_hash)).to eq(symbolized_hash)
23
+ end
24
+ end
25
+ end