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,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'shellwords'
3
4
  require 'chamber/instance'
4
5
 
@@ -9,8 +10,8 @@ module Securable
9
10
  super
10
11
 
11
12
  ignored_settings_options = options.
12
- merge(files: ignored_settings_filepaths).
13
- reject { |k, _v| k == 'basepath' }
13
+ merge(files: ignored_settings_filepaths).
14
+ reject { |k, _v| k == 'basepath' }
14
15
  self.ignored_settings_instance = Chamber::Instance.new(ignored_settings_options)
15
16
  self.current_settings_instance = Chamber::Instance.new(options)
16
17
  self.only_sensitive = options[:only_sensitive]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'chamber/commands/base'
4
+ require 'chamber/commands/securable'
3
5
 
4
6
  module Chamber
5
7
  module Commands
@@ -12,7 +14,7 @@ class Secure < Chamber::Commands::Base
12
14
 
13
15
  def call
14
16
  disable_warnings do
15
- insecure_environment_variables.each do |key, _value|
17
+ insecure_environment_variables.each_key do |key|
16
18
  if dry_run
17
19
  shell.say_status 'encrypt', key, :blue
18
20
  else
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'pp'
3
4
  require 'chamber/commands/base'
4
5
 
5
6
  module Chamber
6
7
  module Commands
7
8
  class Show < Chamber::Commands::Base
9
+ attr_accessor :as_env,
10
+ :only_sensitive
11
+
8
12
  def initialize(options = {})
9
13
  super
10
14
 
@@ -17,17 +21,14 @@ class Show < Chamber::Commands::Base
17
21
  settings.to_s(pair_separator: "\n")
18
22
  else
19
23
  PP.
20
- pp(settings.to_hash, StringIO.new, 60).
21
- string.
22
- chomp
24
+ pp(settings.to_hash, StringIO.new, 60).
25
+ string.
26
+ chomp
23
27
  end
24
28
  end
25
29
 
26
30
  protected
27
31
 
28
- attr_accessor :as_env,
29
- :only_sensitive
30
-
31
32
  def settings
32
33
  @settings ||= if only_sensitive
33
34
  chamber.settings.securable
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'bundler'
3
4
 
4
5
  module Chamber
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'chamber/commands/base'
3
4
  require 'chamber/commands/travis'
4
5
  require 'chamber/commands/securable'
@@ -1,31 +1,32 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'chamber/context_resolver'
3
4
 
4
5
  module Chamber
5
6
  class Configuration
6
7
  attr_accessor :basepath,
7
- :decryption_key,
8
- :encryption_key,
8
+ :decryption_keys,
9
+ :encryption_keys,
9
10
  :files,
10
11
  :namespaces
11
12
 
12
13
  def initialize(options = {})
13
- options = ContextResolver.resolve(options)
14
+ options = ContextResolver.resolve(options)
14
15
 
15
- self.basepath = options[:basepath]
16
- self.namespaces = options[:namespaces]
17
- self.decryption_key = options[:decryption_key]
18
- self.encryption_key = options[:encryption_key]
19
- self.files = options[:files]
16
+ self.basepath = options.fetch(:basepath)
17
+ self.namespaces = options.fetch(:namespaces)
18
+ self.decryption_keys = options.fetch(:decryption_keys)
19
+ self.encryption_keys = options.fetch(:encryption_keys)
20
+ self.files = options.fetch(:files)
20
21
  end
21
22
 
22
23
  def to_hash
23
24
  {
24
- basepath: basepath,
25
- decryption_key: decryption_key,
26
- encryption_key: encryption_key,
27
- files: files,
28
- namespaces: namespaces,
25
+ basepath: basepath,
26
+ decryption_keys: decryption_keys,
27
+ encryption_keys: encryption_keys,
28
+ files: files,
29
+ namespaces: namespaces,
29
30
  }
30
31
  end
31
32
  end
@@ -1,64 +1,50 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'pathname'
3
4
  require 'socket'
4
5
  require 'hashie/mash'
5
- require 'chamber/decryption_key'
6
+
7
+ require 'chamber/keys/decryption'
8
+ require 'chamber/keys/encryption'
6
9
 
