chamber 2.13.0 → 3.0.0rc1

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 (54) hide show
  1. checksums.yaml +4 -4
  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 +77 -16
  6. data/lib/chamber/adapters/cloud/circle_ci.rb +16 -13
  7. data/lib/chamber/adapters/cloud/heroku.rb +40 -13
  8. data/lib/chamber/binary/circle_ci.rb +28 -14
  9. data/lib/chamber/binary/heroku.rb +34 -14
  10. data/lib/chamber/binary/runner.rb +40 -29
  11. data/lib/chamber/binary/travis.rb +10 -4
  12. data/lib/chamber/commands/base.rb +10 -16
  13. data/lib/chamber/commands/cloud/base.rb +3 -3
  14. data/lib/chamber/commands/cloud/pull.rb +2 -2
  15. data/lib/chamber/commands/cloud/push.rb +7 -7
  16. data/lib/chamber/commands/comparable.rb +2 -2
  17. data/lib/chamber/commands/compare.rb +6 -9
  18. data/lib/chamber/commands/initialize.rb +26 -22
  19. data/lib/chamber/commands/securable.rb +12 -9
  20. data/lib/chamber/commands/secure.rb +2 -2
  21. data/lib/chamber/commands/show.rb +8 -8
  22. data/lib/chamber/commands/sign.rb +2 -2
  23. data/lib/chamber/commands/verify.rb +2 -2
  24. data/lib/chamber/configuration.rb +6 -3
  25. data/lib/chamber/context_resolver.rb +7 -7
  26. data/lib/chamber/encryption_methods/ssl.rb +12 -12
  27. data/lib/chamber/file.rb +20 -15
  28. data/lib/chamber/file_set.rb +18 -8
  29. data/lib/chamber/files/signature.rb +16 -14
  30. data/lib/chamber/filters/decryption_filter.rb +19 -15
  31. data/lib/chamber/filters/encryption_filter.rb +14 -11
  32. data/lib/chamber/filters/environment_filter.rb +21 -20
  33. data/lib/chamber/filters/failed_decryption_filter.rb +9 -6
  34. data/lib/chamber/filters/insecure_filter.rb +4 -5
  35. data/lib/chamber/filters/namespace_filter.rb +13 -9
  36. data/lib/chamber/filters/secure_filter.rb +9 -7
  37. data/lib/chamber/filters/translate_secure_keys_filter.rb +9 -7
  38. data/lib/chamber/instance.rb +48 -31
  39. data/lib/chamber/key_pair.rb +7 -7
  40. data/lib/chamber/keys/base.rb +13 -13
  41. data/lib/chamber/keys/decryption.rb +3 -3
  42. data/lib/chamber/keys/encryption.rb +3 -3
  43. data/lib/chamber/namespace_set.rb +2 -4
  44. data/lib/chamber/refinements/array.rb +20 -0
  45. data/lib/chamber/refinements/deep_dup.rb +58 -0
  46. data/lib/chamber/refinements/enumerable.rb +36 -0
  47. data/lib/chamber/refinements/hash.rb +51 -0
  48. data/lib/chamber/settings.rb +81 -66
  49. data/lib/chamber/types/secured.rb +21 -23
  50. data/lib/chamber/version.rb +1 -1
  51. data/templates/settings.yml +2 -0
  52. metadata +44 -51
  53. metadata.gz.sig +0 -0
  54. data/lib/chamber/core_ext/hash.rb +0 -15
@@ -2,19 +2,22 @@
2
2
 
3
3
  require 'shellwords'
4
4
  require 'chamber/instance'
5
+ require 'chamber/refinements/hash'
5
6
 
6
7
  module Chamber
7
8
  module Commands
8
9
  module Securable
9
- def initialize(options = {})
10
- super
11
-
12
- ignored_settings_options = options.
13
- merge(files: ignored_settings_filepaths).
14
- reject { |k, _v| k == 'basepath' }
15
- self.ignored_settings_instance = Chamber::Instance.new(ignored_settings_options)
16
- self.current_settings_instance = Chamber::Instance.new(options)
17
- self.only_sensitive = options[:only_sensitive]
10
+ using ::Chamber::Refinements::Hash
11
+
12
+ def initialize(only_sensitive: nil, **args)
13
+ super(**args)
14
+
15
+ ignored_settings_options = args
16
+ .deep_merge(files: ignored_settings_filepaths)
17
+ .reject { |k, _v| k == 'basepath' }
18
+ self.ignored_settings_instance = Chamber::Instance.new(**ignored_settings_options)
19
+ self.current_settings_instance = Chamber::Instance.new(**args)
20
+ self.only_sensitive = only_sensitive
18
21
  end
