secret_config 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a86408c0a69f9e1c3e67a06d004c659abe13f7b482a3ff11e8de6b99c650d05
4
- data.tar.gz: cd16668968bf171809b47b2c3b749de2ef63a7fb91245cdf68bac253aeb5639b
3
+ metadata.gz: 24792842f7d81af2c5d70eb431eae87ec96f190a52500c7e68267b6e875a4e09
4
+ data.tar.gz: 29f1683c2be73597476f976860dd021476559d97ed94e04195789fadaf4fb7f8
5
5
  SHA512:
6
- metadata.gz: b71279e1a7855b8e081239f3eea1bf05c3c6edc5e4226e8e31e347667480156c493f87443d09bc060a2829a7cd87ab1acdcba9e0cad9811c634355b4944ecea7
7
- data.tar.gz: ce127ef03fa642011deb394e3f3cd3de94e8d4ace3de712dbc218dd080ed4ea03ce2ff06c8ee35d32e2b06765e08c93a92cf2066ca072e4d16380e52d2d58504
6
+ metadata.gz: 89a0ecfab4bfb8263768ccf51ec56b987866cac08d23c2c9e34a69b9ba3210d9ed40198cfd525d72f54dcf4e6aa0f2c8965d30204ec96c4b825f1c61313c35b5
7
+ data.tar.gz: c0047de74541f0589ebe2ad66a67133b73d6fa5635610d81b685394aca395d46dfadc4171ca655975296289292203dc004a977fe265c6b0d704ad4a894cc6983
data/README.md CHANGED
@@ -5,6 +5,13 @@ Centralized Configuration and Secrets Management for Ruby and Rails applications
5
5
 
6
6
  Securely store configuration information centrally, supporting multiple tenants of the same application.
7
7
 
8
+ ## Overview
9
+
10
+ Securely store centralized configuration information such as:
11
+ * Settings
12
+ * Passwords
13
+ * Encryption keys and certificates
14
+
8
15
  ## Features
9
16
 
10
17
  Supports storing configuration information in:
@@ -15,6 +22,17 @@ Supports storing configuration information in:
15
22
  * AWS System Manager Parameter Store
16
23
  * Encrypt and securely store secrets such as passwords centrally.
17
24
 
25
+ Supported data types:
26
+ * integer
27
+ * float
28
+ * string
29
+ * boolean
30
+ * symbol
31
+ * json
32
+
33
+ Supported conversions:
34
+ * base64
35
+
18
36
  ## Benefits
19
37
 
20
38
  Benefits of moving sensitive configuration information into AWS System Manager Parameter Store:
@@ -23,7 +41,7 @@ Benefits of moving sensitive configuration information into AWS System Manager P
23
41
  * Environment variables force all config into a single level.
24
42
  * Reduces the number of environment variables.
25
43
  * In a large application the number of secrets can grow dramatically.
26
- * Removes the need to encrypt sensitive data config files.
44
+ * Replaces sensitive data stored in local yaml or configuration files.
27
45
  * Including securing and managing encryption keys.
28
46
  * When encryption keys change, such as during a key rotation, config files don't have to be changed.
29
47
  * Removes security concerns with placing passwords in the clear into environment variables.
@@ -222,7 +240,7 @@ Note: Do not put any production credentials into this file.
222
240
 
223
241
  ### Environment Variables
224
242
 
225
- Any of the above values can be overridden with an environment variable.
243
+ Any of the above values can be overridden with an environment variable, unless explicitly configured `SecretConfig.check_env_var = false`.
226
244
 
227
245
  To overwrite any of these settings with an environment variable:
228
246
 
@@ -471,6 +489,49 @@ to view and modify parameters:
471
489
  - `ssm:GetParameters`
472
490
  - `ssm:GetParameter`
473
491
 
