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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/README.md +101 -26
- data/lib/chamber.rb +77 -16
- data/lib/chamber/adapters/cloud/circle_ci.rb +16 -13
- data/lib/chamber/adapters/cloud/heroku.rb +40 -13
- data/lib/chamber/binary/circle_ci.rb +28 -14
- data/lib/chamber/binary/heroku.rb +34 -14
- data/lib/chamber/binary/runner.rb +40 -29
- data/lib/chamber/binary/travis.rb +10 -4
- data/lib/chamber/commands/base.rb +10 -16
- data/lib/chamber/commands/cloud/base.rb +3 -3
- data/lib/chamber/commands/cloud/pull.rb +2 -2
- data/lib/chamber/commands/cloud/push.rb +7 -7
- data/lib/chamber/commands/comparable.rb +2 -2
- data/lib/chamber/commands/compare.rb +6 -9
- data/lib/chamber/commands/initialize.rb +26 -22
- data/lib/chamber/commands/securable.rb +12 -9
- data/lib/chamber/commands/secure.rb +2 -2
- data/lib/chamber/commands/show.rb +8 -8
- data/lib/chamber/commands/sign.rb +2 -2
- data/lib/chamber/commands/verify.rb +2 -2
- data/lib/chamber/configuration.rb +6 -3
- data/lib/chamber/context_resolver.rb +7 -7
- data/lib/chamber/encryption_methods/ssl.rb +12 -12
- data/lib/chamber/file.rb +20 -15
- data/lib/chamber/file_set.rb +18 -8
- data/lib/chamber/files/signature.rb +16 -14
- data/lib/chamber/filters/decryption_filter.rb +19 -15
- data/lib/chamber/filters/encryption_filter.rb +14 -11
- data/lib/chamber/filters/environment_filter.rb +21 -20
- data/lib/chamber/filters/failed_decryption_filter.rb +9 -6
- data/lib/chamber/filters/insecure_filter.rb +4 -5
- data/lib/chamber/filters/namespace_filter.rb +13 -9
- data/lib/chamber/filters/secure_filter.rb +9 -7
- data/lib/chamber/filters/translate_secure_keys_filter.rb +9 -7
- data/lib/chamber/instance.rb +48 -31
- data/lib/chamber/key_pair.rb +7 -7
- data/lib/chamber/keys/base.rb +13 -13
- data/lib/chamber/keys/decryption.rb +3 -3
- data/lib/chamber/keys/encryption.rb +3 -3
- data/lib/chamber/namespace_set.rb +2 -4
- data/lib/chamber/refinements/array.rb +20 -0
- data/lib/chamber/refinements/deep_dup.rb +58 -0
- data/lib/chamber/refinements/enumerable.rb +36 -0
- data/lib/chamber/refinements/hash.rb +51 -0
- data/lib/chamber/settings.rb +81 -66
- data/lib/chamber/types/secured.rb +21 -23
- data/lib/chamber/version.rb +1 -1
- data/templates/settings.yml +2 -0
- metadata +44 -51
- metadata.gz.sig +0 -0
- 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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
self.
|
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(
|
12
|
-
super(
|
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(
|
13
|
-
super
|
12
|
+
def initialize(as_env: nil, only_sensitive: nil, **args)
|
13
|
+
super(**args)
|
14
14
|
|
15
|
-
self.as_env =
|
16
|
-
self.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(
|
9
|
-
super(
|
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(
|
9
|
-
super(
|
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(
|
15
|
-
options = ContextResolver.resolve(
|
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(
|
15
|
-
self.options =
|
13
|
+
def initialize(**args)
|
14
|
+
self.options = args
|
16
15
|
end
|
17
16
|
|
18
|
-
# rubocop:disable Metrics/
|
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/
|
48
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Layout/LineLength
|
49
49
|
|
50
|
-
def self.resolve(
|
51
|
-
new(
|
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
|
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
|
21
|
-
cipher
|
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
|
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
|
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
|
55
|
+
cipher_dec.iv = iv
|
56
56
|
|
57
57
|
begin
|
58
58
|
unencrypted_value = cipher_dec.update(decoded_string) + cipher_dec.final
|
data/lib/chamber/file.rb
CHANGED
@@ -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(
|
46
|
-
self.namespaces =
|
47
|
-
self.decryption_keys =
|
48
|
-
self.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
|
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/
|
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/
|
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
|
data/lib/chamber/file_set.rb
CHANGED
@@ -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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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 =
|
11
|
+
SIGNATURE_HEADER_PATTERN = /-----BEGIN\sCHAMBER\sSIGNATURE-----/.freeze
|
12
12
|
SIGNATURE_FOOTER = '-----END CHAMBER SIGNATURE-----'
|
13
|
-
SIGNATURE_FOOTER_PATTERN =
|
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: #{
|
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
|
89
|
+
@digest ||= OpenSSL::Digest.new('SHA512')
|
88
90
|
end
|
89
91
|
end
|
90
92
|
end
|