19
22
 
20
23
  protected
@@ -8,8 +8,8 @@ module Commands
8
8
  class Secure < Chamber::Commands::Base
9
9
  include Chamber::Commands::Securable
10
10
 
11
- def initialize(options = {})
12
- super(options.merge(namespaces: ['*']))
11
+ def initialize(**args)
12
+ super(**args.merge(namespaces: ['*']))
13
13
  end
14
14
 
15
15
  def call
@@ -9,21 +9,21 @@ class Show < Chamber::Commands::Base
9
9
  attr_accessor :as_env,
10
10
  :only_sensitive
11
11
 
12
- def initialize(options = {})
13
- super
12
+ def initialize(as_env: nil, only_sensitive: nil, **args)
13
+ super(**args)
14
14
 
15
- self.as_env = options[:as_env]
16
- self.only_sensitive = options[:only_sensitive]
15
+ self.as_env = as_env
16
+ self.only_sensitive = only_sensitive
17
17
  end
18
18
 
19
19
  def call
20
20
  if as_env
21
21
  settings.to_s(pair_separator: "\n")
22
22
  else
23
- PP.
24
- pp(settings.to_hash, StringIO.new, 60).
25
- string.
26
- chomp
23
+ PP
24
+ .pp(settings.to_hash, StringIO.new, 60)
25
+ .string
26
+ .chomp
27
27
  end
28
28
  end
29
29
 
@@ -5,8 +5,8 @@ require 'chamber/commands/base'
5
5
  module Chamber
6
6
  module Commands
7
7
  class Sign < Chamber::Commands::Base
8
- def initialize(options = {})
9
- super(options.merge(namespaces: ['*']))
8
+ def initialize(**args)
9
+ super(**args.merge(namespaces: ['*']))
10
10
  end
11
11
 
12
12
  def call
@@ -5,8 +5,8 @@ require 'chamber/commands/base'
5
5
  module Chamber
6
6
  module Commands
7
7
  class Verify < Chamber::Commands::Base
8
- def initialize(options = {})
9
- super(options.merge(namespaces: ['*']))
8
+ def initialize(**args)
9
+ super(**args.merge(namespaces: ['*']))
10
10
  end
11
11
 
12
12
  def call
@@ -9,10 +9,11 @@ class Configuration
9
9
  :encryption_keys,
10
10
  :files,
11
11
  :namespaces,
12
- :rootpath
12
+ :rootpath,
13
+ :signature_name
13
14
 
14
- def initialize(options = {})
15
- options = ContextResolver.resolve(options)
15
+ def initialize(**args)
16
+ options = ContextResolver.resolve(**args)
16
17
 
17
18
  self.basepath = options.fetch(:basepath)
18
19
  self.namespaces = options.fetch(:namespaces)
@@ -20,6 +21,7 @@ class Configuration
20
21
  self.encryption_keys = options.fetch(:encryption_keys)
21
22
  self.files = options.fetch(:files)
22
23
  self.rootpath = options.fetch(:rootpath)
24
+ self.signature_name = options.fetch(:signature_name)
23
25
  end
24
26
 
25
27
  def to_hash
@@ -29,6 +31,7 @@ class Configuration
29
31
  encryption_keys: encryption_keys,
30
32
  files: files,
31
33
  namespaces: namespaces,
34
+ signature_name: signature_name,
32
35
  }
33
36
  end
34
37
  end
@@ -3,7 +3,6 @@
3
3
  require 'pathname'
4
4
  require 'socket'
5
5
 
6
- require 'chamber/core_ext/hash'
7
6
  require 'chamber/keys/decryption'
8
7
  require 'chamber/keys/encryption'
9
8
 
@@ -11,11 +10,11 @@ module Chamber
11
10
  class ContextResolver
12
11
  attr_accessor :options
13
12
 
14
- def initialize(options = {})
15
- self.options = options.transform_keys(&:to_sym)
13
+ def initialize(**args)
14
+ self.options = args
16
15
  end
17
16
 
18
- # rubocop:disable Metrics/AbcSize, Metrics/LineLength
17
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Layout/LineLength
19
18
  def resolve
20
19
  options[:rootpath] ||= Pathname.pwd