492
+ ## String Interpolation
493
+
494
+ Values supplied for config settings can be replaced inline with date, time, hostname, pid and random values.
495
+
496
+ For example to include the `hostname` in the log file name setting:
497
+
498
+ ~~~yaml
499
+ development:
500
+ logger:
501
+ level: info
502
+ file_name: /var/log/my_application_%{hostname}.log
503
+ ~~~
504
+
505
+ Available interpolations:
506
+
507
+ * %{date}
508
+ * Current date in the format of "%Y%m%d" (CCYYMMDD)
509
+ * %{date:format}
510
+ * Current date in the supplied format. See strftime
511
+ * %{time}
512
+ * Current date and time down to ms in the format of "%Y%m%d%Y%H%M%S%L" (CCYYMMDDHHMMSSmmm)
513
+ * %{time:format}
514
+ * Current date and time in the supplied format. See strftime
515
+ * %{env:name}
516
+ * Extract value from the named environment variable.
517
+ * %{hostname}
518
+ * Full name of this host.
519
+ * %{hostname:short}
520
+ * Short name of this host. Everything up to the first period.
521
+ * %{pid}
522
+ * Process Id for this process.
523
+ * %{random}
524
+ * URL safe Random 32 byte value.
525
+ * %{random:size}
526
+ * URL safe Random value of `size` bytes.
527
+
528
+ #### Notes:
529
+
530
+ * To prevent interpolation use %%{...}
531
+ * %% is not touched, only %{...} is searched for.
532
+ * Since these interpolations are only evaluated at load time and
533
+ every time the registry is refreshed there is no runtime overhead when keys are fetched.
534
+
474
535
  ## Command Line Interface
475
536
 
476
537
  Secret Config has a command line interface for exporting, importing and copying between paths in the registry.
@@ -10,4 +10,7 @@ module SecretConfig
10
10
 
11
11
  class ConfigurationError < Error
12
12
  end
13
+
14
+ class InvalidInterpolation < Error
15
+ end
13
16
  end
@@ -62,7 +62,8 @@ module SecretConfig
62
62
  value: value.to_s,
63
63
  type: "SecureString",
64
64
  key_id: key_id,
65
- overwrite: true
65
+ overwrite: true,
66
+ tier: "Intelligent-Tiering"
66
67
  )
67
68
  end
68
69
 
@@ -32,7 +32,13 @@ module SecretConfig
32
32
 
33
33
  # Returns [String] configuration value for the supplied key, or nil when missing.
34
34
  def [](key)
35
- cache[expand_key(key)]
35
+ full_key = expand_key(key)
36
+ value = cache[full_key]
37
+ if value.nil? && SecretConfig.check_env_var?
38
+ value = env_var_override(key, value)
39
+ cache[full_key] = value unless value.nil?
40
+ end
41
+ value
36
42
  end
37
43
 
38
44
  # Returns [String] configuration value for the supplied key, or nil when missing.
@@ -45,8 +51,7 @@ module SecretConfig
45
51
  value = self[key]
46
52
  if value.nil?
47
53
  raise(MissingMandatoryKey, "Missing configuration value for #{path}/#{key}") if default == :no_default_supplied
48
-
49
- value = default.respond_to?(:call) ? default.call : default
54
+ value = block_given? ? yield : default
50
55
  end
51
56
 
52
57
  value = convert_encoding(encoding, value) if encoding
@@ -78,7 +83,8 @@ module SecretConfig
78
83
  existing_keys = cache.keys
79
84
  updated_keys = []
80
85
  provider.each(path) do |key, value|
81
- cache[key] = env_var_override(key, value)
86
+ value = interpolator.parse(value) if value.is_a?(String) && value.include?('%{')
87
+ cache[key] = env_var_override(relative_key(key), value)
82
88
  updated_keys << key
83
89
  end
84
90
 
@@ -92,10 +98,16 @@ module SecretConfig
92
98
 
93
99
  attr_reader :cache
94
100
 
101
+ def interpolator
102
+ @interpolator ||= SettingInterpolator.new
103
+ end
104
+
95
105
  # Returns the value from an env var if it is present,
96
106
  # Otherwise the value is returned unchanged.
97
107
  def env_var_override(key, value)
98
- env_var_name = relative_key(key).upcase.gsub("/", "_")
108
+ return value unless SecretConfig.check_env_var?
109
+
110
+ env_var_name = key.upcase.gsub("/", "_")
99
111
  ENV[env_var_name] || value
