secret_config 0.9.1 → 0.10.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/secret_config.rb CHANGED
@@ -97,5 +97,5 @@ module SecretConfig
97
97
  end
98
98
 
99
99
  @check_env_var = true
100
- @filters = [/password/i, /key\Z/i, /passphrase/i]
100
+ @filters = [/password/i, /key\Z/i, /passphrase/i, /secret/i, /pwd\Z/i]
101
101
  end
@@ -210,7 +210,13 @@ module SecretConfig
210
210
  begin
211
211
  case provider
212
212
  when :ssm
213
- Providers::Ssm.new(key_id: key_id, key_alias: key_alias)
213
+ if key_alias
214
+ Providers::Ssm.new(key_alias: key_alias)
215
+ elsif key_id
216
+ Providers::Ssm.new(key_id: key_id)
217
+ else
218
+ Providers::Ssm.new
219
+ end
214
220
  else
215
221
  raise ArgumentError, "Invalid provider: #{provider}"
216
222
  end
@@ -353,16 +359,16 @@ module SecretConfig
353
359
  # Ignore filtered values
354
360
  if (value != target[key].to_s) && (value != FILTERED)
355
361
  puts "#{Colors::KEY}#{key}:"
356
- puts "#{Colors::REMOVE}#{prefix_lines("- ", target[key])}"
357
- puts "#{Colors::ADD}#{prefix_lines("+ ", source[key])}#{Colors::CLEAR}\n\n"
362
+ puts "#{Colors::REMOVE}#{prefix_lines('- ', target[key])}"
363
+ puts "#{Colors::ADD}#{prefix_lines('+ ', source[key])}#{Colors::CLEAR}\n\n"
358
364
  end
359
365
  else
360
366
  puts "#{Colors::KEY}#{key}:"
361
- puts "#{Colors::REMOVE}#{prefix_lines("- ", target[key])}\n\n"
367
+ puts "#{Colors::REMOVE}#{prefix_lines('- ', target[key])}\n\n"
362
368
  end
363
369
  elsif source.key?(key)
364
370
  puts "#{Colors::KEY}#{key}:"
365
- puts "#{Colors::ADD}#{prefix_lines("+ ", source[key])}#{Colors::CLEAR}\n\n"
371
+ puts "#{Colors::ADD}#{prefix_lines('+ ', source[key])}#{Colors::CLEAR}\n\n"
366
372
  end
367
373
  end
368
374
  end
@@ -5,31 +5,45 @@ module SecretConfig
5
5
  def_delegator :registry, :refresh!
6
6
 
7
7
  def initialize(path, registry)
8
+ raise(ArgumentError, "path cannot be nil") if path.nil?
9
+
8
10
  @path = path
9
11
  @registry = registry
10
12
  end
11
13
 
12
14
  def fetch(sub_path, **options)
15
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
16
+
13
17
  registry.fetch(join_path(sub_path), **options)
14
18
  end
15
19
 
16
20
  def [](sub_path)
21
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
22
+
17
23
  registry[join_path(sub_path)]
18
24
  end
19
25
 
20
26
  def []=(sub_path, value)
27
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
28
+
21
29
  registry[join_path(sub_path)] = value
22
30
  end
23
31
 
24
32
  def key?(sub_path)
33
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
34
+
25
35
  registry.key?(join_path(sub_path))
26
36
  end
27
37
 
28
38
  def set(sub_path, value)
39
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
40
+
29
41
  registry.set(join_path(sub_path), value)
30
42
  end
31
43
 
32
44
  def delete(sub_path)
45
+ raise(ArgumentError, "sub_path cannot be nil") if sub_path.nil?
46
+
33
47
  registry.delete(join_path(sub_path))
34
48
  end
35
49
 
@@ -5,6 +5,9 @@ module SecretConfig
5
5
  class MissingMandatoryKey < Error
6
6
  end
7
7
 
8
+ class MissingEnvironmentVariable < Error
9
+ end
10
+
8
11
  class UndefinedRootError < Error
9
12
  end
10
13
 
@@ -15,7 +15,7 @@ module SecretConfig
15
15
  # Keys are returned with path names relative to the supplied path.
16
16
  def parse(key, value)
