chamber 2.13.0 → 3.0.0rc1

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