100
112
  end
101
113
 
@@ -0,0 +1,58 @@
1
+ require 'date'
2
+ require 'socket'
3
+ require 'securerandom'
4
+ # * SecretConfig Interpolations
5
+ #
6
+ # Expanding values inline for date, time, hostname, pid and random values.
7
+ # %{date} # Current date in the format of "%Y%m%d" (CCYYMMDD)
8
+ # %{date:format} # Current date in the supplied format. See strftime
9
+ # %{time} # Current date and time down to ms in the format of "%Y%m%d%Y%H%M%S%L" (CCYYMMDDHHMMSSmmm)
10
+ # %{time:format} # Current date and time in the supplied format. See strftime
11
+ # %{env:name} # Extract value from the named environment value.
12
+ # %{hostname} # Full name of this host.
13
+ # %{hostname:short} # Short name of this host. Everything up to the first period.
14
+ # %{pid} # Process Id for this process.
15
+ # %{random} # URL safe Random 32 byte value.
16
+ # %{random:size} # URL safe Random value of `size` bytes.
17
+ #
18
+ # Retrieve values elsewhere in the registry.
19
+ # Paths can be relative to the current root, or absolute paths outside the current root.
20
+ # %{fetch:key} # Fetches a single value from a relative or absolute path
21
+ # %{include:path} # Fetches a path of keys and values
22
+ module SecretConfig
23
+ class SettingInterpolator < StringInterpolator
24
+ def date(format = "%Y%m%d")
25
+ Date.today.strftime(format)
26
+ end
27
+
28
+ def time(format = "%Y%m%d%H%M%S%L")
29
+ Time.now.strftime(format)
30
+ end
31
+
32
+ def env(name)
33
+ ENV[name]
34
+ end
35
+
36
+ def hostname(format = nil)
37
+ name = Socket.gethostname
38
+ name = name.split('.')[0] if format == "short"
39
+ name
40
+ end
41
+
42
+ def pid
43
+ $$
44
+ end
45
+
46
+ def random(size = 32)
47
+ SecureRandom.urlsafe_base64(size)
48
+ end
49
+
50
+ #def fetch(key)
51
+ # SecretConfig[key]
52
+ #end
53
+ #
54
+ #def include(path)
55
+ #
56
+ #end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ # Parse strings containing %{key:value1,value2,value3}
2
+ # Where `key` is a method implemented by a class inheriting from this class
3
+ #
4
+ # The following `key`s are reserved:
5
+ # * parse
6
+ # * initialize
7
+ #
8
+ # Notes:
9
+ # * To prevent interpolation use %%{...}
10
+ # * %% is not touched, only %{...} is identified.
11
+ module SecretConfig
12
+ class StringInterpolator
13
+ def initialize(pattern = nil)
14
+ @pattern = pattern || /%{1,2}\{([^}]+)\}/
15
+ end
16
+
17
+ def parse(string)
18
+ string.gsub(/%{1,2}\{([^}]+)\}/) do |match|
19
+ if match.start_with?('%%')
20
+ match[1..-1]
21
+ else
22
+ expr = $1 || $2 || match.tr("%{}", "")
23
+ key, args_str = expr.split(':')
24
+ key = key.strip.to_sym
25
+ arguments = args_str&.split(',')&.map { |v| v.strip == '' ? nil : v.strip } || []
26
+ raise(InvalidInterpolation, "Invalid key: #{key} in string: #{match}") unless respond_to?(key)
27
+ public_send(key, *arguments)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module SecretConfig
2
- VERSION = "0.6.4".freeze
2
+ VERSION = "0.7.0".freeze
3
3
  end
data/lib/secret_config.rb CHANGED
@@ -18,6 +18,8 @@ module SecretConfig
18
18
  end
19
19
 
20
20
  autoload :CLI, "secret_config/cli"
21
+ autoload :SettingInterpolator, "secret_config/setting_interpolator"
22
+ autoload :StringInterpolator, "secret_config/string_interpolator"
21
23
  autoload :Utils, "secret_config/utils"
22
24
 
23
25
  class << self
