secret_config 0.1.0 → 0.2.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/lib/secret_config.rb +44 -4
- data/lib/secret_config/providers/file.rb +1 -1
- data/lib/secret_config/providers/ssm.rb +1 -1
- data/lib/secret_config/registry.rb +20 -28
- data/lib/secret_config/version.rb +1 -1
- data/test/config/application.yml +3 -3
- data/test/providers/file_test.rb +12 -12
- data/test/providers/ssm_test.rb +24 -16
- data/test/registry_test.rb +35 -20
- data/test/secret_config_test.rb +4 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce78920783d826865d118ba93de56be3e428d766efd502070999f7a5b1ebb069
|
4
|
+
data.tar.gz: 69bf56d09a5e20e3966531a9e75591c44376c6e5d0413d429a79dbfad11d4823
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dee07a19a4728594c49f9c207d2006dde24ce2d87e25654c274efa11de4a9e0c274b9c66c6e31b4d8043c756fa007df8a2e5db84a189bf36b5ebeef2c596138c
|
7
|
+
data.tar.gz: 3a1eb219305927565c9920943bbde78b34bb4a1ad2cc7c703cb53bef9a1c7a35a3b9d82736d7969af4c08562de85dc05bc4224f76d6c2f38474ea0d479297ec2
|
data/lib/secret_config.rb
CHANGED
@@ -24,8 +24,8 @@ module SecretConfig
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.root
|
27
|
-
@root ||= ENV["
|
28
|
-
raise(UndefinedRootError, "Either set env var '
|
27
|
+
@root ||= ENV["SECRET_CONFIG_ROOT"] ||
|
28
|
+
raise(UndefinedRootError, "Either set env var 'SECRET_CONFIG_ROOT' or call SecretConfig.root=")
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.root=(root)
|
@@ -33,8 +33,15 @@ module SecretConfig
|
|
33
33
|
@registry = nil if @registry
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
# When provider is not supplied, returns the current provider instance
|
37
|
+
# When provider is supplied, sets the new provider and stores any arguments
|
38
|
+
def self.provider(provider = nil, **args)
|
39
|
+
if provider.nil?
|
40
|
+
return @provider ||= create_provider((ENV["SECRET_CONFIG_PROVIDER"] || :file).to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
@provider = create_provider(provider, args)
|
44
|
+
@registry = nil if @registry
|
38
45
|
end
|
39
46
|
|
40
47
|
def self.provider=(provider)
|
@@ -45,4 +52,37 @@ module SecretConfig
|
|
45
52
|
def self.registry
|
46
53
|
@registry ||= SecretConfig::Registry.new(root: root, provider: provider)
|
47
54
|
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def self.create_provider(provider, args = nil)
|
59
|
+
klass = constantize_symbol(provider)
|
60
|
+
if args && args.size > 0
|
61
|
+
klass.new(**args)
|
62
|
+
else
|
63
|
+
klass.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def implementation
|
68
|
+
@implementation ||= constantize_symbol(provider).new
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.constantize_symbol(symbol, namespace = 'SecretConfig::Providers')
|
72
|
+
klass = "#{namespace}::#{camelize(symbol.to_s)}"
|
73
|
+
begin
|
74
|
+
Object.const_get(klass)
|
75
|
+
rescue NameError
|
76
|
+
raise(ArgumentError, "Could not convert symbol: #{symbol.inspect} to a class in: #{namespace}. Looking for: #{klass}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Borrow from Rails, when not running Rails
|
81
|
+
def self.camelize(term)
|
82
|
+
string = term.to_s
|
83
|
+
string = string.sub(/^[a-z\d]*/, &:capitalize)
|
84
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
|
85
|
+
string.gsub!('/'.freeze, '::'.freeze)
|
86
|
+
string
|
87
|
+
end
|
48
88
|
end
|
@@ -7,7 +7,7 @@ module SecretConfig
|
|
7
7
|
class File
|
8
8
|
attr_reader :file_name
|
9
9
|
|
10
|
-
def initialize(file_name:
|
10
|
+
def initialize(file_name: "config/application.yml")
|
11
11
|
@file_name = file_name
|
12
12
|
raise(ConfigurationError, "Cannot find config file: #{file_name}") unless ::File.exist?(file_name)
|
13
13
|
end
|
@@ -6,7 +6,7 @@ module SecretConfig
|
|
6
6
|
class Ssm
|
7
7
|
attr_reader :client, :key_id
|
8
8
|
|
9
|
-
def initialize(key_id:
|
9
|
+
def initialize(key_id: nil)
|
10
10
|
@key_id = key_id
|
11
11
|
logger = SemanticLogger['Aws::SSM'] if defined?(SemanticLogger)
|
12
12
|
@client = Aws::SSM::Client.new(logger: logger)
|
@@ -2,16 +2,11 @@ require 'base64'
|
|
2
2
|
|
3
3
|
module SecretConfig
|
4
4
|
# Centralized configuration with values stored in AWS System Manager Parameter Store
|
5
|
-
#
|
6
|
-
# Values are fetched from the central store on startup. Only those values starting with the specified
|
7
|
-
# root are loaded, supply multiple paths using the env var SECRETCONFIG_PATHS.
|
8
|
-
#
|
9
|
-
# Existing event mechanisms can be used to force a reload of the cached copy.
|
10
5
|
class Registry
|
11
6
|
attr_reader :provider
|
12
7
|
attr_accessor :root
|
13
8
|
|
14
|
-
def initialize(root:, provider:
|
9
|
+
def initialize(root:, provider:)
|
15
10
|
# TODO: Validate root starts with /, etc
|
16
11
|
@root = root
|
17
12
|
@provider = provider
|
@@ -21,7 +16,7 @@ module SecretConfig
|
|
21
16
|
# Returns [Hash] a copy of the in memory configuration data.
|
22
17
|
def configuration
|
23
18
|
h = {}
|
24
|
-
registry.each_pair { |
|
19
|
+
registry.each_pair { |k, v| decompose(k, v, h) }
|
25
20
|
h
|
26
21
|
end
|
27
22
|
|
@@ -54,7 +49,7 @@ module SecretConfig
|
|
54
49
|
|
55
50
|
def refresh!
|
56
51
|
h = {}
|
57
|
-
|
52
|
+
provider.each(root) { |k, v| h[k] = v }
|
58
53
|
@registry = h
|
59
54
|
end
|
60
55
|
|
@@ -66,8 +61,19 @@ module SecretConfig
|
|
66
61
|
key.start_with?('/') ? key : "#{root}/#{key}"
|
67
62
|
end
|
68
63
|
|
69
|
-
def
|
70
|
-
|
64
|
+
def decompose(key, value, h = {})
|
65
|
+
path, name = File.split(key)
|
66
|
+
last = path.split('/').reduce(h) do |target, path|
|
67
|
+
if path == ''
|
68
|
+
target
|
69
|
+
elsif target.key?(path)
|
70
|
+
target[path]
|
71
|
+
else
|
72
|
+
target[path] = {}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
last[name] = value
|
76
|
+
h
|
71
77
|
end
|
72
78
|
|
73
79
|
def convert_encoding(encoding, value)
|
@@ -87,26 +93,12 @@ module SecretConfig
|
|
87
93
|
value.to_f
|
88
94
|
when :string
|
89
95
|
value
|
96
|
+
when :boolean
|
97
|
+
%w[true 1 t].include?(value.to_s.downcase)
|
98
|
+
when :symbol
|
99
|
+
value.to_sym unless value.nil? || value.to_s.strip == ''
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
93
|
-
def constantize_symbol(symbol, namespace = 'SecretConfig::Providers')
|
94
|
-
klass = "#{namespace}::#{camelize(symbol.to_s)}"
|
95
|
-
begin
|
96
|
-
Object.const_get(klass)
|
97
|
-
rescue NameError
|
98
|
-
raise(ArgumentError, "Could not convert symbol: #{symbol.inspect} to a class in: #{namespace}. Looking for: #{klass}")
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Borrow from Rails, when not running Rails
|
103
|
-
def camelize(term)
|
104
|
-
string = term.to_s
|
105
|
-
string = string.sub(/^[a-z\d]*/, &:capitalize)
|
106
|
-
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
|
107
|
-
string.gsub!('/'.freeze, '::'.freeze)
|
108
|
-
string
|
109
|
-
end
|
110
|
-
|
111
103
|
end
|
112
104
|
end
|
data/test/config/application.yml
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
# These are for development and test only.
|
3
3
|
|
4
4
|
#
|
5
|
-
# Development - Local - Root: '/development/
|
5
|
+
# Development - Local - Root: '/development/my_application'
|
6
6
|
#
|
7
7
|
development:
|
8
|
-
|
8
|
+
my_application:
|
9
9
|
symmetric_encryption:
|
10
10
|
key: QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=
|
11
11
|
iv: QUJDREVGMTIzNDU2Nzg5MA==
|
@@ -26,7 +26,7 @@ development:
|
|
26
26
|
secret_key_base: somereallylongstring
|
27
27
|
|
28
28
|
test:
|
29
|
-
|
29
|
+
my_application:
|
30
30
|
symmetric_encryption:
|
31
31
|
key: QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=
|
32
32
|
iv: QUJDREVGMTIzNDU2Nzg5MA==
|
data/test/providers/file_test.rb
CHANGED
@@ -8,22 +8,22 @@ module Providers
|
|
8
8
|
end
|
9
9
|
|
10
10
|
let :root do
|
11
|
-
"/development/
|
11
|
+
"/development/my_application"
|
12
12
|
end
|
13
13
|
|
14
14
|
let :expected do
|
15
15
|
{
|
16
|
-
"/development/
|
17
|
-
"/development/
|
18
|
-
"/development/
|
19
|
-
"/development/
|
20
|
-
"/development/
|
21
|
-
"/development/
|
22
|
-
"/development/
|
23
|
-
"/development/
|
24
|
-
"/development/
|
25
|
-
"/development/
|
26
|
-
"/development/
|
16
|
+
"/development/my_application/mongo/database" => "secret_config_development",
|
17
|
+
"/development/my_application/mongo/primary" => "127.0.0.1:27017",
|
18
|
+
"/development/my_application/mongo/secondary" => "127.0.0.1:27018",
|
19
|
+
"/development/my_application/mysql/database" => "secret_config_development",
|
20
|
+
"/development/my_application/mysql/password" => "secret_configrules",
|
21
|
+
"/development/my_application/mysql/username" => "secret_config",
|
22
|
+
"/development/my_application/mysql/host" => "127.0.0.1",
|
23
|
+
"/development/my_application/secrets/secret_key_base" => "somereallylongstring",
|
24
|
+
"/development/my_application/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
|
25
|
+
"/development/my_application/symmetric_encryption/version" => 2,
|
26
|
+
"/development/my_application/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
data/test/providers/ssm_test.rb
CHANGED
@@ -8,42 +8,50 @@ module Providers
|
|
8
8
|
end
|
9
9
|
|
10
10
|
let :root do
|
11
|
-
"/development/
|
11
|
+
"/development/my_application"
|
12
12
|
end
|
13
13
|
|
14
14
|
let :expected do
|
15
15
|
{
|
16
|
-
"/development/
|
17
|
-
"/development/
|
18
|
-
"/development/
|
19
|
-
"/development/
|
20
|
-
"/development/
|
21
|
-
"/development/
|
22
|
-
"/development/
|
23
|
-
"/development/
|
24
|
-
"/development/
|
25
|
-
"/development/
|
26
|
-
"/development/
|
16
|
+
"/development/my_application/mongo/database" => "secret_config_development",
|
17
|
+
"/development/my_application/mongo/primary" => "127.0.0.1:27017",
|
18
|
+
"/development/my_application/mongo/secondary" => "127.0.0.1:27018",
|
19
|
+
"/development/my_application/mysql/database" => "secret_config_development",
|
20
|
+
"/development/my_application/mysql/password" => "secret_configrules",
|
21
|
+
"/development/my_application/mysql/username" => "secret_config",
|
22
|
+
"/development/my_application/mysql/host" => "127.0.0.1",
|
23
|
+
"/development/my_application/secrets/secret_key_base" => "somereallylongstring",
|
24
|
+
"/development/my_application/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
|
25
|
+
"/development/my_application/symmetric_encryption/version" => "2",
|
26
|
+
"/development/my_application/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
|
27
27
|
}
|
28
28
|
end
|
29
29
|
|
30
|
+
before do
|
31
|
+
unless ENV['AWS_ACCESS_KEY_ID']
|
32
|
+
skip "Skipping AWS SSM Parameter Store tests because env var 'AWS_ACCESS_KEY_ID' is not defined."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
30
36
|
describe '#each' do
|
31
37
|
it 'fetches all keys in path' do
|
32
|
-
upload_settings if ENV['SECRETCONFIG_TEST_UPLOAD_SSM']
|
33
|
-
|
34
38
|
ssm = SecretConfig::Providers::Ssm.new
|
35
39
|
paths = {}
|
36
40
|
ssm.each(root) { |key, value| paths[key] = value }
|
37
41
|
|
42
|
+
if paths.empty?
|
43
|
+
upload_settings(ssm) unless ssm.key?("/development/my_application/mongo/database")
|
44
|
+
ssm.each(root) { |key, value| paths[key] = value }
|
45
|
+
end
|
46
|
+
|
38
47
|
expected.each_pair do |key, value|
|
39
48
|
assert_equal paths[key], value, "Path: #{key}"
|
40
49
|
end
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
|
-
def upload_settings
|
53
|
+
def upload_settings(ssm)
|
45
54
|
file_provider = SecretConfig::Providers::File.new(file_name: file_name)
|
46
|
-
ssm = SecretConfig::Providers::Ssm.new
|
47
55
|
file_provider.each(root) { |key, value| ap key; ssm.set(key, value) }
|
48
56
|
end
|
49
57
|
end
|
data/test/registry_test.rb
CHANGED
@@ -7,38 +7,36 @@ class RegistryTest < Minitest::Test
|
|
7
7
|
end
|
8
8
|
|
9
9
|
let :root do
|
10
|
-
"/development/
|
10
|
+
"/development/my_application"
|
11
11
|
end
|
12
12
|
|
13
|
-
let :
|
14
|
-
|
13
|
+
let :provider do
|
14
|
+
SecretConfig::Providers::File.new(file_name: file_name)
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
+
let :registry do
|
18
|
+
SecretConfig::Registry.new(root: root, provider: provider)
|
17
19
|
end
|
18
20
|
|
19
21
|
let :expected do
|
20
22
|
{
|
21
|
-
"/development/
|
22
|
-
"/development/
|
23
|
-
"/development/
|
24
|
-
"/development/
|
25
|
-
"/development/
|
26
|
-
"/development/
|
27
|
-
"/development/
|
28
|
-
"/development/
|
29
|
-
"/development/
|
30
|
-
"/development/
|
31
|
-
"/development/
|
23
|
+
"/development/my_application/mongo/database" => "secret_config_development",
|
24
|
+
"/development/my_application/mongo/primary" => "127.0.0.1:27017",
|
25
|
+
"/development/my_application/mongo/secondary" => "127.0.0.1:27018",
|
26
|
+
"/development/my_application/mysql/database" => "secret_config_development",
|
27
|
+
"/development/my_application/mysql/password" => "secret_configrules",
|
28
|
+
"/development/my_application/mysql/username" => "secret_config",
|
29
|
+
"/development/my_application/mysql/host" => "127.0.0.1",
|
30
|
+
"/development/my_application/secrets/secret_key_base" => "somereallylongstring",
|
31
|
+
"/development/my_application/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
|
32
|
+
"/development/my_application/symmetric_encryption/version" => 2,
|
33
|
+
"/development/my_application/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
|
32
34
|
}
|
33
35
|
end
|
34
36
|
|
35
37
|
describe '#configuration' do
|
36
38
|
it 'returns a copy of the config' do
|
37
|
-
|
38
|
-
|
39
|
-
expected.each_pair do |key, value|
|
40
|
-
assert_equal value, paths[key], "Path: #{key}"
|
41
|
-
end
|
39
|
+
assert_equal "127.0.0.1", registry.configuration.dig("development", "my_application", "mysql", "host")
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
@@ -108,5 +106,22 @@ class RegistryTest < Minitest::Test
|
|
108
106
|
assert_equal "ABCDEF1234567890ABCDEF1234567890", registry.fetch("symmetric_encryption/key", encoding: :base64)
|
109
107
|
end
|
110
108
|
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def decompose(key, value, h = {})
|
113
|
+
path, name = File.split(key)
|
114
|
+
last = path.split('/').reduce(h) do |target, path|
|
115
|
+
if path == ''
|
116
|
+
target
|
117
|
+
elsif target.key?(path)
|
118
|
+
target[path]
|
119
|
+
else
|
120
|
+
target[path] = {}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
last[name] = value
|
124
|
+
h
|
125
|
+
end
|
111
126
|
end
|
112
127
|
end
|
data/test/secret_config_test.rb
CHANGED
@@ -7,18 +7,17 @@ class SecretConfigTest < Minitest::Test
|
|
7
7
|
end
|
8
8
|
|
9
9
|
let :root do
|
10
|
-
"/development/
|
10
|
+
"/development/my_application"
|
11
11
|
end
|
12
12
|
|
13
13
|
before do
|
14
|
-
|
15
|
-
SecretConfig.
|
16
|
-
SecretConfig.provider = :file
|
14
|
+
SecretConfig.root = root
|
15
|
+
SecretConfig.provider :file, file_name: file_name
|
17
16
|
end
|
18
17
|
|
19
18
|
describe '#configuration' do
|
20
19
|
it 'returns a copy of the config' do
|
21
|
-
assert_equal "127.0.0.1", SecretConfig.configuration
|
20
|
+
assert_equal "127.0.0.1", SecretConfig.configuration.dig("development", "my_application", "mysql", "host")
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
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.2.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-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|