chamber 2.10.2 → 2.11.0
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/LICENSE.txt +1 -1
- data/lib/chamber.rb +3 -6
- data/lib/chamber/binary/heroku.rb +1 -0
- data/lib/chamber/binary/runner.rb +29 -13
- data/lib/chamber/binary/travis.rb +1 -0
- data/lib/chamber/commands/base.rb +10 -9
- data/lib/chamber/commands/comparable.rb +1 -0
- data/lib/chamber/commands/compare.rb +8 -7
- data/lib/chamber/commands/files.rb +1 -0
- data/lib/chamber/commands/heroku.rb +1 -0
- data/lib/chamber/commands/heroku/clear.rb +2 -1
- data/lib/chamber/commands/heroku/compare.rb +1 -0
- data/lib/chamber/commands/heroku/pull.rb +3 -4
- data/lib/chamber/commands/heroku/push.rb +1 -0
- data/lib/chamber/commands/initialize.rb +88 -76
- data/lib/chamber/commands/securable.rb +3 -2
- data/lib/chamber/commands/secure.rb +3 -1
- data/lib/chamber/commands/show.rb +7 -6
- data/lib/chamber/commands/travis.rb +1 -0
- data/lib/chamber/commands/travis/secure.rb +1 -0
- data/lib/chamber/configuration.rb +14 -13
- data/lib/chamber/context_resolver.rb +52 -55
- data/lib/chamber/encryption_methods/none.rb +4 -2
- data/lib/chamber/encryption_methods/public_key.rb +4 -2
- data/lib/chamber/encryption_methods/ssl.rb +11 -9
- data/lib/chamber/errors/decryption_failure.rb +1 -0
- data/lib/chamber/file.rb +27 -18
- data/lib/chamber/file_set.rb +14 -13
- data/lib/chamber/filters/decryption_filter.rb +48 -18
- data/lib/chamber/filters/encryption_filter.rb +32 -22
- data/lib/chamber/filters/environment_filter.rb +109 -16
- data/lib/chamber/filters/failed_decryption_filter.rb +10 -8
- data/lib/chamber/filters/insecure_filter.rb +1 -0
- data/lib/chamber/filters/namespace_filter.rb +8 -7
- data/lib/chamber/filters/secure_filter.rb +10 -9
- data/lib/chamber/filters/translate_secure_keys_filter.rb +10 -9
- data/lib/chamber/instance.rb +5 -4
- data/lib/chamber/key_pair.rb +82 -0
- data/lib/chamber/keys/base.rb +64 -0
- data/lib/chamber/keys/decryption.rb +41 -0
- data/lib/chamber/keys/encryption.rb +41 -0
- data/lib/chamber/namespace_set.rb +10 -9
- data/lib/chamber/rails.rb +1 -0
- data/lib/chamber/rails/railtie.rb +1 -0
- data/lib/chamber/rubinius_fix.rb +1 -0
- data/lib/chamber/settings.rb +45 -41
- data/lib/chamber/types/secured.rb +14 -12
- data/lib/chamber/version.rb +2 -1
- metadata +28 -27
- metadata.gz.sig +0 -0
- data/lib/chamber/decryption_key.rb +0 -52
- data/lib/chamber/environmentable.rb +0 -27
- 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
|
-
|
13
|
-
|
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.
|
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
|
-
|
21
|
-
|
22
|
-
|
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,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
|
-
:
|
8
|
-
:
|
8
|
+
:decryption_keys,
|
9
|
+
:encryption_keys,
|
9
10
|
:files,
|
10
11
|
:namespaces
|
11
12
|
|
12
13
|
def initialize(options = {})
|
13
|
-
options
|
14
|
+
options = ContextResolver.resolve(options)
|
14
15
|
|
15
|
-
self.basepath
|
16
|
-
self.namespaces
|
17
|
-
self.
|
18
|
-
self.
|
19
|
-
self.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:
|
25
|
-
|
26
|
-
|
27
|
-
files:
|
28
|
-
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
|
-
|
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 =
|
15
|
+
self.options = options.dup
|
11
16
|
end
|
12
17
|
|
13
|
-
# rubocop:disable Metrics/AbcSize, Metrics/
|
14
|
-
# rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
|
18
|
+
# rubocop:disable Metrics/AbcSize, Metrics/LineLength
|
15
19
|
def resolve
|
16
|
-
options[:rootpath]
|
17
|
-
options[:rootpath]
|
18
|
-
options[:
|
19
|
-
options[:
|
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]
|
25
|
-
|
26
|
-
|
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]
|
30
|
+
options[:basepath] ||= options[:rootpath]
|
47
31
|
end
|
48
32
|
|
49
|
-
options[:
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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/
|
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,
|
6
|
+
def self.encrypt(_key, value, _encryption_keys)
|
5
7
|
value
|
6
8
|
end
|
7
9
|
|
8
|
-
def self.decrypt(key, value,
|
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,
|
6
|
+
def self.encrypt(_key, value, encryption_keys)
|
5
7
|
value = YAML.dump(value)
|
6
|
-
encrypted_string =
|
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,
|
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 =
|
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,
|
26
|
-
if
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
match(LARGE_DATA_STRING_PATTERN).
|
33
|
+
captures.
|
34
|
+
map do |part|
|
35
|
+
Base64.strict_decode64(part)
|
34
36
|
end
|
35
|
-
key =
|
37
|
+
key = decryption_keys.private_decrypt(key)
|
36
38
|
|
37
39
|
cipher_dec = OpenSSL::Cipher.new('AES-128-CBC')
|
38
40
|
|
data/lib/chamber/file.rb
CHANGED
@@ -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
|
41
|
-
self.
|
42
|
-
self.
|
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:
|
68
|
-
namespaces:
|
69
|
-
|
70
|
-
|
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
|
-
|
86
|
-
/^(\s*)
|
87
|
-
"\\
|
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
|
-
|
92
|
-
/^(\s*)
|
93
|
-
"\\
|
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
|
-
|
107
|
+
private
|
101
108
|
|
102
|
-
|
103
|
-
|
104
|
-
|
109
|
+
def secure_prefix
|
110
|
+
'_secure_'
|
111
|
+
end
|
105
112
|
|
106
|
-
|
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
|