@@ -65,8 +67,6 @@ module SecretConfig
65
67
  @check_env_var = check_env_var
66
68
  end
67
69
 
68
- private
69
-
70
70
  @check_env_var = true
71
71
  @filters = [/password/i, /key\Z/i, /passphrase/i]
72
72
  end
@@ -41,7 +41,7 @@ test:
41
41
  mongo:
42
42
  database: secret_config_test
43
43
  primary: 127.0.0.1:27017
44
- secondary: 127.0.0.1:27018
44
+ secondary: "%{hostname}:27018"
45
45
 
46
46
  secrets:
47
47
  secret_key_base: somereallylongteststring
@@ -15,7 +15,7 @@ module Providers
15
15
  {
16
16
  "/test/my_application/mongo/database" => "secret_config_test",
17
17
  "/test/my_application/mongo/primary" => "127.0.0.1:27017",
18
- "/test/my_application/mongo/secondary" => "127.0.0.1:27018",
18
+ "/test/my_application/mongo/secondary" => "%{hostname}:27018",
19
19
  "/test/my_application/mysql/database" => "secret_config_test",
20
20
  "/test/my_application/mysql/password" => "secret_configrules",
21
21
  "/test/my_application/mysql/username" => "secret_config",
@@ -1,4 +1,5 @@
1
1
  require_relative 'test_helper'
2
+ require 'socket'
2
3
 
3
4
  class RegistryTest < Minitest::Test
4
5
  describe SecretConfig::Registry do
@@ -22,7 +23,7 @@ class RegistryTest < Minitest::Test
22
23
  {
23
24
  "/test/my_application/mongo/database" => "secret_config_test",
24
25
  "/test/my_application/mongo/primary" => "127.0.0.1:27017",
25
- "/test/my_application/mongo/secondary" => "127.0.0.1:27018",
26
+ "/test/my_application/mongo/secondary" => "#{Socket.gethostname}:27018",
26
27
  "/test/my_application/mysql/database" => "secret_config_test",
27
28
  "/test/my_application/mysql/password" => "secret_configrules",
28
29
  "/test/my_application/mysql/username" => "secret_config",
@@ -1,9 +1,10 @@
1
- require_relative 'test_helper'
1
+ require_relative "test_helper"
2
+ require "socket"
2
3
 
3
4
  class SecretConfigTest < Minitest::Test
4
5
  describe SecretConfig::Providers::File do
5
6
  let :file_name do
6
- File.join(File.dirname(__FILE__), 'config', 'application.yml')
7
+ File.join(File.dirname(__FILE__), "config", "application.yml")
7
8
  end
8
9
 
9
10
  let :path do
@@ -14,39 +15,56 @@ class SecretConfigTest < Minitest::Test
14
15
  SecretConfig.use :file, path: path, file_name: file_name
15
16
  end
16
17
 
17
- describe '#configuration' do
18
- it 'returns a copy of the config' do
18
+ describe "#configuration" do
19
+ it "returns a copy of the config" do
19
20
  assert_equal "127.0.0.1", SecretConfig.configuration.dig("mysql", "host")
20
21
  end
21
22
  end
22
23
 
23
- describe '#key?' do
24
- it 'has key' do
24
+ describe "#key?" do
25
+ it "has key" do
25
26
  assert SecretConfig.key?("mysql/database")
26
27
  end
27
28
  end
28
29
 
29
- describe '#[]' do
30
- it 'returns values' do
30
+ describe "#[]" do
31
+ it "returns values" do
31
32
  assert_equal "secret_config_test", SecretConfig["mysql/database"]
32
33
  end
34
+
35
+ it "returns values with interpolation" do
36
+ assert_equal "#{Socket.gethostname}:27018", SecretConfig["mongo/secondary"]
37
+ end
33
38
  end
34
39
 
35
- describe '#fetch' do
40
+ describe "#fetch" do
36
41
  after do
37
- ENV['MYSQL_DATABASE'] = nil
42
+ ENV["MYSQL_DATABASE"] = nil
43
+ SecretConfig.check_env_var = true
38
44
  end
39
45
 
40
- it 'fetches values' do
46
+ it "fetches values" do
41
47
  assert_equal "secret_config_test", SecretConfig.fetch("mysql/database")
42
48
  end
43
49
 
44
- it 'can be overridden by an environment variable' do
45
- ENV['MYSQL_DATABASE'] = 'other'
50
+ it "can be overridden by an environment variable" do
51
+ ENV["MYSQL_DATABASE"] = "other"
46
52
 
47
53
  SecretConfig.use :file, path: path, file_name: file_name
48
54
  assert_equal "other", SecretConfig.fetch("mysql/database")
49
55
  end
56
+
57
+ it "returns values with interpolation" do
58
+ assert_equal "#{Socket.gethostname}:27018", SecretConfig.fetch("mongo/secondary")
59
+ end
60
+
61
+ it "can be omitted an environment variable override with #check_env_var configuration" do
62
+ ENV["MYSQL_DATABASE"] = "other"
63
+
64
+ SecretConfig.check_env_var = false
65
+ SecretConfig.use :file, path: path, file_name: file_name
66
+ assert_equal "secret_config_test", SecretConfig.fetch("mysql/database")
67
+ end
50
68
  end
51
69
  end
52
70
  end
@@ -0,0 +1,152 @@
1
+ require_relative 'test_helper'
2
+ module SecretConfig
3
+ class SettingInterpolatorTest < Minitest::Test
4
+ describe SettingInterpolator do
5
+ let(:interpolator) { SettingInterpolator.new }
6
+
7
+ describe '#parse' do
8
+ it "handles good key" do
9
+ string = "Set a date of %{date} here."
10
+ expected = string.gsub("%{date}", Date.today.strftime("%Y%m%d"))
11
+ actual = interpolator.parse(string)
12
+ assert_equal expected, actual, string
13
+ end
14
+
15
+ it "handles multiple keys" do
16
+ string = "%{pid}: Set a date of %{date} here and a %{time:%H%M} here and for luck %{pid}"
17
+ expected = string.gsub("%{date}", Date.today.strftime("%Y%m%d"))
18
+ expected = expected.gsub("%{time:%H%M}", Time.now.strftime("%H%M"))
19
+ expected = expected.gsub("%{pid}", $$.to_s)
20
+ actual = interpolator.parse(string)
21
+ assert_equal expected, actual, string
22
+ end
23
+
24
+ it "handles bad key" do
25
+ string = "Set a date of %{blah} here."
26
+ assert_raises InvalidInterpolation do
27
+ interpolator.parse(string)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "#date" do
33
+ it 'interpolates date only' do
34
+ string = "%{date}"
35
+ expected = Date.today.strftime("%Y%m%d")
36
+ actual = interpolator.parse(string)
37
+ assert_equal expected, actual, string
38
+ end
39
+
40
+ it 'interpolates date' do
41
+ string = "Set a date of %{date} here."
42
+ expected = string.gsub("%{date}", Date.today.strftime("%Y%m%d"))
43
+ actual = interpolator.parse(string)
44
+ assert_equal expected, actual, string
45
+ end
46
+
47
+ it 'interpolates date with custom format' do
48
+ string = "Set a custom %{date:%m%d%Y} here."
49
+ expected = string.gsub("%{date:%m%d%Y}", Date.today.strftime("%m%d%Y"))
50
+ actual = interpolator.parse(string)
51
+ assert_equal expected, actual, string
52
+ end
53
+ end
54
+
55
+ describe "#time" do
56
+ it 'interpolates time only' do
57
+ string = "%{time}"
58
+ time = Time.now
59
+ Time.stub(:now, time) do
60
+ expected = Time.now.strftime("%Y%m%d%H%M%S%L")
61
+ actual = interpolator.parse(string)
62
+ assert_equal expected, actual, string
63
+ end
64
+ end
65
+
66
+ it 'interpolates time' do
67
+ string = "Set a time of %{time} here."
68
+ time = Time.now
69
+ Time.stub(:now, time) do
70
+ expected = string.gsub("%{time}", Time.now.strftime("%Y%m%d%H%M%S%L"))
71
+ actual = interpolator.parse(string)
72
+ assert_equal expected, actual, string
73
+ end
74
+ end
75
+
76
+ it 'interpolates time with custom format' do
77
+ string = "Set a custom time of %{time:%H%M} here."
78
+ expected = string.gsub("%{time:%H%M}", Time.now.strftime("%H%M"))
79
+ actual = interpolator.parse(string)
80
+ assert_equal expected, actual, string
81
+ end
82
+ end
83
+
84
+ describe "#env" do
85
+ before do
86
+ ENV["TEST_SETTING"] = "Secret"
87
+ end
88
+
89
+ it 'fetches existing ENV var' do
90
+ string = "%{env:TEST_SETTING}"
91
+ actual = interpolator.parse(string)
92
+ assert_equal "Secret", actual, string
93
+ end
94
+
95
+ it 'fetches existing ENV var into a larger string' do
96
+ string = "Hello %{env:TEST_SETTING}. How are you?"
97
+ actual = interpolator.parse(string)
98
+ expected = string.gsub("%{env:TEST_SETTING}", "Secret")
99
+ assert_equal expected, actual, string
100
+ end
101
+
102
+ it 'handles missing ENV var' do
103
+ string = "%{env:OTHER_TEST_SETTING}"
104
+ actual = interpolator.parse(string)
105
+ assert_equal "", actual, string
106
+ end
107
+ end
108
+
109
+ describe "#hostname" do
110
+ it 'returns hostname' do
111
+ string = "%{hostname}"
112
+ actual = interpolator.parse(string)
113
+ assert_equal Socket.gethostname, actual, string
114
+ end
115
+
116
+ it 'returns short hostname' do
117
+ string = "%{hostname:short}"
118
+ actual = interpolator.parse(string)
119
+ assert_equal Socket.gethostname.split('.')[0], actual, string
120
+ end
121
+ end
122
+
123
+ describe "#pid" do
124
+ it 'returns process id' do
125
+ string = "%{pid}"
126
+ actual = interpolator.parse(string)
127
+ assert_equal $$.to_s, actual, string
128
+ end
129
+ end
130
+
131
+ describe "#random" do
132
+ it 'interpolates random 32 byte string' do
133
+ string = "%{random}"
134
+ random = SecureRandom.urlsafe_base64(32)
135
+ SecureRandom.stub(:urlsafe_base64, random) do
136
+ actual = interpolator.parse(string)
137
+ assert_equal random, actual, string
138
+ end
139
+ end
140
+
141
+ it 'interpolates custom length random string' do
142
+ string = "%{random:64}"
143
+ random = SecureRandom.urlsafe_base64(64)
144
+ SecureRandom.stub(:urlsafe_base64, random) do
145
+ actual = interpolator.parse(string)
146
+ assert_equal random, actual, string
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ 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.6.4
4
+ version: 0.7.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-11-21 00:00:00.000000000 Z
11
+ date: 2020-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -44,6 +44,8 @@ files:
44
44
  - lib/secret_config/providers/ssm.rb
45
45
  - lib/secret_config/railtie.rb
46
46
  - lib/secret_config/registry.rb
47
+ - lib/secret_config/setting_interpolator.rb
48
+ - lib/secret_config/string_interpolator.rb
47
49
  - lib/secret_config/utils.rb
48
50
  - lib/secret_config/version.rb
49
51
  - test/config/application.yml
@@ -51,6 +53,7 @@ files:
51
53
  - test/providers/ssm_test.rb
52
54
  - test/registry_test.rb
53
55
  - test/secret_config_test.rb
56
+ - test/setting_interpolator_test.rb
54
57
  - test/test_helper.rb
55
58
  - test/utils_test.rb
56
59
  homepage: https://github.com/rocketjob/secret_config
@@ -81,6 +84,7 @@ test_files:
81
84
  - test/providers/ssm_test.rb
82
85
  - test/providers/file_test.rb
83
86
  - test/registry_test.rb
87
+ - test/setting_interpolator_test.rb
84
88
  - test/test_helper.rb
85
89
  - test/utils_test.rb
86
90
  - test/secret_config_test.rb