7
10
  module Chamber
8
11
  class ContextResolver
12
+ attr_accessor :options
13
+
9
14
  def initialize(options = {})
10
- self.options = Hashie::Mash.new(options)
15
+ self.options = options.dup
11
16
  end
12
17
 
13
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
14
- # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
18
+ # rubocop:disable Metrics/AbcSize, Metrics/LineLength
15
19
  def resolve
16
- options[:rootpath] ||= Pathname.pwd
17
- options[:rootpath] = Pathname.new(options[:rootpath])
18
- options[:encryption_key] = resolve_encryption_key(options[:encryption_key])
19
- options[:decryption_key] = resolve_decryption_key(options[:decryption_key])
20
- options[:namespaces] ||= []
21
- options[:preset] ||= resolve_preset
20
+ options[:rootpath] ||= Pathname.pwd
21
+ options[:rootpath] = Pathname.new(options[:rootpath])
22
+ options[:namespaces] ||= []
23
+ options[:preset] ||= resolve_preset
22
24
 
23
25
  if %w{rails rails-engine}.include?(options[:preset])
24
- if options[:preset] == 'rails-engine'
25
- engine_spec_dummy_directory = options[:rootpath] + 'spec' + 'dummy'
26
- engine_test_dummy_directory = options[:rootpath] + 'test' + 'dummy'
27
-
28
- options[:rootpath] = if (engine_spec_dummy_directory + 'config.ru').exist?
29
- engine_spec_dummy_directory
30
- elsif (engine_test_dummy_directory + 'config.ru').exist?
31
- engine_test_dummy_directory
32
- end
33
- end
34
-
35
- options[:basepath] ||= options[:rootpath] + 'config'
36
-
37
- if options[:namespaces] == []
38
- require options[:rootpath].join('config', 'application').to_s
39
-
40
- options[:namespaces] = [
41
- ::Rails.env,
42
- Socket.gethostname,
43
- ]
44
- end
26
+ options[:rootpath] = detect_engine_root if options[:preset] == 'rails-engine'
27
+ options[:namespaces] = load_rails_default_namespaces(options[:rootpath]) if options[:namespaces] == []
28
+ options[:basepath] ||= options[:rootpath] + 'config'
45
29
  else
46
- options[:basepath] ||= options[:rootpath]
30
+ options[:basepath] ||= options[:rootpath]
47
31
  end
48
32
 
49
- options[:basepath] = Pathname.new(options[:basepath])
50
-
51
- options[:files] ||= [
52
- options[:basepath] + 'settings*.yml',
53
- options[:basepath] + 'settings',
54
- ]
33
+ options[:encryption_keys] = Keys::Encryption.resolve(filenames: options[:encryption_keys],
34
+ namespaces: options[:namespaces],
35
+ rootpath: options[:rootpath])
36
+ options[:decryption_keys] = Keys::Decryption.resolve(filenames: options[:decryption_keys],
37
+ namespaces: options[:namespaces],
38
+ rootpath: options[:rootpath])
39
+ options[:basepath] = Pathname.new(options[:basepath])
40
+ options[:files] ||= [
41
+ options[:basepath] + 'settings*.yml',
42
+ options[:basepath] + 'settings',
43
+ ]
55
44
 
56
- options
57
- rescue LoadError
58
45
  options
59
46
  end
60
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
61
- # rubocop:enable Metrics/PerceivedComplexity, Metrics/MethodLength
47
+ # rubocop:enable Metrics/AbcSize, Metrics/LineLength
62
48
 
63
49
  def self.resolve(options = {})
64
50
  new(options).resolve
@@ -66,8 +52,6 @@ class ContextResolver
66
52
 
67
53
  protected
68
54
 
69
- attr_accessor :options
70
-
71
55
  def resolve_preset
72
56
  if in_a_rails_project?
73
57
  'rails'
@@ -76,17 +60,6 @@ class ContextResolver
76
60
  end
77
61
  end
78
62
 
79
- def resolve_encryption_key(key)
80
- key ||= options[:rootpath] + '.chamber.pub.pem'
81
-
82
- key if Pathname.new(key).readable?
83
- end
84
-
85
- def resolve_decryption_key(key)
86
- DecryptionKey.resolve(filename: key,
87
- rootpath: options[:rootpath])
88
- end
89
-
90
63
  def in_a_rails_project?
