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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -930
  3. data/Rakefile +6 -0
  4. data/lib/chamber.rb +11 -7
  5. data/lib/chamber/binary/heroku.rb +45 -25
  6. data/lib/chamber/binary/runner.rb +82 -44
  7. data/lib/chamber/binary/travis.rb +14 -8
  8. data/lib/chamber/commands/base.rb +1 -2
  9. data/lib/chamber/commands/comparable.rb +0 -1
  10. data/lib/chamber/commands/compare.rb +1 -1
  11. data/lib/chamber/commands/files.rb +0 -1
  12. data/lib/chamber/commands/heroku.rb +2 -3
  13. data/lib/chamber/commands/heroku/push.rb +1 -1
  14. data/lib/chamber/commands/initialize.rb +69 -12
  15. data/lib/chamber/commands/securable.rb +9 -4
  16. data/lib/chamber/commands/secure.rb +1 -1
  17. data/lib/chamber/commands/show.rb +20 -4
  18. data/lib/chamber/commands/travis.rb +0 -1
  19. data/lib/chamber/configuration.rb +5 -5
  20. data/lib/chamber/context_resolver.rb +12 -12
  21. data/lib/chamber/decryption_key.rb +51 -0
  22. data/lib/chamber/environmentable.rb +4 -1
  23. data/lib/chamber/errors/decryption_failure.rb +6 -0
  24. data/lib/chamber/file.rb +7 -8
  25. data/lib/chamber/file_set.rb +23 -22
  26. data/lib/chamber/filters/boolean_conversion_filter.rb +1 -2
  27. data/lib/chamber/filters/decryption_filter.rb +42 -25
  28. data/lib/chamber/filters/encryption_filter.rb +7 -5
  29. data/lib/chamber/filters/environment_filter.rb +7 -7
  30. data/lib/chamber/filters/failed_decryption_filter.rb +41 -0
  31. data/lib/chamber/filters/namespace_filter.rb +1 -1
  32. data/lib/chamber/filters/secure_filter.rb +3 -5
  33. data/lib/chamber/filters/translate_secure_keys_filter.rb +5 -24
  34. data/lib/chamber/namespace_set.rb +6 -6
  35. data/lib/chamber/rails.rb +1 -3
  36. data/lib/chamber/rails/railtie.rb +6 -3
  37. data/lib/chamber/settings.rb +34 -32
  38. data/lib/chamber/version.rb +1 -1
  39. data/spec/fixtures/settings.yml +1 -0
  40. data/spec/lib/chamber/commands/files_spec.rb +4 -2
  41. data/spec/lib/chamber/commands/secure_spec.rb +8 -5
  42. data/spec/lib/chamber/commands/show_spec.rb +18 -3
  43. data/spec/lib/chamber/context_resolver_spec.rb +38 -18
  44. data/spec/lib/chamber/file_set_spec.rb +73 -52
  45. data/spec/lib/chamber/file_spec.rb +37 -23
  46. data/spec/lib/chamber/filters/boolean_conversion_filter_spec.rb +35 -33
  47. data/spec/lib/chamber/filters/decryption_filter_spec.rb +142 -21
  48. data/spec/lib/chamber/filters/encryption_filter_spec.rb +51 -19
  49. data/spec/lib/chamber/filters/environment_filter_spec.rb +12 -6
  50. data/spec/lib/chamber/filters/failed_decryption_filter_spec.rb +53 -0
  51. data/spec/lib/chamber/filters/insecure_filter_spec.rb +38 -18
  52. data/spec/lib/chamber/filters/namespace_filter_spec.rb +38 -38
  53. data/spec/lib/chamber/filters/secure_filter_spec.rb +10 -10
  54. data/spec/lib/chamber/filters/translate_secure_keys_filter_spec.rb +9 -6
  55. data/spec/lib/chamber/namespace_set_spec.rb +7 -5
  56. data/spec/lib/chamber/settings_spec.rb +168 -79
  57. data/spec/lib/chamber_spec.rb +72 -71
  58. metadata +22 -21
  59. data/lib/chamber/errors/undecryptable_value_error.rb +0 -6
  60. 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 = %r{\A_secure_}
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
- self.new(options).send(:execute)
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 value.respond_to? :match
32
- if key.match(SECURE_KEY_TOKEN) && !value.match(BASE64_STRING_PATTERN)
33
- encrypted_string = encryption_key.public_encrypt(value)
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
- self.new(options).send(:execute)
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
- ->(key, value, environment_keys) do
67
- { key => execute(value, environment_keys) }
68
- end,
69
- ->(key, value, environment_key) do
70
- { key => (ENV[environment_key] || value) }
71
- end)
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
@@ -9,7 +9,7 @@ class NamespaceFilter
9
9
  end
