chamber 2.12.3 → 2.14.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 (57) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +101 -26
  5. data/lib/chamber.rb +82 -10
  6. data/lib/chamber/adapters/cloud/circle_ci.rb +85 -0
  7. data/lib/chamber/adapters/cloud/heroku.rb +74 -0
  8. data/lib/chamber/binary/circle_ci.rb +122 -0
  9. data/lib/chamber/binary/heroku.rb +45 -16
  10. data/lib/chamber/binary/runner.rb +42 -26
  11. data/lib/chamber/binary/travis.rb +5 -3
  12. data/lib/chamber/commands/base.rb +10 -16
  13. data/lib/chamber/commands/cloud/base.rb +35 -0
  14. data/lib/chamber/commands/{heroku → cloud}/clear.rb +6 -8
  15. data/lib/chamber/commands/cloud/compare.rb +26 -0
  16. data/lib/chamber/commands/cloud/pull.rb +29 -0
  17. data/lib/chamber/commands/cloud/push.rb +44 -0
  18. data/lib/chamber/commands/comparable.rb +2 -2
  19. data/lib/chamber/commands/compare.rb +6 -9
  20. data/lib/chamber/commands/initialize.rb +26 -22
  21. data/lib/chamber/commands/securable.rb +10 -10
  22. data/lib/chamber/commands/secure.rb +2 -2
  23. data/lib/chamber/commands/show.rb +8 -8
  24. data/lib/chamber/commands/sign.rb +2 -2
  25. data/lib/chamber/commands/verify.rb +2 -2
  26. data/lib/chamber/configuration.rb +8 -3
  27. data/lib/chamber/context_resolver.rb +16 -7
  28. data/lib/chamber/encryption_methods/ssl.rb +21 -12
  29. data/lib/chamber/file.rb +22 -20
  30. data/lib/chamber/file_set.rb +21 -11
  31. data/lib/chamber/files/signature.rb +31 -23
  32. data/lib/chamber/filters/decryption_filter.rb +13 -11
  33. data/lib/chamber/filters/encryption_filter.rb +17 -8
  34. data/lib/chamber/filters/environment_filter.rb +12 -14
  35. data/lib/chamber/filters/failed_decryption_filter.rb +6 -6
  36. data/lib/chamber/filters/insecure_filter.rb +12 -3
  37. data/lib/chamber/filters/namespace_filter.rb +5 -5
  38. data/lib/chamber/filters/secure_filter.rb +5 -5
  39. data/lib/chamber/filters/translate_secure_keys_filter.rb +5 -5
  40. data/lib/chamber/instance.rb +54 -30
  41. data/lib/chamber/integrations/rails.rb +1 -1
  42. data/lib/chamber/integrations/sinatra.rb +6 -6
  43. data/lib/chamber/key_pair.rb +8 -8
  44. data/lib/chamber/keys/base.rb +35 -41
  45. data/lib/chamber/keys/decryption.rb +8 -14
  46. data/lib/chamber/keys/encryption.rb +8 -14
  47. data/lib/chamber/namespace_set.rb +2 -4
  48. data/lib/chamber/settings.rb +86 -56
  49. data/lib/chamber/types/secured.rb +8 -10
  50. data/lib/chamber/version.rb +1 -1
  51. data/templates/settings.yml +2 -0
  52. metadata +51 -41
  53. metadata.gz.sig +0 -0
  54. data/lib/chamber/commands/heroku.rb +0 -31
  55. data/lib/chamber/commands/heroku/compare.rb +0 -33
  56. data/lib/chamber/commands/heroku/pull.rb +0 -30
  57. data/lib/chamber/commands/heroku/push.rb +0 -27
@@ -9,7 +9,7 @@ class Rails < ::Rails::Railtie
9
9
  Chamber.load(basepath: ::Rails.root.join('config'),
10
10
  namespaces: {
11
11
  environment: -> { ::Rails.env },
12
- hostname: -> { Socket.gethostname },
12
+ hostname: -> { ::Socket.gethostname },
13
13
  })