91
64
  (options[:rootpath] + 'config.ru').exist? &&
92
65
  rails_executable_exists?
@@ -102,5 +75,29 @@ class ContextResolver
102
75
  options[:rootpath].join('script', 'rails').exist? ||
103
76
  options[:rootpath].join('script', 'console').exist?
104
77
  end
78
+
79
+ private
80
+
81
+ def detect_engine_root
82
+ engine_spec_dummy_directory = options[:rootpath] + 'spec' + 'dummy'
83
+ engine_test_dummy_directory = options[:rootpath] + 'test' + 'dummy'
84
+
85
+ if (engine_spec_dummy_directory + 'config.ru').exist?
86
+ engine_spec_dummy_directory
87
+ elsif (engine_test_dummy_directory + 'config.ru').exist?
88
+ engine_test_dummy_directory
89
+ end
90
+ end
91
+
92
+ def load_rails_default_namespaces(root)
93
+ require root.join('config', 'application').to_s
94
+
95
+ [
96
+ ::Rails.env,
97
+ Socket.gethostname,
98
+ ]
99
+ rescue LoadError
100
+ []
101
+ end
105
102
  end
106
103
  end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chamber
2
4
  module EncryptionMethods
3
5
  class None
4
- def self.encrypt(_key, value, _encryption_key)
6
+ def self.encrypt(_key, value, _encryption_keys)
5
7
  value
6
8
  end
7
9
 
8
- def self.decrypt(key, value, _decryption_key)
10
+ def self.decrypt(key, value, _decryption_keys)
9
11
  return value if value.nil?
10
12
 
11
13
  warn "WARNING: It appears that you would like to keep your information for #{key} " \
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chamber
2
4
  module EncryptionMethods
3
5
  class PublicKey
4
- def self.encrypt(_key, value, encryption_key)
6
+ def self.encrypt(_key, value, encryption_keys)
5
7
  value = YAML.dump(value)
6
- encrypted_string = encryption_key.public_encrypt(value)
8
+ encrypted_string = encryption_keys.public_encrypt(value)
7
9
 
8
10
  Base64.strict_encode64(encrypted_string)
9
11
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Chamber
2
4
  module EncryptionMethods
3
5
  class Ssl