10
10
 
11
11
  def self.execute(options = {})
12
- self.new(options).send(:execute)
12
+ new(options).send(:execute)
13
13
  end
14
14
 
15
15
  protected
@@ -3,14 +3,14 @@ require 'hashie/mash'
3
3
  module Chamber
4
4
  module Filters
5
5
  class SecureFilter
6
- SECURE_KEY_TOKEN = %r{\A_secure_}
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
- self.new(options).send(:execute)
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 = %r{\A_secure_}
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
- self.new(options).send(:execute)
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
- value = execute(value)
27
- end
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
- self.new(namespace_values)
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
- self.to_a.eql? other.to_a
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?( NamespaceSet) &&
106
- self.namespaces == other.namespaces
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
- (value.respond_to?(:call) ? value.call : value).to_s
155
- end
154
+ (value.respond_to?(:call) ? value.call : value).to_s
155
+ end
156
156
  end
157
157
 
158
158
  def raw_namespaces=(raw_namespaces)
@@ -1,3 +1 @@
1
- if defined?(::Rails)
2
- require 'chamber/rails/railtie'
3
- end
1
+ require 'chamber/rails/railtie' if defined?(::Rails)
@@ -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( :basepath => ::Rails.root.join('config'),
6
- :namespaces => {
7
- :environment => -> { ::Rails.env } })
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
@@ -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
- Filters::NamespaceFilter,
27
- ]
26
+ Filters::NamespaceFilter,
27
+ ]
28
28
  self.post_filters = options[:post_filters] || [
29
- Filters::DecryptionFilter,
30
- Filters::TranslateSecureKeysFilter,
31
- Filters::EnvironmentFilter,
32
- Filters::BooleanConversionFilter,
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
- %Q{#{key.upcase}#{name_value_separator}#{value_surrounder}#{value}#{value_surrounder}}
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, flattened_name_components)
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
- self.to_hash == other.to_hash
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?( Chamber::Settings) &&
207
- self.data == other.data &&
208
- self.namespaces == other.namespaces
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( metadata.merge(
213
- settings: raw_data,
214
- pre_filters: [Filters::SecureFilter]))
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( metadata.merge(
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( metadata.merge(
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 ||= pre_filters.reduce(@raw_data) do |filtered_data, filter|
249
- filter.execute({data: filtered_data}.
250
- merge(metadata))
251
- end
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 ||= post_filters.reduce(raw_data) do |filtered_data, filter|
256
- filter.execute({data: filtered_data}.
257
- merge(metadata))
258
- end
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: self.namespaces,
264
- decryption_key: self.decryption_key,
265
- encryption_key: self.encryption_key,
265
+ namespaces: namespaces,
266
+ decryption_key: decryption_key,
267
+ encryption_key: encryption_key,
266
268
  }
267
269
  end
268
270
 
@@ -1,3 +1,3 @@
1
1
  module Chamber
2
- VERSION = '2.4.0'
2
+ VERSION = '2.7.1'
3
3
  end
@@ -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) { { basepath: rootpath,
9
- rootpath: rootpath } }
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) { { basepath: rootpath,
12
- rootpath: rootpath,
13
- encryption_key: rootpath + '../spec_key',
14
- shell: double.as_null_object } }
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).to match %r{_secure_my_unencrpyted_setting: [A-Za-z0-9\+\/]{342}==}
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