14
14
  end
15
15
  end
@@ -16,12 +16,12 @@ module Sinatra
16
16
  end
17
17
 
18
18
  Chamber.load(
19
- basepath: root,
20
- namespaces: {
21
- environment: -> { env },
22
- hostname: -> { Socket.gethostname },
23
- },
24
- )
19
+ basepath: root,
20
+ namespaces: {
21
+ environment: -> { env },
22
+ hostname: -> { Socket.gethostname },
23
+ },
24
+ )
25
25
  end
26
26
  end
27
27
  end
@@ -9,10 +9,10 @@ class KeyPair
9
9
  :namespace,
10
10
  :passphrase
11
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))
12
+ def initialize(key_file_path:, namespace: nil, passphrase: ::SecureRandom.uuid)
13
+ self.namespace = namespace
14
+ self.passphrase = passphrase
15
+ self.key_file_path = Pathname.new(key_file_path)
16
16
  end
17
17
 
18
18
  def encrypted_private_key_passphrase_filepath
@@ -77,10 +77,10 @@ class KeyPair
77
77
  def base_key_filename
78
78
  @base_key_filename ||= [
79
79
  '.chamber',
80
- namespace,
81
- ].
82
- compact.
83
- join('.')
80
+ namespace ? namespace.tr('-.', '') : nil,
81
+ ]
82
+ .compact
83
+ .join('.')
84
84
  end
85
85
  end
86
86
  end
@@ -3,73 +3,67 @@
3
3
  module Chamber
4
4
  module Keys
5
5
  class Base
6
- def self.resolve(*args)
7
- new(*args).resolve
6
+ def self.resolve(**args)
7
+ new(**args).resolve
8
8
  end
9
9
 
10
10
  attr_accessor :rootpath
11
11
  attr_reader :filenames,
12
12
  :namespaces
13
13
 
14
- def initialize(options = {})
15
- self.rootpath = Pathname.new(options.fetch(:rootpath))
16
- self.namespaces = options.fetch(:namespaces)
17
- self.filenames = options[:filenames]
14
+ def initialize(rootpath:, namespaces:, filenames: nil)
15
+ self.rootpath = Pathname.new(rootpath)
16
+ self.namespaces = namespaces
17
+ self.filenames = filenames
18
18
  end
19
19
 
20
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)
21
+ key_paths.each_with_object({}) do |path, memo|
22
+ namespace = namespace_from_path(path) || '__default'
23
+ value = path.readable? ? path.read : ENV[environment_variable_from_path(path)]
25
24
 
26
25
  memo[namespace.downcase.to_sym] = value if value
27
26
  end
28
27
  end
29
28
 
30
- def filenames=(other)
31
- @filenames = begin
32
- paths = Array(other).
33
- map { |o| Pathname.new(o) }.
34
- compact
29
+ def as_environment_variables
30
+ key_paths.select(&:readable?).each_with_object({}) do |path, memo|
31
+ memo[environment_variable_from_path(path)] = path.read
32
+ end
33
+ end
35
34
 
36
- paths << default_key_file_path if paths.empty?
35
+ private
37
36
 
38
- (
39
- paths +
40
- generate_key_filenames
41
- ).
42
- uniq
43
- end
37
+ def key_paths
38
+ @key_paths = (filenames.any? ? filenames : [default_key_file_path]) +
39
+ namespaces.map { |n| namespace_to_key_path(n) }
44
40
  end
45
41
 
46
- private
42
+ # rubocop:disable Performance/ChainArrayAllocation
43
+ def filenames=(other)
44
+ @filenames = Array(other)
45
+ .map { |o| Pathname.new(o) }
46
+ .compact
47
+ end
48
+ # rubocop:enable Performance/ChainArrayAllocation
47
49
 
48
50
  def namespaces=(other)
49
- @namespaces ||= begin
50
- keys = if other.respond_to?(:keys)
51
- other.keys.map(&:to_s)
52
- else
53
- other
54
- end
55
-
56
- keys + %w{signature}
57
- end
51
+ @namespaces = other + %w{signature}
58
52
  end
