chamber 2.10.2 → 2.11.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE.txt +1 -1
  5. data/lib/chamber.rb +3 -6
  6. data/lib/chamber/binary/heroku.rb +1 -0
  7. data/lib/chamber/binary/runner.rb +29 -13
  8. data/lib/chamber/binary/travis.rb +1 -0
  9. data/lib/chamber/commands/base.rb +10 -9
  10. data/lib/chamber/commands/comparable.rb +1 -0
  11. data/lib/chamber/commands/compare.rb +8 -7
  12. data/lib/chamber/commands/files.rb +1 -0
  13. data/lib/chamber/commands/heroku.rb +1 -0
  14. data/lib/chamber/commands/heroku/clear.rb +2 -1
  15. data/lib/chamber/commands/heroku/compare.rb +1 -0
  16. data/lib/chamber/commands/heroku/pull.rb +3 -4
  17. data/lib/chamber/commands/heroku/push.rb +1 -0
  18. data/lib/chamber/commands/initialize.rb +88 -76
  19. data/lib/chamber/commands/securable.rb +3 -2
  20. data/lib/chamber/commands/secure.rb +3 -1
  21. data/lib/chamber/commands/show.rb +7 -6
  22. data/lib/chamber/commands/travis.rb +1 -0
  23. data/lib/chamber/commands/travis/secure.rb +1 -0
  24. data/lib/chamber/configuration.rb +14 -13
  25. data/lib/chamber/context_resolver.rb +52 -55
  26. data/lib/chamber/encryption_methods/none.rb +4 -2
  27. data/lib/chamber/encryption_methods/public_key.rb +4 -2
  28. data/lib/chamber/encryption_methods/ssl.rb +11 -9
  29. data/lib/chamber/errors/decryption_failure.rb +1 -0
  30. data/lib/chamber/file.rb +27 -18
  31. data/lib/chamber/file_set.rb +14 -13
  32. data/lib/chamber/filters/decryption_filter.rb +48 -18
  33. data/lib/chamber/filters/encryption_filter.rb +32 -22
  34. data/lib/chamber/filters/environment_filter.rb +109 -16
  35. data/lib/chamber/filters/failed_decryption_filter.rb +10 -8
  36. data/lib/chamber/filters/insecure_filter.rb +1 -0
  37. data/lib/chamber/filters/namespace_filter.rb +8 -7
  38. data/lib/chamber/filters/secure_filter.rb +10 -9
  39. data/lib/chamber/filters/translate_secure_keys_filter.rb +10 -9
  40. data/lib/chamber/instance.rb +5 -4
  41. data/lib/chamber/key_pair.rb +82 -0
  42. data/lib/chamber/keys/base.rb +64 -0
  43. data/lib/chamber/keys/decryption.rb +41 -0
  44. data/lib/chamber/keys/encryption.rb +41 -0
  45. data/lib/chamber/namespace_set.rb +10 -9
  46. data/lib/chamber/rails.rb +1 -0
  47. data/lib/chamber/rails/railtie.rb +1 -0
  48. data/lib/chamber/rubinius_fix.rb +1 -0
  49. data/lib/chamber/settings.rb +45 -41
  50. data/lib/chamber/types/secured.rb +14 -12
  51. data/lib/chamber/version.rb +2 -1
  52. metadata +28 -27
  53. metadata.gz.sig +0 -0
  54. data/lib/chamber/decryption_key.rb +0 -52
  55. data/lib/chamber/environmentable.rb +0 -27
  56. data/lib/chamber/filters/boolean_conversion_filter.rb +0 -41
@@ -1,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'hashie/mash'
3
4
 
4
5
  module Chamber
5
6
  module Filters
6
7
  class SecureFilter
7
- SECURE_KEY_TOKEN = /\A_secure_/
8
-
9
- def initialize(options = {})
10
- self.data = Hashie::Mash.new(options.fetch(:data))
11
- end
12
-
13
8
  def self.execute(options = {})
14
9
  new(options).__send__(:execute)
15
10
  end
16
11
 
