secret_config 0.2.0 → 0.3.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
- data/README.md +83 -0
- data/lib/secret_config.rb +45 -24
- data/lib/secret_config/railtie.rb +13 -0
- data/lib/secret_config/registry.rb +50 -6
- data/lib/secret_config/version.rb +1 -1
- data/test/registry_test.rb +9 -1
- data/test/secret_config_test.rb +8 -3
- metadata +3 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67325636265a59114b4ff33575572d931c337a0796def120ca68ef7ebfbd340e
|
4
|
+
data.tar.gz: 0b3280f1aab15a4301af120dcf1001757d998fc76b0141eebe66c778e8e6b665
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f06a83adb1211924f4fca6e73976e8f23bf9bf2784da3bd98a6428327acccfa831b2dd9d1ab735f636e239f8db730210a0fd0d5c8370344145b86a22b47fe66e
|
7
|
+
data.tar.gz: 674f8ea7d0f0b7340d462f8d90992edd6c93fe14e2979ad6b347992a472ef2fae75e9ea184291281368e45c407e61c661b1b309b4877f168339eb22909836143
|
data/README.md
CHANGED
@@ -136,6 +136,89 @@ production:
|
|
136
136
|
|
137
137
|
Since the secrets are externalized the configuration between environments is simpler.
|
138
138
|
|
139
|
+
## Configuration
|
140
|
+
|
141
|
+
Add the following line to Gemfile
|
142
|
+
|
143
|
+
gem "secret_config"
|
144
|
+
|
145
|
+
Out of the box Secret Config will look in the local file system for the file `config/application.yml`
|
146
|
+
as covered above. By default it will use env var `RAILS_ENV` to define the root path to look under for settings.
|
147
|
+
|
148
|
+
The default settings are great for getting started in development and test, but should not be used in production.
|
149
|
+
|
150
|
+
Add the setting to `config/environments/production.rb` to make it fetch its settings from
|
151
|
+
AWS System Manager Parameter Store:
|
152
|
+
|
153
|
+
~~~ruby
|
154
|
+
Rails.application.configure do
|
155
|
+
# Read configuration from AWS Parameter Store
|
156
|
+
config.secret_config.use :ssm, root: '/production/my_application'
|
157
|
+
end
|
158
|
+
~~~
|
159
|
+
|
160
|
+
`root` is the path from which the configuration data will be read. This path uniquely identifies the
|
161
|
+
configuration for this instance of the application.
|
162
|
+
|
163
|
+
If we need 2 completely separate instances of the application running in a single AWS account then we could use
|
164
|
+
multiple paths. For example:
|
165
|
+
|
166
|
+
/production1/my_application
|
167
|
+
/production2/my_application
|
168
|
+
|
169
|
+
/production/instance1/my_application
|
170
|
+
/production/instance2/my_application
|
171
|
+
|
172
|
+
The root path is completely flexible, but must be unique for every AWS account under which the application will run.
|
173
|
+
The same root path can be used in different AWS accounts though. It is also not replicated across regions.
|
174
|
+
|
175
|
+
When writing settings to the parameter store, it is recommended to use a custom KMS key to encrypt the values.
|
176
|
+
To supply the key to encrypt the values with, add the `key_id` parameter:
|
177
|
+
|
178
|
+
~~~ruby
|
179
|
+
Rails.application.configure do
|
180
|
+
# Read configuration from AWS Parameter Store
|
181
|
+
config.secret_config.use :ssm,
|
182
|
+
key_id: 'alias/production/myapplication',
|
183
|
+
root: '/production/my_application'
|
184
|
+
end
|
185
|
+
~~~
|
186
|
+
|
187
|
+
Note: The relevant KMS key must be created first prior to using it here.
|
188
|
+
|
189
|
+
The `key_id` is only used when writing settings to the AWS Parameter store and can be left off when that instance
|
190
|
+
will only read from the parameter store.
|
191
|
+
|
192
|
+
### Authorization
|
193
|
+
|
194
|
+
The following policy needs to be added to the AMI Group under which the application will be running:
|
195
|
+
|
196
|
+
~~~json
|
197
|
+
{
|
198
|
+
"Version": "2012-10-17",
|
199
|
+
"Statement": [
|
200
|
+
{
|
201
|
+
"Sid": "VisualEditor0",
|
202
|
+
"Effect": "Allow",
|
203
|
+
"Action": [
|
204
|
+
"ssm:PutParameter",
|
205
|
+
"ssm:GetParametersByPath",
|
206
|
+
],
|
207
|
+
"Resource": "*"
|
208
|
+
}
|
209
|
+
]
|
210
|
+
}
|
211
|
+
~~~
|
212
|
+
|
213
|
+
The above policy restricts read and write access to just the Parameter Store capabilities of AWS System Manager.
|
214
|
+
|
215
|
+
These additional Actions are not used by Secret Config, but may be useful for anyone using the AWS Console directly
|
216
|
+
to view and modify parameters:
|
217
|
+
- `ssm:DescribeParameters`
|
218
|
+
- `ssm:GetParameterHistory`
|
219
|
+
- `ssm:GetParameters`
|
220
|
+
- `ssm:GetParameter`
|
221
|
+
|
139
222
|
## Versioning
|
140
223
|
|
141
224
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
data/lib/secret_config.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
require '
|
1
|
+
require 'forwardable'
|
2
2
|
require 'secret_config/version'
|
3
3
|
require 'secret_config/errors'
|
4
4
|
require 'secret_config/registry'
|
5
|
+
require 'secret_config/railtie' if defined?(Rails)
|
5
6
|
|
6
7
|
# Centralized Configuration and Secrets Management for Ruby and Rails applications.
|
7
8
|
module SecretConfig
|
@@ -23,45 +24,65 @@ module SecretConfig
|
|
23
24
|
def_delegator :registry, :refresh!
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def self.root=(root)
|
32
|
-
@root = root
|
27
|
+
# Which provider to use along with any arguments
|
28
|
+
# The root will be overriden by env var `SECRET_CONFIG_ROOT` if present.
|
29
|
+
def self.use(provider, root: nil, **args)
|
30
|
+
@provider = create_provider(provider, args)
|
31
|
+
@root = ENV["SECRET_CONFIG_ROOT"] || root
|
33
32
|
@registry = nil if @registry
|
34
33
|
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
def self.root
|
36
|
+
@root ||= begin
|
37
|
+
root = ENV["SECRET_CONFIG_ROOT"] || ENV["RAILS_ENV"]
|
38
|
+
raise(UndefinedRootError, "Either set env var 'SECRET_CONFIG_ROOT' or call SecretConfig.use") unless root
|
39
|
+
root = "/#{root}" unless root.start_with?('/')
|
40
|
+
root
|
41
41
|
end
|
42
|
-
|
43
|
-
@provider = create_provider(provider, args)
|
44
|
-
@registry = nil if @registry
|
45
42
|
end
|
46
43
|
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
# Returns the current provider.
|
45
|
+
# If `SecretConfig.use` was not called previously it automatically use the file based provider.
|
46
|
+
def self.provider
|
47
|
+
@provider ||= begin
|
48
|
+
create_provider(:file)
|
49
|
+
end
|
50
50
|
end
|
51
51
|
|
52
52
|
def self.registry
|
53
53
|
@registry ||= SecretConfig::Registry.new(root: root, provider: provider)
|
54
54
|
end
|
55
55
|
|
56
|
+
# Filters to apply when returning the configuration
|
57
|
+
def self.filters
|
58
|
+
@filters
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.filters=(filters)
|
62
|
+
@filters = filters
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check the environment variables for a matching key and override the value returned from
|
66
|
+
# the central registry.
|
67
|
+
def self.check_env_var?
|
68
|
+
@check_env_var
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.check_env_var=(check_env_var)
|
72
|
+
@check_env_var = check_env_var
|
73
|
+
end
|
74
|
+
|
56
75
|
private
|
57
76
|
|
77
|
+
@check_env_var = true
|
78
|
+
@filters = [/password/, 'key', /secret_key/]
|
79
|
+
|
80
|
+
# Create a new provider instance unless it is alread a provider instance.
|
58
81
|
def self.create_provider(provider, args = nil)
|
82
|
+
return provider if provider.respond_to?(:each) && provider.respond_to?(:set)
|
83
|
+
|
59
84
|
klass = constantize_symbol(provider)
|
60
|
-
|
61
|
-
klass.new(**args)
|
62
|
-
else
|
63
|
-
klass.new
|
64
|
-
end
|
85
|
+
args && args.size > 0 ? klass.new(**args) : klass.new
|
65
86
|
end
|
66
87
|
|
67
88
|
def implementation
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SecretConfig
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
# Exposes Secret Config's configuration to the Rails application configuration.
|
4
|
+
#
|
5
|
+
# @example Set up configuration in the Rails app.
|
6
|
+
# module MyApplication
|
7
|
+
# class Application < Rails::Application
|
8
|
+
# config.secret_config.use :file, root: '/development'
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
config.secret_config = SecretConfig
|
12
|
+
end
|
13
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'base64'
|
2
|
+
require 'concurrent-ruby'
|
2
3
|
|
3
4
|
module SecretConfig
|
4
5
|
# Centralized configuration with values stored in AWS System Manager Parameter Store
|
@@ -10,13 +11,18 @@ module SecretConfig
|
|
10
11
|
# TODO: Validate root starts with /, etc
|
11
12
|
@root = root
|
12
13
|
@provider = provider
|
14
|
+
@registry = Concurrent::Map.new
|
13
15
|
refresh!
|
14
16
|
end
|
15
17
|
|
16
18
|
# Returns [Hash] a copy of the in memory configuration data.
|
17
|
-
def configuration
|
19
|
+
def configuration(relative: true, filters: SecretConfig.filters)
|
18
20
|
h = {}
|
19
|
-
registry.each_pair
|
21
|
+
registry.each_pair do |key, value|
|
22
|
+
key = relative_key(key) if relative
|
23
|
+
value = filter_value(key, value, filters)
|
24
|
+
decompose(key, value, h)
|
25
|
+
end
|
20
26
|
h
|
21
27
|
end
|
22
28
|
|
@@ -43,24 +49,62 @@ module SecretConfig
|
|
43
49
|
type == :string ? value : convert_type(type, value)
|
44
50
|
end
|
45
51
|
|
52
|
+
# Set the value for a key in the centralized configuration store.
|
46
53
|
def set(key:, value:, encrypt: true)
|
47
|
-
|
54
|
+
key = expand_key(key)
|
55
|
+
provider.set(key, value, encrypt: true)
|
56
|
+
registry[key] = value
|
48
57
|
end
|
49
58
|
|
59
|
+
# Refresh the in-memory cached copy of the centralized configuration information.
|
60
|
+
# Environment variable values will take precendence over the central store values.
|
50
61
|
def refresh!
|
51
|
-
|
52
|
-
|
53
|
-
|
62
|
+
existing_keys = registry.keys
|
63
|
+
updated_keys = []
|
64
|
+
provider.each(root) do |key, value|
|
65
|
+
registry[key] = env_var_override(key, value)
|
66
|
+
updated_keys << key
|
67
|
+
end
|
68
|
+
|
69
|
+
# Remove keys deleted from the registry.
|
70
|
+
(existing_keys - updated_keys).each { |key| registry.delete(key) }
|
71
|
+
|
72
|
+
true
|
54
73
|
end
|
55
74
|
|
56
75
|
private
|
57
76
|
|
58
77
|
attr_reader :registry
|
59
78
|
|
79
|
+
# Returns the value from an env var if it is present,
|
80
|
+
# Otherwise the value is returned unchanged.
|
81
|
+
def env_var_override(key, value)
|
82
|
+
env_var_name = relative_key(key).upcase.gsub('/', '_')
|
83
|
+
ENV[env_var_name] || value
|
84
|
+
end
|
85
|
+
|
86
|
+
# Add the root to the path if it is a relative path.
|
60
87
|
def expand_key(key)
|
61
88
|
key.start_with?('/') ? key : "#{root}/#{key}"
|
62
89
|
end
|
63
90
|
|
91
|
+
# Convert the key to a relative path by removing the
|
92
|
+
# root path.
|
93
|
+
def relative_key(key)
|
94
|
+
key.start_with?('/') ? key.sub("#{root}/", '') : key
|
95
|
+
end
|
96
|
+
|
97
|
+
def filter_value(key, value, filters)
|
98
|
+
return value unless filters
|
99
|
+
|
100
|
+
_, name = File.split(key)
|
101
|
+
filter = filters.any? do |filter|
|
102
|
+
filter.is_a?(Regexp) ? name =~ filter : name == filter
|
103
|
+
end
|
104
|
+
|
105
|
+
filter ? '[FILTERED]' : value
|
106
|
+
end
|
107
|
+
|
64
108
|
def decompose(key, value, h = {})
|
65
109
|
path, name = File.split(key)
|
66
110
|
last = path.split('/').reduce(h) do |target, path|
|
data/test/registry_test.rb
CHANGED
@@ -36,7 +36,15 @@ class RegistryTest < Minitest::Test
|
|
36
36
|
|
37
37
|
describe '#configuration' do
|
38
38
|
it 'returns a copy of the config' do
|
39
|
-
assert_equal "127.0.0.1", registry.configuration.dig("
|
39
|
+
assert_equal "127.0.0.1", registry.configuration.dig("mysql", "host")
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'filters passwords' do
|
43
|
+
assert_equal "[FILTERED]", registry.configuration.dig("mysql", "password")
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'filters key' do
|
47
|
+
assert_equal "[FILTERED]", registry.configuration.dig("symmetric_encryption", "key")
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
data/test/secret_config_test.rb
CHANGED
@@ -11,13 +11,12 @@ class SecretConfigTest < Minitest::Test
|
|
11
11
|
end
|
12
12
|
|
13
13
|
before do
|
14
|
-
SecretConfig.root
|
15
|
-
SecretConfig.provider :file, file_name: file_name
|
14
|
+
SecretConfig.use :file, root: root, file_name: file_name
|
16
15
|
end
|
17
16
|
|
18
17
|
describe '#configuration' do
|
19
18
|
it 'returns a copy of the config' do
|
20
|
-
assert_equal "127.0.0.1", SecretConfig.configuration.dig("
|
19
|
+
assert_equal "127.0.0.1", SecretConfig.configuration.dig("mysql", "host")
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -37,6 +36,12 @@ class SecretConfigTest < Minitest::Test
|
|
37
36
|
it 'fetches values' do
|
38
37
|
assert_equal "secret_config_development", SecretConfig.fetch("mysql/database")
|
39
38
|
end
|
39
|
+
|
40
|
+
it 'can be overridden by an environment variable' do
|
41
|
+
ENV['MYSQL_DATABASE'] = 'other'
|
42
|
+
assert_equal "other", SecretConfig.fetch("mysql/database")
|
43
|
+
ENV['MYSQL_DATABASE'] = nil
|
44
|
+
end
|
40
45
|
end
|
41
46
|
end
|
42
47
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secret_config
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-04-
|
11
|
+
date: 2019-04-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: sync_attr
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: aws-sdk-ssm
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +52,7 @@ files:
|
|
66
52
|
- lib/secret_config/errors.rb
|
67
53
|
- lib/secret_config/providers/file.rb
|
68
54
|
- lib/secret_config/providers/ssm.rb
|
55
|
+
- lib/secret_config/railtie.rb
|
69
56
|
- lib/secret_config/registry.rb
|
70
57
|
- lib/secret_config/version.rb
|
71
58
|
- test/config/application.yml
|