59
53
 
60
- def key_from_file_contents(filename)
61
- filename.readable? && filename.read
54
+ def namespace_from_path(path)
55
+ path
56
+ .basename
57
+ .to_s
58
+ .match(self.class::NAMESPACE_PATTERN) { |m| m[1].upcase }
62
59
  end
63
60
 
64
- def key_from_environment_variable(filename)
65
- ENV[environment_variable_from_filename(filename)]
61
+ def namespace_to_key_path(namespace)
62
+ rootpath + ".chamber.#{namespace.to_s.tr('.-', '')}#{key_filename_extension}"
66
63
  end
67
64
 
68
- def namespace_from_filename(filename)
69
- filename.
70
- basename.
71
- to_s.
72
- match(self.class::NAMESPACE_PATTERN) { |m| m[1].upcase }
65
+ def default_key_file_path
66
+ Pathname.new(rootpath + ".chamber#{key_filename_extension}")
73
67
  end
74
68
  end
75
69
  end
@@ -13,28 +13,22 @@ class Decryption < Chamber::Keys::Base
13
13
  (\w+) # Namespace
14
14
  \.pem # Extension
15
15
  \z # End of Filename
16
- /x
16
+ /x.freeze
17
17
 
18
18
  private
19
19
 
20
- def environment_variable_from_filename(filename)
20
+ def environment_variable_from_path(path)
21
21
  [
22
22
  'CHAMBER',
23
- namespace_from_filename(filename),
23
+ namespace_from_path(path),
24
24
  'KEY',
25
- ].
26
- compact.
27
- join('_')
25
+ ]
26
+ .compact
27
+ .join('_')
28
28
  end
29
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')
30
+ def key_filename_extension
31
+ '.pem'
38
32
  end
39
33
  end
40
34
  end
@@ -13,28 +13,22 @@ class Encryption < Chamber::Keys::Base
13
13
  (\w+) # Namespace
14
14
  \.pub\.pem # Extension
15
15
  \z # End of Filename
16
- /x
16
+ /x.freeze
17
17
 
18
18
  private
19
19
 
20
- def environment_variable_from_filename(filename)
20
+ def environment_variable_from_path(path)
21
21
  [
22
22
  'CHAMBER',
23
- namespace_from_filename(filename),
23
+ namespace_from_path(path),
24
24
  'PUBLIC_KEY',
25
- ].
26
- compact.
27
- join('_')
25
+ ]
26
+ .compact
27
+ .join('_')
28
28
  end
29
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')
30
+ def key_filename_extension
31
+ '.pub.pem'
38
32
  end
39
33
  end
40
34
  end
@@ -71,10 +71,8 @@ class NamespaceSet
71
71
  # Internal: Iterates over each namespace value and allows it to be used in
72
72
  # a block.
73
73
  #
74
- def each
75
- namespaces.each do |namespace|
76
- yield namespace
77
- end
74
+ def each(&block)
75
+ namespaces.each(&block)
78
76
  end
79
77
 
80
78
  ###
@@ -16,29 +16,40 @@ require 'chamber/filters/failed_decryption_filter'
16
16
  #
17
17
  module Chamber
18
18
  class Settings
19
- attr_accessor :pre_filters,
20
- :post_filters,
19
+ attr_accessor :decryption_keys,
21
20
  :encryption_keys,
22
- :decryption_keys
21
+ :post_filters,
22
+ :pre_filters,
23
+ :secure_key_prefix
23
24
  attr_reader :namespaces
24
25
 