21
20
  options[:rootpath] = Pathname.new(options[:rootpath])
@@ -42,13 +41,14 @@ class ContextResolver
42
41
  options[:basepath] + 'settings*.yml',
43
42
  options[:basepath] + 'settings',
44
43
  ]
44
+ options[:signature_name] = options[:signature_name]
45
45
 
46
46
  options
47
47
  end
48
- # rubocop:enable Metrics/AbcSize, Metrics/LineLength
48
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Layout/LineLength
49
49
 
50
- def self.resolve(options = {})
51
- new(options).resolve
50
+ def self.resolve(**args)
51
+ new(**args).resolve
52
52
  end
53
53
 
54
54
  protected
@@ -5,7 +5,7 @@ require 'base64'
5
5
  module Chamber
6
6
  module EncryptionMethods
7
7
  class Ssl
8
- BASE64_STRING_PATTERN = %r{[A-Za-z0-9\+\/#]*\={0,2}}.freeze
8
+ BASE64_STRING_PATTERN = %r{[A-Za-z0-9+/#]*={0,2}}.freeze
9
9
  LARGE_DATA_STRING_PATTERN = /
10
10
  \A
11
11
  (#{BASE64_STRING_PATTERN})
@@ -16,12 +16,12 @@ class Ssl
16
16
  \z
17
17
  /x.freeze
18
18
 
19
- def self.encrypt(_key, value, encryption_keys)
20
- value = YAML.dump(value)
21
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
19
+ def self.encrypt(_key, value, encryption_keys) # rubocop:disable Metrics/AbcSize
20
+ value = YAML.dump(value)
21
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
22
22
  cipher.encrypt
23
23
  symmetric_key = cipher.random_key
24
- iv = cipher.random_iv
24
+ iv = cipher.random_iv
25
25
 
26
26
  # encrypt all data with this key and iv
27
27
  encrypted_data = cipher.update(value) + cipher.final
@@ -35,24 +35,24 @@ class Ssl
35
35
  Base64.strict_encode64(encrypted_data)
36
36
  end
37
37
 
38
- def self.decrypt(key, value, decryption_keys)
38
+ def self.decrypt(key, value, decryption_keys) # rubocop:disable Metrics/AbcSize
39
39
  if decryption_keys.nil?
40
40
  value
41
41
  else
42
- key, iv, decoded_string = value.
43
- match(LARGE_DATA_STRING_PATTERN).
44
- captures.
45
- map do |part|
42
+ key, iv, decoded_string = value
43
+ .match(LARGE_DATA_STRING_PATTERN)
44
+ .captures
45
+ .map do |part|
46
46
  Base64.strict_decode64(part)
47
47
  end
48
- key = decryption_keys.private_decrypt(key)
48
+ key = decryption_keys.private_decrypt(key)
49
49
 
50
50
  cipher_dec = OpenSSL::Cipher.new('AES-128-CBC')
51
51
 
52
52
  cipher_dec.decrypt
53
53
 
54
54
  cipher_dec.key = key
55
- cipher_dec.iv = iv
55
+ cipher_dec.iv = iv
56
56
 
57
57
  begin
58
58
  unencrypted_value = cipher_dec.update(decoded_string) + cipher_dec.final
@@ -4,6 +4,7 @@ require 'pathname'
4
4
  require 'yaml'
5
5
  require 'erb'
6
6
  require 'chamber/files/signature'
7
+ require 'chamber/refinements/hash'
7
8
 
8
9
  ###
9
10
  # Internal: Represents a single file containing settings information in a given
@@ -11,9 +12,12 @@ require 'chamber/files/signature'
11
12
  #
12
13
  module Chamber
13
14
  class File < Pathname
15
+ using ::Chamber::Refinements::Hash
16
+
14
17
  attr_accessor :namespaces,
15
18
  :decryption_keys,
16
- :encryption_keys
19
+ :encryption_keys,
20
+ :signature_name
17
21
 
18
22
  ###
19
23
  # Internal: Creates a settings file representing a path to a file on the
@@ -42,12 +46,13 @@ class File < Pathname
42
46
  # Chamber::File.new path: '/tmp/settings.yml'
43
47
  # # => <Chamber::File>
44
48
  #
45
- def initialize(options = {})
46
- self.namespaces = options[:namespaces] || {}
47
- self.decryption_keys = options[:decryption_keys] || {}
48
- self.encryption_keys = options[:encryption_keys] || {}
49
+ def initialize(path:, namespaces: {}, decryption_keys: {}, encryption_keys: {}, signature_name: nil)
50
+ self.namespaces = namespaces
51
+ self.decryption_keys = decryption_keys
52
+ self.encryption_keys = encryption_keys
53
+ self.signature_name = signature_name
49
54
 
50
- super options.fetch(:path)
55
+ super path
51
56
  end
52
57
 
53
58
  ###
@@ -76,7 +81,7 @@ class File < Pathname
76
81
  encryption_keys: encryption_keys)
77
82
  end
78
83
 
79
- # rubocop:disable Metrics/LineLength
84
+ # rubocop:disable Layout/LineLength, Metrics/AbcSize
80
85
  def secure
81
86
  insecure_settings = to_settings.insecure.to_flattened_name_hash
82
87
  secure_settings = to_settings.insecure.secure.to_flattened_name_hash
@@ -88,14 +93,14 @@ class File < Pathname
88
93
  escaped_name = Regexp.escape(name_pieces.last)
89
94
  escaped_value = Regexp.escape(value)
90
95
 
91
- file_contents.
92
- sub!(
96
+ file_contents
97
+ .sub!(
93
98
  /^(\s*)#{secure_prefix_pattern}#{escaped_name}(\s*):(\s*)['"]?#{escaped_value}['"]?$/,
94
99
  "\\1#{secure_prefix}#{name_pieces.last}\\2:\\3#{secure_value}",
95
100
  )
96
101
 
97
- file_contents.
98
- sub!(
102
+ file_contents
103
+ .sub!(
99
104
  /^(\s*)#{secure_prefix_pattern}#{escaped_name}(\s*):(\s*)\|((?:\n\1\s{2}.*)+)/,
100
105
  "\\1#{secure_prefix}#{name_pieces.last}\\2:\\3#{secure_value}",
101
106
  )
@@ -103,7 +108,7 @@ class File < Pathname
103
108
 
104
109
  write(file_contents)
105
110
  end
106
- # rubocop:enable Metrics/LineLength
111
+ # rubocop:enable Layout/LineLength, Metrics/AbcSize
107
112
 
108
113
  def sign
109
114
  signature_key_contents = decryption_keys[:signature]
@@ -111,7 +116,7 @@ class File < Pathname
111
116
  fail ArgumentError, 'You asked to sign your settings files but no signature key was found. Run `chamber init --signature` to generate one.' \
112
117
  unless signature_key_contents
113
118
 
114
- signature = Files::Signature.new(to_s, read, signature_key_contents)
119
+ signature = Files::Signature.new(to_s, read, signature_key_contents, signature_name)
115
120
 
116
121
  signature.write
117
122
  end
@@ -122,7 +127,7 @@ class File < Pathname
122
127
  fail ArgumentError, 'You asked to verify your settings files but no signature key was found. Run `chamber init --signature` to generate one.' \
123
128
  unless signature_key_contents
124
129
 
125
- signature = Files::Signature.new(to_s, read, signature_key_contents)
130
+ signature = Files::Signature.new(to_s, read, signature_key_contents, signature_name)
126
131
 
127
132
  signature.verify
128
133
  end
@@ -141,7 +146,7 @@ class File < Pathname
141
146
  file_contents = read
142
147
  erb_result = ERB.new(file_contents).result
143
148
 
144
- YAML.load(erb_result) || {}
149
+ (YAML.load(erb_result) || {}).deep_transform_keys(&:to_s)
145
150
  rescue Errno::ENOENT
146
151
  {}
147
152
  end
@@ -114,17 +114,26 @@ module Chamber
114
114
  class FileSet
115
115
  attr_accessor :decryption_keys,
116
116
  :encryption_keys,
117
- :basepath
117
+ :basepath,
118
+ :signature_name
118
119
  attr_reader :namespaces,
119
120
  :paths
120
121
 
121
- def initialize(options = {})
122
- self.namespaces = options[:namespaces] || {}
123
- self.decryption_keys = options[:decryption_keys]
124
- self.encryption_keys = options[:encryption_keys]
125
- self.paths = options.fetch(:files)
126
- self.basepath = options[:basepath]
122
+ # rubocop:disable Metrics/ParameterLists
123
+ def initialize(files:,
124
+ basepath: nil,
125
+ decryption_keys: nil,
126
+ encryption_keys: nil,
127
+ namespaces: {},
128
+ signature_name: nil)
129
+ self.basepath = basepath
130
+ self.decryption_keys = decryption_keys
131
+ self.encryption_keys = encryption_keys
132
+ self.namespaces = namespaces
133
+ self.paths = files
134
+ self.signature_name = signature_name
127
135
  end
136
+ # rubocop:enable Metrics/ParameterLists
128
137
 
129
138
  ###
130
139
  # Internal: Returns an Array of the ordered list of files that was processed
@@ -234,7 +243,8 @@ class FileSet
234
243
  File.new(path: file,
235
244
  namespaces: namespaces,
236
245
  decryption_keys: decryption_keys,
237
- encryption_keys: encryption_keys)
246
+ encryption_keys: encryption_keys,
247
+ signature_name: signature_name)
238
248
  end
239
249
 
240
250
  sorted_relevant_files += relevant_glob_files
@@ -8,9 +8,9 @@ module Chamber
8
8
  module Files
9
9
  class Signature
10
10
  SIGNATURE_HEADER = '-----BEGIN CHAMBER SIGNATURE-----'
11
- SIGNATURE_HEADER_PATTERN = /\-\-\-\-\-BEGIN\sCHAMBER\sSIGNATURE\-\-\-\-\-/.freeze
11
+ SIGNATURE_HEADER_PATTERN = /-----BEGIN\sCHAMBER\sSIGNATURE-----/.freeze
12
12
  SIGNATURE_FOOTER = '-----END CHAMBER SIGNATURE-----'
13
- SIGNATURE_FOOTER_PATTERN = /\-\-\-\-\-END\sCHAMBER\sSIGNATURE\-\-\-\-\-/.freeze
13
+ SIGNATURE_FOOTER_PATTERN = /-----END\sCHAMBER\sSIGNATURE-----/.freeze
14
14
  SIGNATURE_IN_FILE_PATTERN = /
15
15
  #{SIGNATURE_HEADER_PATTERN}\n # Header
16
16
  (.*)\n # Signature Body
@@ -18,14 +18,16 @@ class Signature
18
18
  /x.freeze
19
19
 
20
20
  attr_accessor :settings_content,
21
- :settings_filename
21
+ :settings_filename,
22
+ :signature_name
22
23
 
23
24
  attr_reader :signature_key
24
25
 
25
- def initialize(settings_filename, settings_content, signature_key)
26
+ def initialize(settings_filename, settings_content, signature_key, signature_name)
26
27
  self.signature_key = signature_key
27
28
  self.settings_content = settings_content
28
29
  self.settings_filename = Pathname.new(settings_filename)
30
+ self.signature_name = signature_name
29
31
  end
30
32
 
31
33
  def signature_key=(keyish)
@@ -41,7 +43,7 @@ class Signature
41
43
 
42
44
  def write
43
45
  signature_filename.write(<<-HEREDOC, 0, mode: 'w+')
44
- Signed By: #{`git config --get 'user.name'`.chomp}
46
+ Signed By: #{signature_name}
45
47
  Signed At: #{Time.now.utc.iso8601}
46
48
 
47
49
  #{SIGNATURE_HEADER}
@@ -61,20 +63,20 @@ Signed At: #{Time.now.utc.iso8601}
61
63
  end
62
64
 
63
65
  def raw_signature
64
- @raw_signature ||= signature_key.
65
- sign(digest, settings_content)
66
+ @raw_signature ||= signature_key
67
+ .sign(digest, settings_content)
66
68
  end
67
69
 
68
70
  def signature_filename
69
- @signature_filename ||= settings_filename.
70
- sub('.yml', '.sig').
71
- sub('.erb', '')
71
+ @signature_filename ||= settings_filename
72
+ .sub('.yml', '.sig')
73
+ .sub('.erb', '')
72
74
  end
73
75
 
74
76
  def encoded_signature_content
75
- @encoded_signature_content ||= signature_filename.
76
- read.
77
- match(SIGNATURE_IN_FILE_PATTERN) do |match|
77
+ @encoded_signature_content ||= signature_filename
78
+ .read
79
+ .match(SIGNATURE_IN_FILE_PATTERN) do |match|
78
80
  match[1]
79
81
  end
80
82
  end
@@ -84,7 +86,7 @@ Signed At: #{Time.now.utc.iso8601}
84
86
  end
85
87
 
86
88
  def digest
87
- @digest ||= OpenSSL::Digest::SHA512.new
89
+ @digest ||= OpenSSL::Digest.new('SHA512')
88
90
  end
89
91
  end
90
92
  end