17
- protected
12
+ attr_accessor :data,
13
+ :secure_key_token
18
14
 
19
- attr_accessor :data
15
+ def initialize(options = {})
16
+ self.data = Hashie::Mash.new(options.fetch(:data))
17
+ self.secure_key_token = /\A#{Regexp.escape(options.fetch(:secure_key_prefix))}/
18
+ end
19
+
20
+ protected
20
21
 
21
22
  def execute(raw_data = data)
22
23
  settings = Hashie::Mash.new
@@ -25,7 +26,7 @@ class SecureFilter
25
26
  secure_value = if value.respond_to? :each_pair
26
27
  execute(value)
27
28
  elsif key.respond_to? :match
28
- value if key.match(SECURE_KEY_TOKEN)
29
+ value if key.match(secure_key_token)
29
30
  end
30
31
 
31
32
  settings[key] = secure_value unless secure_value.nil?
@@ -1,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'hashie/mash'
3
4
 
4
5
  module Chamber
5
6
  module Filters
6
7
  class TranslateSecureKeysFilter
7
- SECURE_KEY_TOKEN = /\A_secure_/
8
-
9
- def initialize(options = {})
10
- self.data = options.fetch(:data).dup
11
- end
12
-
13
8
  def self.execute(options = {})
14
9
  new(options).__send__(:execute)
15
10
  end
16
11
 
17
- protected
12
+ attr_accessor :data,
13
+ :secure_key_token
18
14
 
19
- attr_accessor :data
15
+ def initialize(options = {})
16
+ self.data = options.fetch(:data).dup
17
+ self.secure_key_token = /\A#{Regexp.escape(options.fetch(:secure_key_prefix))}/
18
+ end
19
+
20
+ protected
20
21
 
21
22
  def execute(raw_data = data)
22
23
  settings = Hashie::Mash.new
@@ -24,7 +25,7 @@ class TranslateSecureKeysFilter
24
25
  raw_data.each_pair do |key, value|
25
26
  value = execute(value) if value.respond_to? :each_pair
26
27
  key = key.to_s
27
- key = key.sub(SECURE_KEY_TOKEN, '') if key.match(SECURE_KEY_TOKEN)
28
+ key = key.sub(secure_key_token, '') if key.match(secure_key_token)
28
29
 
29
30
  settings[key] = value
30
31
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'chamber/configuration'
3
4
  require 'chamber/file_set'
4
5
  require 'chamber/settings'
@@ -29,21 +30,21 @@ class Instance
29
30
  config = configuration.to_hash.merge(options)
30
31
 
31
32
  Settings.
