chamber 2.4.0 → 2.7.1
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 +4 -4
- data/README.md +16 -930
- data/Rakefile +6 -0
- data/lib/chamber.rb +11 -7
- data/lib/chamber/binary/heroku.rb +45 -25
- data/lib/chamber/binary/runner.rb +82 -44
- data/lib/chamber/binary/travis.rb +14 -8
- data/lib/chamber/commands/base.rb +1 -2
- data/lib/chamber/commands/comparable.rb +0 -1
- data/lib/chamber/commands/compare.rb +1 -1
- data/lib/chamber/commands/files.rb +0 -1
- data/lib/chamber/commands/heroku.rb +2 -3
- data/lib/chamber/commands/heroku/push.rb +1 -1
- data/lib/chamber/commands/initialize.rb +69 -12
- data/lib/chamber/commands/securable.rb +9 -4
- data/lib/chamber/commands/secure.rb +1 -1
- data/lib/chamber/commands/show.rb +20 -4
- data/lib/chamber/commands/travis.rb +0 -1
- data/lib/chamber/configuration.rb +5 -5
- data/lib/chamber/context_resolver.rb +12 -12
- data/lib/chamber/decryption_key.rb +51 -0
- data/lib/chamber/environmentable.rb +4 -1
- data/lib/chamber/errors/decryption_failure.rb +6 -0
- data/lib/chamber/file.rb +7 -8
- data/lib/chamber/file_set.rb +23 -22
- data/lib/chamber/filters/boolean_conversion_filter.rb +1 -2
- data/lib/chamber/filters/decryption_filter.rb +42 -25
- data/lib/chamber/filters/encryption_filter.rb +7 -5
- data/lib/chamber/filters/environment_filter.rb +7 -7
- data/lib/chamber/filters/failed_decryption_filter.rb +41 -0
- data/lib/chamber/filters/namespace_filter.rb +1 -1
- data/lib/chamber/filters/secure_filter.rb +3 -5
- data/lib/chamber/filters/translate_secure_keys_filter.rb +5 -24
- data/lib/chamber/namespace_set.rb +6 -6
- data/lib/chamber/rails.rb +1 -3
- data/lib/chamber/rails/railtie.rb +6 -3
- data/lib/chamber/settings.rb +34 -32
- data/lib/chamber/version.rb +1 -1
- data/spec/fixtures/settings.yml +1 -0
- data/spec/lib/chamber/commands/files_spec.rb +4 -2
- data/spec/lib/chamber/commands/secure_spec.rb +8 -5
- data/spec/lib/chamber/commands/show_spec.rb +18 -3
- data/spec/lib/chamber/context_resolver_spec.rb +38 -18
- data/spec/lib/chamber/file_set_spec.rb +73 -52
- data/spec/lib/chamber/file_spec.rb +37 -23
- data/spec/lib/chamber/filters/boolean_conversion_filter_spec.rb +35 -33
- data/spec/lib/chamber/filters/decryption_filter_spec.rb +142 -21
- data/spec/lib/chamber/filters/encryption_filter_spec.rb +51 -19
- data/spec/lib/chamber/filters/environment_filter_spec.rb +12 -6
- data/spec/lib/chamber/filters/failed_decryption_filter_spec.rb +53 -0
- data/spec/lib/chamber/filters/insecure_filter_spec.rb +38 -18
- data/spec/lib/chamber/filters/namespace_filter_spec.rb +38 -38
- data/spec/lib/chamber/filters/secure_filter_spec.rb +10 -10
- data/spec/lib/chamber/filters/translate_secure_keys_filter_spec.rb +9 -6
- data/spec/lib/chamber/namespace_set_spec.rb +7 -5
- data/spec/lib/chamber/settings_spec.rb +168 -79
- data/spec/lib/chamber_spec.rb +72 -71
- metadata +22 -21
- data/lib/chamber/errors/undecryptable_value_error.rb +0 -6
- data/templates/settings.yml +0 -14
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'openssl'
|
2
2
|
require 'base64'
|
3
3
|
require 'hashie/mash'
|
4
|
+
require 'yaml'
|
4
5
|
|
5
6
|
module Chamber
|
6
7
|
module Filters
|
7
8
|
class EncryptionFilter
|
8
|
-
SECURE_KEY_TOKEN =
|
9
|
+
SECURE_KEY_TOKEN = /\A_secure_/
|
9
10
|
BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9\+\/]{342}==\z}
|
10
11
|
|
11
12
|
def initialize(options = {})
|
@@ -14,7 +15,7 @@ class EncryptionFilter
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def self.execute(options = {})
|
17
|
-
|
18
|
+
new(options).send(:execute)
|
18
19
|
end
|
19
20
|
|
20
21
|
protected
|
@@ -28,9 +29,10 @@ class EncryptionFilter
|
|
28
29
|
raw_data.each_pair do |key, value|
|
29
30
|
if value.respond_to? :each_pair
|
30
31
|
value = execute(value)
|
31
|
-
elsif
|
32
|
-
|
33
|
-
|
32
|
+
elsif key.match(SECURE_KEY_TOKEN)
|
33
|
+
unless value.respond_to?(:match) && value.match(BASE64_STRING_PATTERN)
|
34
|
+
serialized_value = YAML.dump(value)
|
35
|
+
encrypted_string = encryption_key.public_encrypt(serialized_value)
|
34
36
|
value = Base64.strict_encode64(encrypted_string)
|
35
37
|
end
|
36
38
|
end
|
@@ -54,7 +54,7 @@ class EnvironmentFilter
|
|
54
54
|
#
|
55
55
|
#
|
56
56
|
def self.execute(options = {})
|
57
|
-
|
57
|
+
new(options).send(:execute)
|
58
58
|
end
|
59
59
|
|
60
60
|
protected
|
@@ -63,12 +63,12 @@ class EnvironmentFilter
|
|
63
63
|
|
64
64
|
def execute(settings = data, parent_keys = [])
|
65
65
|
with_environment(settings, parent_keys,
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
66
|
+
lambda do |key, value, environment_keys|
|
67
|
+
{ key => execute(value, environment_keys) }
|
68
|
+
end,
|
69
|
+
lambda do |key, value, environment_key|
|
70
|
+
{ key => (ENV[environment_key] || value) }
|
71
|
+
end)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'chamber/errors/decryption_failure'
|
2
|
+
|
3
|
+
module Chamber
|
4
|
+
module Filters
|
5
|
+
class FailedDecryptionFilter
|
6
|
+
SECURE_KEY_TOKEN = /\A_secure_/
|
7
|
+
BASE64_STRING_PATTERN = %r{\A[A-Za-z0-9\+/]{342}==\z}
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
self.data = options.fetch(:data).dup
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.execute(options = {})
|
14
|
+
new(options).send(:execute)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
attr_accessor :data
|
20
|
+
|
21
|
+
def execute(raw_data = data)
|
22
|
+
settings = raw_data
|
23
|
+
|
24
|
+
raw_data.each_pair do |key, value|
|
25
|
+
if value.respond_to? :each_pair
|
26
|
+
execute(value)
|
27
|
+
elsif key.match(SECURE_KEY_TOKEN) &&
|
28
|
+
value.respond_to?(:match) &&
|
29
|
+
value.match(BASE64_STRING_PATTERN)
|
30
|
+
|
31
|
+
fail Chamber::Errors::DecryptionFailure,
|
32
|
+
"Failed to decrypt #{key} (with an encrypted value of '#{value}') " \
|
33
|
+
'in your settings.'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
settings
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -3,14 +3,14 @@ require 'hashie/mash'
|
|
3
3
|
module Chamber
|
4
4
|
module Filters
|
5
5
|
class SecureFilter
|
6
|
-
SECURE_KEY_TOKEN =
|
6
|
+
SECURE_KEY_TOKEN = /\A_secure_/
|
7
7
|
|
8
8
|
def initialize(options = {})
|
9
9
|
self.data = Hashie::Mash.new(options.fetch(:data))
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.execute(options = {})
|
13
|
-
|
13
|
+
new(options).send(:execute)
|
14
14
|
end
|
15
15
|
|
16
16
|
protected
|
@@ -24,9 +24,7 @@ class SecureFilter
|
|
24
24
|
secure_value = if value.respond_to? :each_pair
|
25
25
|
execute(value)
|
26
26
|
elsif key.respond_to? :match
|
27
|
-
if key.match(SECURE_KEY_TOKEN)
|
28
|
-
value
|
29
|
-
end
|
27
|
+
value if key.match(SECURE_KEY_TOKEN)
|
30
28
|
end
|
31
29
|
|
32
30
|
settings[key] = secure_value unless secure_value.nil?
|
@@ -1,17 +1,16 @@
|
|
1
1
|
require 'hashie/mash'
|
2
|
-
require 'chamber/errors/undecryptable_value_error'
|
3
2
|
|
4
3
|
module Chamber
|
5
4
|
module Filters
|
6
5
|
class TranslateSecureKeysFilter
|
7
|
-
SECURE_KEY_TOKEN =
|
6
|
+
SECURE_KEY_TOKEN = /\A_secure_/
|
8
7
|
|
9
8
|
def initialize(options = {})
|
10
9
|
self.data = options.fetch(:data).dup
|
11
10
|
end
|
12
11
|
|
13
12
|
def self.execute(options = {})
|
14
|
-
|
13
|
+
new(options).send(:execute)
|
15
14
|
end
|
16
15
|
|
17
16
|
protected
|
@@ -22,33 +21,15 @@ class TranslateSecureKeysFilter
|
|
22
21
|
settings = Hashie::Mash.new
|
23
22
|
|
24
23
|
raw_data.each_pair do |key, value|
|
25
|
-
if value.respond_to? :each_pair
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
key = key.to_s
|
30
|
-
|
31
|
-
if key.match(SECURE_KEY_TOKEN)
|
32
|
-
key = key.sub(SECURE_KEY_TOKEN, '')
|
33
|
-
end
|
24
|
+
value = execute(value) if value.respond_to? :each_pair
|
25
|
+
key = key.to_s
|
26
|
+
key = key.sub(SECURE_KEY_TOKEN, '') if key.match(SECURE_KEY_TOKEN)
|
34
27
|
|
35
28
|
settings[key] = value
|
36
29
|
end
|
37
30
|
|
38
31
|
settings
|
39
32
|
end
|
40
|
-
|
41
|
-
def decryption_key=(keyish)
|
42
|
-
return @decryption_key = nil if keyish.nil?
|
43
|
-
|
44
|
-
key_content = if ::File.readable?(::File.expand_path(keyish))
|
45
|
-
::File.read(::File.expand_path(keyish))
|
46
|
-
else
|
47
|
-
keyish
|
48
|
-
end
|
49
|
-
|
50
|
-
@decryption_key = OpenSSL::PKey::RSA.new(key_content)
|
51
|
-
end
|
52
33
|
end
|
53
34
|
end
|
54
35
|
end
|
@@ -30,7 +30,7 @@ class NamespaceSet
|
|
30
30
|
# Returns a new NamespaceSet
|
31
31
|
#
|
32
32
|
def self.[](*namespace_values)
|
33
|
-
|
33
|
+
new(namespace_values)
|
34
34
|
end
|
35
35
|
|
36
36
|
###
|
@@ -92,7 +92,7 @@ class NamespaceSet
|
|
92
92
|
# Returns a Boolean
|
93
93
|
#
|
94
94
|
def ==(other)
|
95
|
-
|
95
|
+
to_a.eql? other.to_a
|
96
96
|
end
|
97
97
|
|
98
98
|
###
|
@@ -102,8 +102,8 @@ class NamespaceSet
|
|
102
102
|
# Returns a Boolean
|
103
103
|
#
|
104
104
|
def eql?(other)
|
105
|
-
other.is_a?(
|
106
|
-
|
105
|
+
other.is_a?(NamespaceSet) &&
|
106
|
+
namespaces == other.namespaces
|
107
107
|
end
|
108
108
|
|
109
109
|
protected
|
@@ -151,8 +151,8 @@ class NamespaceSet
|
|
151
151
|
#
|
152
152
|
def namespaces
|
153
153
|
@namespaces ||= Set.new namespace_values.map do |value|
|
154
|
-
|
155
|
-
|
154
|
+
(value.respond_to?(:call) ? value.call : value).to_s
|
155
|
+
end
|
156
156
|
end
|
157
157
|
|
158
158
|
def raw_namespaces=(raw_namespaces)
|
data/lib/chamber/rails.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
1
3
|
module Chamber
|
2
4
|
module Rails
|
3
5
|
class Railtie < ::Rails::Railtie
|
4
6
|
initializer 'chamber.load', before: :load_environment_config do
|
5
|
-
Chamber.load(
|
6
|
-
|
7
|
-
|
7
|
+
Chamber.load(basepath: ::Rails.root.join('config'),
|
8
|
+
namespaces: {
|
9
|
+
environment: -> { ::Rails.env },
|
10
|
+
hostname: -> { Socket.gethostname } })
|
8
11
|
end
|
9
12
|
end
|
10
13
|
end
|
data/lib/chamber/settings.rb
CHANGED
@@ -8,13 +8,13 @@ require 'chamber/filters/boolean_conversion_filter'
|
|
8
8
|
require 'chamber/filters/secure_filter'
|
9
9
|
require 'chamber/filters/translate_secure_keys_filter'
|
10
10
|
require 'chamber/filters/insecure_filter'
|
11
|
+
require 'chamber/filters/failed_decryption_filter'
|
11
12
|
|
12
13
|
###
|
13
14
|
# Internal: Represents the base settings storage needed for Chamber.
|
14
15
|
#
|
15
16
|
module Chamber
|
16
17
|
class Settings
|
17
|
-
|
18
18
|
attr_reader :namespaces
|
19
19
|
|
20
20
|
def initialize(options = {})
|
@@ -23,14 +23,15 @@ class Settings
|
|
23
23
|
self.decryption_key = options[:decryption_key]
|
24
24
|
self.encryption_key = options[:encryption_key]
|
25
25
|
self.pre_filters = options[:pre_filters] || [
|
26
|
-
|
27
|
-
|
26
|
+
Filters::NamespaceFilter,
|
27
|
+
]
|
28
28
|
self.post_filters = options[:post_filters] || [
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
Filters::DecryptionFilter,
|
30
|
+
Filters::EnvironmentFilter,
|
31
|
+
Filters::FailedDecryptionFilter,
|
32
|
+
Filters::BooleanConversionFilter,
|
33
|
+
Filters::TranslateSecureKeysFilter,
|
34
|
+
]
|
34
35
|
end
|
35
36
|
|
36
37
|
###
|
@@ -81,7 +82,7 @@ class Settings
|
|
81
82
|
concatenated_name_hash = to_concatenated_name_hash(hierarchical_separator)
|
82
83
|
|
83
84
|
pairs = concatenated_name_hash.to_a.map do |key, value|
|
84
|
-
|
85
|
+
"#{key.upcase}#{name_value_separator}#{value_surrounder}#{value}#{value_surrounder}"
|
85
86
|
end
|
86
87
|
|
87
88
|
pairs.join(pair_separator)
|
@@ -131,7 +132,8 @@ class Settings
|
|
131
132
|
flattened_name_components = parent_keys.dup.push(key)
|
132
133
|
|
133
134
|
if value.respond_to?(:each_pair)
|
134
|
-
flattened_name_hash.merge! to_flattened_name_hash(value,
|
135
|
+
flattened_name_hash.merge! to_flattened_name_hash(value,
|
136
|
+
flattened_name_components)
|
135
137
|
else
|
136
138
|
flattened_name_hash[flattened_name_components] = value
|
137
139
|
end
|
@@ -194,7 +196,7 @@ class Settings
|
|
194
196
|
# Returns a Boolean
|
195
197
|
#
|
196
198
|
def ==(other)
|
197
|
-
|
199
|
+
to_hash == other.to_hash
|
198
200
|
end
|
199
201
|
|
200
202
|
###
|
@@ -203,29 +205,29 @@ class Settings
|
|
203
205
|
# Returns a Boolean
|
204
206
|
#
|
205
207
|
def eql?(other)
|
206
|
-
other.is_a?(
|
207
|
-
|
208
|
-
|
208
|
+
other.is_a?(Chamber::Settings) &&
|
209
|
+
data == other.data &&
|
210
|
+
namespaces == other.namespaces
|
209
211
|
end
|
210
212
|
|
211
213
|
def securable
|
212
|
-
Settings.new(
|
213
|
-
settings:
|
214
|
-
pre_filters:
|
214
|
+
Settings.new(metadata.merge(
|
215
|
+
settings: raw_data,
|
216
|
+
pre_filters: [Filters::SecureFilter]))
|
215
217
|
end
|
216
218
|
|
217
219
|
def secure
|
218
|
-
Settings.new(
|
220
|
+
Settings.new(metadata.merge(
|
219
221
|
settings: raw_data,
|
220
222
|
pre_filters: [Filters::EncryptionFilter],
|
221
|
-
post_filters: [Filters::TranslateSecureKeysFilter]
|
223
|
+
post_filters: [Filters::TranslateSecureKeysFilter]))
|
222
224
|
end
|
223
225
|
|
224
226
|
def insecure
|
225
|
-
Settings.new(
|
227
|
+
Settings.new(metadata.merge(
|
226
228
|
settings: raw_data,
|
227
229
|
pre_filters: [Filters::InsecureFilter],
|
228
|
-
post_filters: [Filters::TranslateSecureKeysFilter]
|
230
|
+
post_filters: [Filters::TranslateSecureKeysFilter]))
|
229
231
|
end
|
230
232
|
|
231
233
|
protected
|
@@ -245,24 +247,24 @@ class Settings
|
|
245
247
|
end
|
246
248
|
|
247
249
|
def raw_data
|
248
|
-
@filtered_raw_data
|
249
|
-
|
250
|
-
|
251
|
-
|
250
|
+
@filtered_raw_data ||= pre_filters.reduce(@raw_data) do |filtered_data, filter|
|
251
|
+
filter.execute({ data: filtered_data }.
|
252
|
+
merge(metadata))
|
253
|
+
end
|
252
254
|
end
|
253
255
|
|
254
256
|
def data
|
255
|
-
@data
|
256
|
-
|
257
|
-
|
258
|
-
|
257
|
+
@data ||= post_filters.reduce(raw_data) do |filtered_data, filter|
|
258
|
+
filter.execute({ data: filtered_data }.
|
259
|
+
merge(metadata))
|
260
|
+
end
|
259
261
|
end
|
260
262
|
|
261
263
|
def metadata
|
262
264
|
{
|
263
|
-
namespaces:
|
264
|
-
decryption_key:
|
265
|
-
encryption_key:
|
265
|
+
namespaces: namespaces,
|
266
|
+
decryption_key: decryption_key,
|
267
|
+
encryption_key: encryption_key,
|
266
268
|
}
|
267
269
|
end
|
268
270
|
|
data/lib/chamber/version.rb
CHANGED
data/spec/fixtures/settings.yml
CHANGED
@@ -14,6 +14,7 @@ development:
|
|
14
14
|
a_scalar: hello
|
15
15
|
test:
|
16
16
|
my_setting: my_value
|
17
|
+
_secure_my_secure_settings: 'M3yI2fIHsfD+zznsvO3FB/ryCwvvdQQ9ZXPQlTIR6Y9vtzNFAeRAxZpSyYUdOpeMDkWQSo5ZVLseM20iTh1YpNCjzd7D0bT4O9aBskYBE92b4ioYPAPSZ3NcvA1pGa6A/hWGo3iJZK1t96mGrfxy2mSFFqGHQbj4ix6D7PpCfVkjuUMp3NG3XjgGhmynK88XENWXBQfgxdfwylZZSQTm058BubkuM5MXgf4WGL3qWo+wWk9AOwjohAGq3UAf5Q341g/OlPGbCV3rBPTnlm866N8aAsHtppg5HwbknaySpLMPcv0KhUGC/bEPgbm3tuG7JZKsoqvDmWr/I+LjVi/LKg=='
|
17
18
|
my_boolean: 'false'
|
18
19
|
my_dynamic_setting: 2
|
19
20
|
another_level:
|
@@ -5,8 +5,10 @@ module Chamber
|
|
5
5
|
module Commands
|
6
6
|
describe Files do
|
7
7
|
let(:rootpath) { ::File.expand_path('./spec/fixtures') }
|
8
|
-
let(:options)
|
9
|
-
|
8
|
+
let(:options) do
|
9
|
+
{ basepath: rootpath,
|
10
|
+
rootpath: rootpath }
|
11
|
+
end
|
10
12
|
|
11
13
|
it 'can return values formatted as environment variables' do
|
12
14
|
files = Files.call(options)
|
@@ -8,10 +8,12 @@ describe Secure do
|
|
8
8
|
let(:rootpath) { Pathname.new(::File.expand_path('./spec/fixtures')) }
|
9
9
|
let(:settings_directory) { rootpath + 'settings' }
|
10
10
|
let(:settings_filename) { settings_directory + 'unencrypted.yml' }
|
11
|
-
let(:options)
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
let(:options) do
|
12
|
+
{ basepath: rootpath,
|
13
|
+
rootpath: rootpath,
|
14
|
+
encryption_key: rootpath + '../spec_key',
|
15
|
+
shell: double.as_null_object }
|
16
|
+
end
|
15
17
|
|
16
18
|
before(:each) do
|
17
19
|
::FileUtils.mkdir_p settings_directory unless ::File.exist? settings_directory
|
@@ -29,7 +31,8 @@ HEREDOC
|
|
29
31
|
|
30
32
|
Secure.call(options)
|
31
33
|
|
32
|
-
expect(settings_filename.read).
|
34
|
+
expect(settings_filename.read).
|
35
|
+
to match %r{_secure_my_unencrpyted_setting: [A-Za-z0-9\+\/]{342}==}
|
33
36
|
end
|
34
37
|
end
|
35
38
|
end
|