25
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/LineLength
26
- def initialize(options = {})
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
- ]
26
+ # rubocop:disable Metrics/ParameterLists
27
+ def initialize(
28
+ decryption_keys: {},
29
+ encryption_keys: {},
30
+ namespaces: [],
31
+ pre_filters: [
32
+ Filters::NamespaceFilter,
33
+ ],
34
+ post_filters: [
35
+ Filters::DecryptionFilter,
36
+ Filters::EnvironmentFilter,
37
+ Filters::FailedDecryptionFilter,
38
+ Filters::TranslateSecureKeysFilter,
39
+ ],
40
+ secure_key_prefix: '_secure_',
41
+ settings: {},
42
+ **_args
43
+ )
44
+ self.decryption_keys = decryption_keys
45
+ self.encryption_keys = encryption_keys
46
+ self.namespaces = namespaces
47
+ self.post_filters = post_filters
48
+ self.pre_filters = pre_filters
49
+ self.raw_data = settings
50
+ self.secure_key_prefix = secure_key_prefix
40
51
  end
41
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/LineLength
52
+ # rubocop:enable Metrics/ParameterLists
42
53
 
43
54
  ###
44
55
  # Internal: Converts a Settings object into a hash that is compatible as an
@@ -79,15 +90,11 @@ class Settings
79
90
  # } ).to_s
80
91
  # # => 'MY_KEY="my value" MY_OTHER_KEY="my other value"'
81
92
  #
82
- def to_s(options = {})
83
- hierarchical_separator = options[:hierarchical_separator] || '_'
84
- pair_separator = options[:pair_separator] || ' '
85
- value_surrounder = options[:value_surrounder] || '"'
86
- name_value_separator = options[:name_value_separator] || '='
87
-
88
- concatenated_name_hash = to_concatenated_name_hash(hierarchical_separator)
89
-
90
- pairs = concatenated_name_hash.to_a.map do |key, value|
93
+ def to_s(hierarchical_separator: '_',
94
+ pair_separator: ' ',
95
+ value_surrounder: '"',
96
+ name_value_separator: '=')
97
+ pairs = to_concatenated_name_hash(hierarchical_separator).to_a.map do |key, value|
91
98
  "#{key.upcase}#{name_value_separator}#{value_surrounder}#{value}#{value_surrounder}"
92
99
  end
93
100
 
@@ -182,20 +189,21 @@ class Settings
182
189
  # Returns a new Settings object
183
190
  #
184
191
  def merge(other)
185
- other_settings = if other.is_a? Settings
192
+ other_settings = case other
193
+ when Settings
186
194
  other
187
- elsif other.is_a? Hash
195
+ when Hash
188
196
  Settings.new(settings: other)
189
197
  end
190
198
 
191
- # rubocop:disable Metrics/LineLength
199
+ # rubocop:disable Layout/LineLength
192
200
  Settings.new(
193
201
  encryption_keys: encryption_keys.any? ? encryption_keys : other_settings.encryption_keys,
194
202
  decryption_keys: decryption_keys.any? ? decryption_keys : other_settings.decryption_keys,
195
203
  namespaces: (namespaces + other_settings.namespaces),
196
204
  settings: raw_data.merge(other_settings.raw_data),
197
205
  )
198
- # rubocop:enable Metrics/LineLength
206
+ # rubocop:enable Layout/LineLength
199
207
  end
200
208
 
201
209
  ###
@@ -219,33 +227,59 @@ class Settings
219
227
  namespaces == other.namespaces
220
228
  end
221
229
 
230
+ def [](key)
231
+ warn "WARNING: Bracket access will require strings instead of symbols in Chamber 3.0. You attempted to access the '#{key}' setting. See https://github.com/thekompanee/chamber/wiki/Upgrading-To-Chamber-3.0#removal-of-bracket-indifferent-access for full details." if key.is_a?(::Symbol) # rubocop:disable Layout/LineLength
232
+ warn "WARNING: Accessing a non-existent key ('#{key}') with brackets will fail in Chamber 3.0. See https://github.com/thekompanee/chamber/wiki/Upgrading-To-Chamber-3.0#bracket-access-now-fails-on-non-existent-keys for full details." unless data.has_key?(key) # rubocop:disable Layout/LineLength
233
+
234
+ data.[](key)
235
+ end
236
+
237
+ def dig!(*args)
238
+ args.inject(data) do |data_value, bracket_value|
239
+ key = bracket_value.is_a?(::Symbol) ? bracket_value.to_s : bracket_value
240
+
241
+ data_value.fetch(key)
242
+ end
243
+ end
244
+
245
+ def dig(*args)
246
+ dig!(*args)
247
+ rescue ::KeyError, ::IndexError # rubocop:disable Lint/ShadowedException
248
+ nil
249
+ end
250
+
222
251
  def securable
