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