chamber 2.10.2 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
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