4
6
  LARGE_DATA_STRING_PATTERN = %r{\A([A-Za-z0-9\+\/#]*\={0,2})#([A-Za-z0-9\+\/#]*\={0,2})#([A-Za-z0-9\+\/#]*\={0,2})\z} # rubocop:disable Metrics/LineLength
5
7
 
6
- def self.encrypt(_key, value, encryption_key)
8
+ def self.encrypt(_key, value, encryption_keys)
7
9
  value = YAML.dump(value)
8
10
  cipher = OpenSSL::Cipher.new('AES-128-CBC')
9
11
  cipher.encrypt
@@ -14,7 +16,7 @@ class Ssl
14
16
  encrypted_data = cipher.update(value) + cipher.final
15
17
 
16
18
  # encrypt the key with the public key
17
- encrypted_key = encryption_key.public_encrypt(symmetric_key)
19
+ encrypted_key = encryption_keys.public_encrypt(symmetric_key)
18
20
 
19
21
  # assemble the resulting Base64 encoded data, the key
20
22
  Base64.strict_encode64(encrypted_key) + '#' +
@@ -22,17 +24,17 @@ class Ssl
22
24
  Base64.strict_encode64(encrypted_data)
23
25
  end
24
26
 
25
- def self.decrypt(key, value, decryption_key)
26
- if decryption_key.nil?
27
+ def self.decrypt(key, value, decryption_keys)
28
+ if decryption_keys.nil?
27
29
  value
28
30
  else
29
31
  key, iv, decoded_string = value.
30
- match(LARGE_DATA_STRING_PATTERN).
31
- captures.
32
- map do |part|
33
- Base64.strict_decode64(part)
32
+ match(LARGE_DATA_STRING_PATTERN).
33
+ captures.
34
+ map do |part|
35
+ Base64.strict_decode64(part)
34
36
  end
35
- key = decryption_key.private_decrypt(key)
37
+ key = decryption_keys.private_decrypt(key)
36
38
 
37
39
  cipher_dec = OpenSSL::Cipher.new('AES-128-CBC')
38
40
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Chamber
3
4
  module Errors
4
5
  class DecryptionFailure < RuntimeError
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'pathname'
3
4
  require 'yaml'
4
5
  require 'erb'
@@ -9,6 +10,10 @@ require 'erb'
9
10
  #
10
11
  module Chamber
11
12
  class File < Pathname
13
+ attr_accessor :namespaces,
14
+ :decryption_keys,
15
+ :encryption_keys
16
+
12
17
  ###
13
18
  # Internal: Creates a settings file representing a path to a file on the
14
19
  # filesystem.
@@ -37,9 +42,9 @@ class File < Pathname
37
42
  # # => <Chamber::File>
38
43
  #
39
44
  def initialize(options = {})
40
- self.namespaces = options[:namespaces] || {}
41
- self.decryption_key = options[:decryption_key]
42
- self.encryption_key = options[:encryption_key]
45
+ self.namespaces = options[:namespaces] || {}
46
+ self.decryption_keys = options[:decryption_keys] || {}
47
+ self.encryption_keys = options[:encryption_keys] || {}
43
48
 
44
49
  super options.fetch(:path)
45
50
  end
@@ -64,12 +69,13 @@ class File < Pathname
64
69
  # ```
65
70
  #
66
71
  def to_settings
67
- @data ||= Settings.new(settings: file_contents_hash,
68
- namespaces: namespaces,
69
- decryption_key: decryption_key,
70
- encryption_key: encryption_key)
72
+ @data ||= Settings.new(settings: file_contents_hash,
73
+ namespaces: namespaces,
74
+ decryption_keys: decryption_keys,
75
+ encryption_keys: encryption_keys)
71
76
  end
72
77
 
78
+ # rubocop:disable Metrics/LineLength
73
79
  def secure
74
80
  insecure_settings = to_settings.insecure.to_flattened_name_hash
75
81
  secure_settings = to_settings.insecure.secure.to_flattened_name_hash
@@ -82,28 +88,31 @@ class File < Pathname
82
88
  escaped_value = Regexp.escape(value)
83
89
 
84
90
  file_contents.
85
- sub!(
86
- /^(\s*)_secure_#{escaped_name}(\s*):(\s*)['"]?#{escaped_value}['"]?$/,
87
- "\\1_secure_#{name_pieces.last}\\2:\\3#{secure_value}",
91
+ sub!(
92
+ /^(\s*)#{secure_prefix_pattern}#{escaped_name}(\s*):(\s*)['"]?#{escaped_value}['"]?$/,
93
+ "\\1#{secure_prefix}#{name_pieces.last}\\2:\\3#{secure_value}",
88
94
  )
89
95
 
90
96
  file_contents.
91
- sub!(
92
- /^(\s*)_secure_#{escaped_name}(\s*):(\s*)\|((?:\n\1\s{2}.*)+)/,
93
- "\\1_secure_#{name_pieces.last}\\2:\\3#{secure_value}",
97
+ sub!(
98
+ /^(\s*)#{secure_prefix_pattern}#{escaped_name}(\s*):(\s*)\|((?:\n\1\s{2}.*)+)/,
99
+ "\\1#{secure_prefix}#{name_pieces.last}\\2:\\3#{secure_value}",
94
100
  )
95
101
  end
96
102
 
97
103
  write(file_contents)
98
104
  end
105
+ # rubocop:enable Metrics/LineLength
99
106
 
100
- protected
107
+ private
101
108
 
102
- attr_accessor :namespaces,
103
- :decryption_key,
104
- :encryption_key
109
+ def secure_prefix
110
+ '_secure_'
111
+ end
105
112
 
106
- private
113
+ def secure_prefix_pattern
114
+ @secure_prefix_pattern ||= Regexp.escape(secure_prefix)
115
+ end
107
116
 
108
117
  def file_contents_hash
109
118
  file_contents = read