32
- new(
33
+ new(
33
34
  config.merge(
34
35
  settings: data,
35
36
  pre_filters: [Filters::EncryptionFilter],
36
37
  post_filters: [],
37
38
  ),
38
39
  ).
39
- to_hash
40
+ to_hash
40
41
  end
41
42
 
42
43
  def decrypt(data, options = {})
43
44
  config = configuration.to_hash.merge(options)
44
45
 
45
46
  Settings.
46
- new(
47
+ new(
47
48
  config.merge(
48
49
  settings: data,
49
50
  pre_filters: [Filters::NamespaceFilter],
@@ -53,7 +54,7 @@ class Instance
53
54
  ],
54
55
  ),
55
56
  ).
56
- to_hash
57
+ to_hash
57
58
  end
58
59
 
59
60
  def to_s(options = {})
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'securerandom'
5
+
6
+ module Chamber
7
+ class KeyPair
8
+ attr_accessor :key_file_path,
9
+ :namespace,
10
+ :passphrase
11
+
12
+ def initialize(options = {})
13
+ self.namespace = options[:namespace]
14
+ self.passphrase = options.fetch(:passphrase, SecureRandom.uuid)
15
+ self.key_file_path = Pathname.new(options.fetch(:key_file_path))
16
+ end
17
+
18
+ def encrypted_private_key_filepath
19
+ key_file_path + encrypted_private_key_filename
20
+ end
21
+
22
+ def unencrypted_private_key_filepath
23
+ key_file_path + unencrypted_private_key_filename
24
+ end
25
+
26
+ def public_key_filepath
27
+ key_file_path + public_key_filename
28
+ end
29
+
30
+ def encrypted_private_key_pem
31
+ encrypted_private_key
32
+ end
33
+
34
+ def unencrypted_private_key_pem
35
+ unencrypted_private_key.to_pem
36
+ end
37
+
38
+ def public_key_pem
39
+ public_key.to_pem
40
+ end
41
+
42
+ def encrypted_private_key_filename
43
+ "#{base_key_filename}.enc"
44
+ end
45
+
46
+ def unencrypted_private_key_filename
47
+ "#{base_key_filename}.pem"
48
+ end
49
+
50
+ def public_key_filename
51
+ "#{base_key_filename}.pub.pem"
52
+ end
53
+
54
+ private
55
+
56
+ def encrypted_private_key
57
+ @encrypted_private_key ||= \
58
+ unencrypted_private_key.export(encryption_cipher, passphrase)
59
+ end
60
+
61
+ def unencrypted_private_key
62
+ @unencrypted_private_key ||= OpenSSL::PKey::RSA.new(2048)
63
+ end
64
+
65
+ def public_key
66
+ @public_key ||= unencrypted_private_key.public_key
67
+ end
68
+
69
+ def encryption_cipher
70
+ @encryption_cipher ||= OpenSSL::Cipher.new('AES-128-CBC')
71
+ end
72
+
73
+ def base_key_filename
74
+ @base_key_filename ||= [
75
+ '.chamber',
76
+ namespace,
77
+ ].
78
+ compact.
79
+ join('.')
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chamber
4
+ module Keys
5
+ class Base
6
+ def self.resolve(*args)
7
+ new(*args).resolve
8
+ end
9
+
10
+ attr_accessor :namespaces,
11
+ :rootpath
12
+ attr_reader :filenames
13
+
14
+ def initialize(options = {})
15
+ self.rootpath = Pathname.new(options.fetch(:rootpath))
16
+ self.namespaces = options.fetch(:namespaces)
17
+ self.filenames = options[:filenames]
18
+ end
19
+
20
+ def resolve
21
+ filenames.each_with_object({}) do |filename, memo|
22
+ namespace = namespace_from_filename(filename) || '__default'
23
+ value = key_from_file_contents(filename) ||
24
+ key_from_environment_variable(filename)
25
+
26
+ memo[namespace.downcase.to_sym] = value if value
27
+ end
28
+ end
29
+
30
+ def filenames=(other)
31
+ @filenames = begin
32
+ paths = Array(other).
33
+ map { |o| Pathname.new(o) }.
34
+ compact
35
+
36
+ paths << default_key_file_path if paths.empty?
37
+
38
+ (
39
+ paths +
40
+ generate_key_filenames
41
+ ).
42
+ uniq
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def key_from_file_contents(filename)
49
+ filename.readable? && filename.read
50
+ end
51
+
52
+ def key_from_environment_variable(filename)
53
+ ENV[environment_variable_from_filename(filename)]
54
+ end
55
+
56
+ def namespace_from_filename(filename)
57
+ filename.
58
+ basename.
59
+ to_s.
60
+ match(self.class::NAMESPACE_PATTERN) { |m| m[1].upcase }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'chamber/keys/base'
5
+
6
+ module Chamber
7
+ module Keys
8
+ class Decryption < Chamber::Keys::Base
9
+ NAMESPACE_PATTERN = /
10
+ \A # Beginning of Filename
11
+ \.chamber # Initial Chamber Prefix
12
+ \. # Pre-Namespace Dot
13
+ (\w+) # Namespace
14
+ \.pem # Extension
15
+ \z # End of Filename
16
+ /x
17
+
18
+ private
19
+
20
+ def environment_variable_from_filename(filename)
21
+ [
22
+ 'CHAMBER',
23
+ namespace_from_filename(filename),
24
+ 'KEY',
25
+ ].
26
+ compact.
27
+ join('_')
28
+ end
29
+
30
+ def generate_key_filenames
31
+ namespaces.map do |namespace|
32
+ rootpath + ".chamber.#{namespace}.pem"
33
+ end
34
+ end
35
+
36
+ def default_key_file_path
37
+ Pathname.new(rootpath + '.chamber.pem')
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'chamber/keys/base'
5
+
6
+ module Chamber
7
+ module Keys
8
+ class Encryption < Chamber::Keys::Base
9
+ NAMESPACE_PATTERN = /
10
+ \A # Beginning of Filename
11
+ \.chamber # Initial Chamber Prefix
12
+ \. # Pre-Namespace Dot
13
+ (\w+) # Namespace
14
+ \.pub\.pem # Extension
15
+ \z # End of Filename
16
+ /x
17
+
18
+ private
19
+
20
+ def environment_variable_from_filename(filename)
21
+ [
22
+ 'CHAMBER',
23
+ namespace_from_filename(filename),
24
+ 'PUBLIC_KEY',
25
+ ].
26
+ compact.
27
+ join('_')
28
+ end
29
+
30
+ def generate_key_filenames
31
+ namespaces.map do |namespace|
32
+ rootpath + ".chamber.#{namespace}.pub.pem"
33
+ end
34
+ end
35
+
36
+ def default_key_file_path
37
+ Pathname.new(rootpath + '.chamber.pub.pem')
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'set'
3
4
 
4
5
  ###
@@ -13,13 +14,6 @@ module Chamber
13
14
  class NamespaceSet
14
15
  include Enumerable
15
16
 
16
- ###
17
- # Internal: Creates a new NamespaceSet from arrays, hashes and sets.
18
- #
19
- def initialize(raw_namespaces = {})
20
- self.raw_namespaces = raw_namespaces
21
- end
22
-
23
17
  ###
24
18
  # Internal: Allows for more compact NamespaceSet creation by giving a list of
25
19
  # namespace values.
@@ -34,6 +28,15 @@ class NamespaceSet
34
28
  new(namespace_values)
35
29
  end
36
30
 
31
+ attr_reader :raw_namespaces
32
+
33
+ ###
34
+ # Internal: Creates a new NamespaceSet from arrays, hashes and sets.
35
+ #
36
+ def initialize(raw_namespaces = {})
37
+ self.raw_namespaces = raw_namespaces
38
+ end
39
+
37
40
  ###
38
41
  # Internal: Allows a NamespaceSet to be combined with some other array-like
39
42
  # object.
@@ -109,8 +112,6 @@ class NamespaceSet
109
112
 
110
113
  protected
111
114
 
112
- attr_accessor :raw_namespaces
113
-
114
115
  ###
115
116
  # Internal: Sets the namespaces for the set from a variety of objects and
116
117
  # processes them by checking to see if they can be 'called'.
@@ -1,2 +1,3 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'chamber/rails/railtie' if defined?(::Rails)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'socket'
3
4
 
4
5
  module Chamber
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'pathname'
3
4
 
4
5
  unless Pathname.instance_methods.include?(:write)
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'hashie/mash'
3
4
  require 'chamber/namespace_set'
4
5
  require 'chamber/filters/namespace_filter'
5
6
  require 'chamber/filters/encryption_filter'
6
7
  require 'chamber/filters/decryption_filter'
7
8
  require 'chamber/filters/environment_filter'
8
- require 'chamber/filters/boolean_conversion_filter'
9
9
  require 'chamber/filters/secure_filter'
10
10
  require 'chamber/filters/translate_secure_keys_filter'
11
11
  require 'chamber/filters/insecure_filter'
@@ -16,24 +16,29 @@ require 'chamber/filters/failed_decryption_filter'
16
16
  #
17
17
  module Chamber
18
18
  class Settings
19
- attr_reader :namespaces
19
+ attr_accessor :pre_filters,
20
+ :post_filters,
21
+ :encryption_keys,
22
+ :decryption_keys
23
+ attr_reader :namespaces
20
24
 
25
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/LineLength
21
26
  def initialize(options = {})
22
- self.namespaces = options[:namespaces] || []
23
- self.raw_data = options[:settings] || {}
24
- self.decryption_key = options[:decryption_key]
25
- self.encryption_key = options[:encryption_key]
26
- self.pre_filters = options[:pre_filters] || [
27
- Filters::NamespaceFilter,
28
- ]
29
- self.post_filters = options[:post_filters] || [
30
- Filters::DecryptionFilter,
31
- Filters::EnvironmentFilter,
32
- Filters::FailedDecryptionFilter,
33
- Filters::BooleanConversionFilter,
34
- Filters::TranslateSecureKeysFilter,
35
- ]
27
+ self.namespaces = options[:namespaces] || []
28
+ self.raw_data = options[:settings] || {}
29
+ self.decryption_keys = options[:decryption_keys] || {}
30
+ self.encryption_keys = options[:encryption_keys] || {}
31
+ self.pre_filters = options[:pre_filters] || [
32
+ Filters::NamespaceFilter,
33
+ ]
34
+ self.post_filters = options[:post_filters] || [
35
+ Filters::DecryptionFilter,
36
+ Filters::EnvironmentFilter,
37
+ Filters::FailedDecryptionFilter,
38
+ Filters::TranslateSecureKeysFilter,
39
+ ]
36
40
  end
41
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/LineLength
37
42
 
38
43
  ###
39
44
  # Internal: Converts a Settings object into a hash that is compatible as an
@@ -183,12 +188,14 @@ class Settings
183
188
  Settings.new(settings: other)
184
189
  end
185
190
 
191
+ # rubocop:disable Metrics/LineLength
186
192
  Settings.new(
187
- encryption_key: encryption_key || other_settings.encryption_key,
188
- decryption_key: decryption_key || other_settings.decryption_key,
189
- namespaces: (namespaces + other_settings.namespaces),
190
- settings: raw_data.merge(other_settings.raw_data),
193
+ encryption_keys: encryption_keys.any? ? encryption_keys : other_settings.encryption_keys,
194
+ decryption_keys: decryption_keys.any? ? decryption_keys : other_settings.decryption_keys,
195
+ namespaces: (namespaces + other_settings.namespaces),
196
+ settings: raw_data.merge(other_settings.raw_data),
191
197
  )
198
+ # rubocop:enable Metrics/LineLength
192
199
  end
193
200
 
194
201
  ###
@@ -235,13 +242,17 @@ class Settings
235
242
  ))