17
17
  relative_key = relative_key?(key) ? key : key.sub("#{path}/", "")
18
- value = interpolator.parse(value) if interpolator && value.is_a?(String) && value.include?("%{")
18
+ value = interpolator.parse(value) if interpolator && value.is_a?(String) && value.include?("${")
19
19
  tree[relative_key] = value
20
20
  end
21
21
 
@@ -38,11 +38,11 @@ module SecretConfig
38
38
  # - Imports cannot reference other imports at this time.
39
39
  def apply_imports
40
40
  tree.keys.each do |key|
41
- next unless (key =~ /\/__import__\Z/) || (key == "__import__")
41
+ next unless (key =~ %r{/__import__\Z}) || (key == "__import__")
42
42
 
43
43
  import_key = tree.delete(key)
44
- key, _ = ::File.split(key)
45
- key = nil if key == "."
44
+ key, = ::File.split(key)
45
+ key = nil if key == "."
46
46
 
47
47
  # binding.irb
48
48
 
@@ -51,7 +51,7 @@ module SecretConfig
51
51
 
52
52
  if relative_key?(import_key)
53
53
  tree.keys.each do |current_key|
54
- match = current_key.match(/\A#{import_key}\/(.*)/)
54
+ match = current_key.match(%r{\A#{import_key}/(.*)})
55
55
  next unless match
56
56
 
57
57
  imported_key = key.nil? ? match[1] : ::File.join(key, match[1])
@@ -71,6 +71,5 @@ module SecretConfig
71
71
  def relative_key?(key)
72
72
  !key.start_with?("/")
73
73
  end
74
-
75
74
  end
76
75
  end
@@ -23,9 +23,9 @@ module SecretConfig
23
23
  end
24
24
 
25
25
  # Returns the value or `nil` if not found
26
- def fetch(key)
26
+ def fetch(_key)
27
27
  values = fetch_path(path)
28
- value.is_a?(Hash) ? nil : value
28
+ values.is_a?(Hash) ? nil : values
29
29
  end
30
30
 
31
31
  private
@@ -98,7 +98,7 @@ module SecretConfig
98
98
  end
99
99
 
100
100
  # Remove keys deleted from the central registry.
101
- (existing_keys - updated_keys).each { |key| provider.delete(key) }
101
+ (existing_keys - updated_keys).each { |key| cache.delete(key) }
102
102
 
103
103
  true
104
104
  end
@@ -4,16 +4,22 @@ require "securerandom"
4
4
  # * SecretConfig Interpolations
5
5
  #
6
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.
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
+ # # Raises SecretConfig::MissingEnvironmentVariable when the env var is not defined.
13
+ # ${env:name,default} # Extract value from the named environment value.
14
+ # # Returns the supplied default value when the env var is not defined.
15
+ # ${hostname} # Full name of this host.
16
+ # ${hostname:short} # Short name of this host. Everything up to the first period.
17
+ # ${pid} # Process Id for this process.
18
+ # ${random} # URL safe Random 32 byte value.
19
+ # ${random:size} # URL safe Random value of `size` bytes.
20
+ # ${select:a,b,c,d} # Randomly select one of the supplied values. A new new value is selected on restart or refresh.
21
+ # # Values are separated by `,` and cannot include `,` in their values.
22
+ # # Values are stripped of leading and trailing spaces.
17
23
  module SecretConfig
18
24
  class SettingInterpolator < StringInterpolator
19
25
  def date(format = "%Y%m%d")
@@ -24,8 +30,12 @@ module SecretConfig
24
30
  Time.now.strftime(format)
25
31
  end
26
32
 
27
- def env(name)
28
- ENV[name]
33
+ def env(name, default = :no_default_supplied)
34
+ return ENV[name] if ENV.key?(name)
35
+
36
+ return default unless default == :no_default_supplied
37
+
38
+ raise(MissingEnvironmentVariable, "Missing mandatory environment variable: #{name}")
29
39
  end
30
40
 
31
41
  def hostname(format = nil)
@@ -41,5 +51,14 @@ module SecretConfig
41
51
  def random(size = 32)
42
52
  SecureRandom.urlsafe_base64(size)
43
53
  end
54
+
55
+ # Empty values return nil which removes the key entirely from the config
56
+ def select(*values)
57
+ if values.size < 2
58
+ raise(ConfigurationError, "Must supply at least 2 options when using select: #{values.inspect}")
59
+ end
60
+
61
+ values[SecureRandom.random_number(values.count)]
62
+ end
44
63
  end
45
64
  end
@@ -1,4 +1,4 @@
1
- # Parse strings containing %{key:value1,value2,value3}
1
+ # Parse strings containing ${key:value1,value2,value3}
2
2
  # Where `key` is a method implemented by a class inheriting from this class
3
3
  #
4
4
  # The following `key`s are reserved:
@@ -6,20 +6,20 @@
6
6
  # * initialize
7
7
  #
8
8
  # Notes:
9
- # * To prevent interpolation use %%{...}
10
- # * %% is not touched, only %{...} is identified.
9
+ # * To prevent interpolation use $${...}
10
+ # * $$ is not touched, only ${...} is identified.
11
11
  module SecretConfig
12
12
  class StringInterpolator
13
13
  def initialize(pattern = nil)
14
- @pattern = pattern || /%{1,2}\{([^}]+)\}/
14
+ @pattern = pattern || /\${1,2}\{([^}]+)\}/
15
15
  end
16
16
 
17
17
  def parse(string)
18
- string.gsub(/%{1,2}\{([^}]+)\}/) do |match|
19
- if match.start_with?("%%")
18
+ string.gsub(/\${1,2}\{([^}]+)\}/) do |match|
19
+ if match.start_with?("$$")
20
20
  match[1..-1]