223
- Settings.new(metadata.merge(
224
- settings: raw_data,
225
- pre_filters: [Filters::SecureFilter],
226
- ))
252
+ Settings.new(**metadata.merge(
253
+ settings: raw_data,
254
+ pre_filters: [Filters::SecureFilter],
255
+ ))
227
256
  end
228
257
 
229
258
  def secure
230
- Settings.new(metadata.merge(
231
- settings: raw_data,
232
- pre_filters: [Filters::EncryptionFilter],
233
- post_filters: [Filters::TranslateSecureKeysFilter],
234
- ))
259
+ Settings.new(**metadata.merge(
260
+ settings: raw_data,
261
+ pre_filters: [Filters::EncryptionFilter],
262
+ post_filters: [Filters::TranslateSecureKeysFilter],
263
+ ))
235
264
  end
236
265
 
237
266
  def insecure
238
- Settings.new(metadata.merge(
239
- settings: raw_data,
240
- pre_filters: [Filters::InsecureFilter],
241
- post_filters: [Filters::TranslateSecureKeysFilter],
242
- ))
267
+ Settings.new(**metadata.merge(
268
+ settings: raw_data,
269
+ pre_filters: [Filters::InsecureFilter],
270
+ post_filters: [Filters::TranslateSecureKeysFilter],
271
+ ))
243
272
  end
244
273
 
245
274
  def method_missing(name, *args)
246
- return data.public_send(name, *args) if data.respond_to?(name)
275
+ if data.respond_to?(name)
276
+ warn "WARNING: Object notation access is deprecated and will be removed in Chamber 3.0. You attempted to access the '#{name}' setting. See https://github.com/thekompanee/chamber/wiki/Upgrading-To-Chamber-3.0#removal-of-object-notation-access for full details." # rubocop:disable Layout/LineLength
277
+ warn "WARNING: Predicate methods are deprecated and will be removed in Chamber 3.0. You attempted to access the '#{name}' setting. See https://github.com/thekompanee/chamber/wiki/Upgrading-To-Chamber-3.0#removal-of-predicate-accessors for full details." if name.end_with?('?') # rubocop:disable Layout/LineLength
247
278
 
248
- super
279
+ data.public_send(name, *args)
280
+ else
281
+ super
282
+ end
249
283
  end
250
284
 
251
285
  def respond_to_missing?(name, include_private = false)
@@ -262,24 +296,20 @@ class Settings
262
296
  @namespaces = NamespaceSet.new(raw_namespaces)
263
297
  end
264
298
 
299
+ # rubocop:disable Naming/MemoizedInstanceVariableName
265
300
  def raw_data
266
301
  @filtered_raw_data ||= pre_filters.inject(@raw_data) do |filtered_data, filter|
267
- filter.execute({ data: filtered_data }.
268
- merge(metadata))
302
+ filter.execute(**{ data: filtered_data }.merge(metadata))
269
303
  end
270
304
  end
305
+ # rubocop:enable Naming/MemoizedInstanceVariableName
271
306
 
272
307
  def data
273
308
  @data ||= post_filters.inject(raw_data) do |filtered_data, filter|
274
- filter.execute({ data: filtered_data }.
275
- merge(metadata))
309
+ filter.execute(**{ data: filtered_data }.merge(metadata))
276
310
  end
277
311
  end
278
312
 
279
- def secure_key_prefix
280
- '_secure_'
281
- end
282
-
283
313
  def metadata
284
314
  {
285
315
  decryption_keys: decryption_keys,