236
243
  end
237
244
 
238
- protected
245
+ def method_missing(name, *args)
246
+ return data.public_send(name, *args) if data.respond_to?(name)
239
247
 
240
- attr_accessor :pre_filters,
241
- :post_filters,
242
- :encryption_key,
243
- :decryption_key,
244
- :raw_data
248
+ super
249
+ end
250
+
251
+ def respond_to_missing?(name, include_private = false)
252
+ data.respond_to?(name, include_private)
253
+ end
254
+
255
+ protected
245
256
 
246
257
  def raw_data=(new_raw_data)
247
258
  @raw_data = Hashie::Mash.new(new_raw_data)
@@ -265,24 +276,17 @@ class Settings
265
276
  end
266
277
  end
267
278
 
279
+ def secure_key_prefix
280
+ '_secure_'
281
+ end
282
+
268
283
  def metadata
269
284
  {
270
- namespaces: namespaces,
271
- decryption_key: decryption_key,
272
- encryption_key: encryption_key,
285
+ decryption_keys: decryption_keys,
286
+ encryption_keys: encryption_keys,
287
+ namespaces: namespaces,
288
+ secure_key_prefix: secure_key_prefix,
273
289
  }
274
290
  end
275
-
276
- public
277
-
278
- def method_missing(name, *args)
279
- return data.public_send(name, *args) if data.respond_to?(name)
280
-
281
- super
282
- end
283
-
284
- def respond_to_missing?(name, include_private = false)
285
- data.respond_to?(name, include_private)
286
- end
287
291
  end
288
292
  end