21
21
  else
22
- expr = Regexp.last_match(1) || Regexp.last_match(2) || match.tr("%{}", "")
22
+ expr = Regexp.last_match(1) || Regexp.last_match(2) || match.tr("${}", "")
23
23
  key, args_str = expr.split(":")
24
24
  key = key.strip.to_sym
25
25
  arguments = args_str&.split(",")&.map { |v| v.strip == "" ? nil : v.strip } || []
@@ -7,7 +7,7 @@ module SecretConfig
7
7
  if key == NODE_KEY
8
8
  yield(path, value)
9
9
  else
10
- name = path.nil? ? key : "#{path}/#{key}"
10
+ name = path.nil? ? key : File.join(path, key)
11
11
  value.is_a?(Hash) ? flatten_each(value, name, &block) : yield(name, value)
12
12
  end
13
13
  end
@@ -1,3 +1,3 @@
1
1
  module SecretConfig
2
- VERSION = "0.9.1".freeze
2
+ VERSION = "0.10.4".freeze
3
3
  end
@@ -45,7 +45,7 @@ test:
45
45
  mongo:
46
46
  database: secret_config_test
47
47
  primary: 127.0.0.1:27017
48
- secondary: "%{hostname}:27018"
48
+ secondary: "${hostname}:27018"
49
49
 
50
50
  secrets:
51
51
  secret_key_base: somereallylongteststring
@@ -59,15 +59,15 @@ test:
59
59
  key: key0
60
60
 
61
61
  mysql:
62
- # database: "%{fetch: /test/my_application/mysql/database }"
62
+ # database: "${fetch: /test/my_application/mysql/database }"
63
63
  username: other
64
64
  password: otherrules
65
- host: "%{hostname}"
65
+ host: "${hostname}"
66
66
 
67
67
  mongo:
68
68
  database: secret_config_test
69
69
  primary: localhost:27017
70
- secondary: "%{hostname}:27018"
70
+ secondary: "${hostname}:27018"
71
71
 
72
72
  mongo2:
73
73
  __import__: mongo
data/test/parser_test.rb CHANGED
@@ -26,7 +26,7 @@ class ParserTest < Minitest::Test
26
26
  #
27
27
  # Retrieve values elsewhere in the registry.
28
28
  # Paths can be relative to the current root, or absolute paths outside the current root.
29
- # %{fetch:key} # Fetches a single value from a relative or absolute path
29
+ # ${fetch:key} # Fetches a single value from a relative or absolute path
30
30
  # Return the value of the supplied key.
31
31
  #
32
32
  # With a relative key, look for the value in the current registry.
@@ -47,7 +47,7 @@ class ParserTest < Minitest::Test
47
47
  # end
48
48
  # end
49
49
 
50
- # %{import:path} # Imports a path of keys and values into the current path
50
+ # ${import:path} # Imports a path of keys and values into the current path
51
51
  # Replace the current value with a tree of values with the supplied path.
52
52
  #
53
53
  describe "#import" do
@@ -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" => "%{hostname}: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",
@@ -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" => "%{hostname}: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",
@@ -132,12 +132,12 @@ class RegistryTest < Minitest::Test
132
132
 
133
133
  it "of integers" do
134
134
  value = registry.fetch("mysql/ports", type: :integer, separator: ",")
135
- assert_equal([12345, 5343, 26815], value)
135
+ assert_equal([12_345, 5343, 26_815], value)
136
136
  end
137
137
 
138
138
  it "of integers with spaces" do
139
139
  value = registry.fetch("mysql/ports2", type: :integer, separator: ",")
140
- assert_equal([12345, 5343, 26815], value)
140
+ assert_equal([12_345, 5343, 26_815], value)
141
141
  end
142
142
 
143
143
  it "accepts a default without requiring conversion" do
@@ -96,6 +96,5 @@ class SecretConfigTest < Minitest::Test
96
96
  assert_equal true, database
97
97
  end
98
98
  end
99
-
100
99
  end
101
100
  end
@@ -6,23 +6,23 @@ module SecretConfig
6
6
 
7
7
  describe "#parse" do
8
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"))
9
+ string = "Set a date of ${date} here."
10
+ expected = string.gsub("${date}", Date.today.strftime("%Y%m%d"))
11
11
  actual = interpolator.parse(string)
12
12
  assert_equal expected, actual, string
13
13
  end
14
14
 
15
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)
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
20
  actual = interpolator.parse(string)
21
21
  assert_equal expected, actual, string
22
22
  end
23
23
 
24
24
  it "handles bad key" do
25
- string = "Set a date of %{blah} here."
25
+ string = "Set a date of ${blah} here."
26
26
  assert_raises InvalidInterpolation do
27
27
  interpolator.parse(string)
28
28
  end
@@ -31,22 +31,22 @@ module SecretConfig
31
31
 
32
32
  describe "#date" do
33
33
  it "interpolates date only" do
34
- string = "%{date}"
34
+ string = "${date}"
35
35
  expected = Date.today.strftime("%Y%m%d")
36
36
  actual = interpolator.parse(string)
37
37
  assert_equal expected, actual, string
38
38
  end
39
39
 
40
40
  it "interpolates date" do
41
- string = "Set a date of %{date} here."
42
- expected = string.gsub("%{date}", Date.today.strftime("%Y%m%d"))
41
+ string = "Set a date of ${date} here."
42
+ expected = string.gsub("${date}", Date.today.strftime("%Y%m%d"))
43
43
  actual = interpolator.parse(string)
44
44
  assert_equal expected, actual, string
45
45
  end
46
46
 
47
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"))
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
50
  actual = interpolator.parse(string)
51
51
  assert_equal expected, actual, string
52
52
  end
@@ -54,7 +54,7 @@ module SecretConfig
54
54
 
55
55
  describe "#time" do
56
56
  it "interpolates time only" do
57
- string = "%{time}"
57
+ string = "${time}"
58
58
  time = Time.now
59
59
  Time.stub(:now, time) do
60
60
  expected = Time.now.strftime("%Y%m%d%H%M%S%L")
@@ -64,18 +64,18 @@ module SecretConfig
64
64
  end
65
65
 
66
66
  it "interpolates time" do
67
- string = "Set a time of %{time} here."
67
+ string = "Set a time of ${time} here."
68
68
  time = Time.now
69
69
  Time.stub(:now, time) do
70
- expected = string.gsub("%{time}", Time.now.strftime("%Y%m%d%H%M%S%L"))
70
+ expected = string.gsub("${time}", Time.now.strftime("%Y%m%d%H%M%S%L"))
71
71
  actual = interpolator.parse(string)
72
72
  assert_equal expected, actual, string
73
73
  end
74
74
  end
75
75
 
76
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"))
77
+ string = "Set a custom time of ${time:%H%M} here."
78
+ expected = string.gsub("${time:%H%M}", Time.now.strftime("%H%M"))
79
79
  actual = interpolator.parse(string)
80
80
  assert_equal expected, actual, string
81
81
  end
@@ -87,34 +87,41 @@ module SecretConfig
87
87
  end
88
88
 
89
89
  it "fetches existing ENV var" do
90
- string = "%{env:TEST_SETTING}"
90
+ string = "${env:TEST_SETTING}"
91
91
  actual = interpolator.parse(string)
92
92
  assert_equal "Secret", actual, string
93
93
  end
94
94
 
95
95
  it "fetches existing ENV var into a larger string" do
96
- string = "Hello %{env:TEST_SETTING}. How are you?"
96
+ string = "Hello ${env:TEST_SETTING}. How are you?"
97
97
  actual = interpolator.parse(string)
98
- expected = string.gsub("%{env:TEST_SETTING}", "Secret")
98
+ expected = string.gsub("${env:TEST_SETTING}", "Secret")
99
99
  assert_equal expected, actual, string
100
100
  end
101
101
 
102
102
  it "handles missing ENV var" do
103
- string = "%{env:OTHER_TEST_SETTING}"
103
+ string = "${env:OTHER_TEST_SETTING}"
104
+ assert_raises SecretConfig::MissingEnvironmentVariable do
105
+ interpolator.parse(string)
106
+ end
107
+ end
108
+
109
+ it "uses default value for missing ENV var" do
110
+ string = "${env:OTHER_TEST_SETTING,My default value}"
104
111
  actual = interpolator.parse(string)
105
- assert_equal "", actual, string
112
+ assert_equal "My default value", actual, string
106
113
  end
107
114
  end
108
115
 
109
116
  describe "#hostname" do
110
117
  it "returns hostname" do
111
- string = "%{hostname}"
118
+ string = "${hostname}"
112
119
  actual = interpolator.parse(string)
113
120
  assert_equal Socket.gethostname, actual, string
114
121
  end
115
122
 
116
123
  it "returns short hostname" do
117
- string = "%{hostname:short}"
124
+ string = "${hostname:short}"
118
125
  actual = interpolator.parse(string)
119
126
  assert_equal Socket.gethostname.split(".")[0], actual, string
120
127
  end
@@ -122,7 +129,7 @@ module SecretConfig
122
129
 
123
130
  describe "#pid" do
124
131
  it "returns process id" do
125
- string = "%{pid}"
132
+ string = "${pid}"
126
133
  actual = interpolator.parse(string)
127
134
  assert_equal $$.to_s, actual, string
128
135
  end
@@ -130,7 +137,7 @@ module SecretConfig
130
137
 
131
138
  describe "#random" do
132
139
  it "interpolates random 32 byte string" do
133
- string = "%{random}"
140
+ string = "${random}"
134
141
  random = SecureRandom.urlsafe_base64(32)
135
142
  SecureRandom.stub(:urlsafe_base64, random) do
136
143
  actual = interpolator.parse(string)
@@ -139,7 +146,7 @@ module SecretConfig
139
146
  end
140
147
 
141
148
  it "interpolates custom length random string" do
142
- string = "%{random:64}"
149
+ string = "${random:64}"
143
150
  random = SecureRandom.urlsafe_base64(64)
144
151
  SecureRandom.stub(:urlsafe_base64, random) do
145
152
  actual = interpolator.parse(string)
@@ -147,6 +154,28 @@ module SecretConfig
147
154
  end
148
155
  end
149
156
  end
157
+
158
+ describe "#select" do
159
+ it "randomly selects one of the supplied values" do
160
+ string = "${select:one, two,three}"
161
+ actual = interpolator.parse(string)
162
+ assert %w[one two three].include?(actual), actual
163
+ end
164
+
165
+ it "fails when less than 2 options are supplied" do
166
+ string = "${select:one}"
167
+ assert_raises ConfigurationError do
168
+ interpolator.parse(string)
169
+ end
170
+ end
171
+
172
+ it "fails when no options are supplied" do
173
+ string = "${select}"
174
+ assert_raises ConfigurationError do
175
+ interpolator.parse(string)
176
+ end
177
+ end
178
+ end
150
179
  end
151
180
  end
152
181
  end