secret_config 0.7.1 → 0.8.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: 4774ef1279f56c5ce70a89de9fdada0c661b4085afef81c6296aa56800da92fa
4
- data.tar.gz: 8f16b3bf4a7193315a5c761f3ce39283a23851660c2aa61752b8d958b1798e20
3
+ metadata.gz: 8df953d1a074c81118be3cc9458c9790b81fb72fbda9b075ae2a925393be657d
4
+ data.tar.gz: e1605d3af8e236b458f25e3d5e6b6bd801c9be1e7767d1925d851fc778478a98
5
5
  SHA512:
6
- metadata.gz: 0afe6dab591c52af29ecb7a95317c8da193e8220b0a89da8f4dbd8f641c9aa8848adf033aa32ffc15f7974c54ec245791914526b07f3a589dec1e562caa91e3b
7
- data.tar.gz: feb776daefb9795da0ffabcec4cc3da4a6030bcc3d6043525eba24823e0d6051c072ebfb0ee019466eeadef03e491afdd5ed88a0ac552575a78351b1c456d5b9
6
+ metadata.gz: 0d2527c9397af668b648e1cb4f49994db83bfc9ec0619435392f66b59556e5a67ffa2a1cbbff102d23dc5e23000f5cdd3e2344dd385e876b72951ac25fe70b52
7
+ data.tar.gz: c0a9f535b97e9e2cd2a1ea429b1464365a24d85ac269213548eda15318916490f7d59a2e5fb8eefc33b88a774c1e01ddff4ca6f4112451d61a09f440b9161cdb
data/README.md CHANGED
@@ -20,9 +20,10 @@ Supports storing configuration information in:
20
20
  * Environment Variables
21
21
  * Environment Variables take precedence and can be used to override any setting.
22
22
  * AWS System Manager Parameter Store
23
- * Encrypt and securely store secrets such as passwords centrally.
23
+ * Encrypt and securely store secrets such as passwords centrally.
24
24
 
25
- Supported data types:
25
+ Since all values are stored as strings in the central directory or config file, the following type conversions
26
+ are supported:
26
27
  * integer
27
28
  * float
28
29
  * string
@@ -33,6 +34,8 @@ Supported data types:
33
34
  Supported conversions:
34
35
  * base64
35
36
 
37
+ Arrays are also supported when the value contains a known separator by which to break down the values.
38
+
36
39
  ## Benefits
37
40
 
38
41
  Benefits of moving sensitive configuration information into AWS System Manager Parameter Store:
@@ -52,10 +55,10 @@ Benefits of moving sensitive configuration information into AWS System Manager P
52
55
  * Configure multiple distinct application instances to support multiple tenants.
53
56
  * For example, use separate databases with unique credentials for each tenant.
54
57
  * Separation of responsibilities is achieved since operations can manage production configuration.
55
- * Developers do not need to be involved with production configuration such as host names and passwords.
56
- * All values are encrypted by default when stored in the AWS Parameter Store.
58
+ * Developers do not need to be involved with production configuration such as host names and passwords.
59
+ * All values are encrypted by default when stored in the AWS Parameter Store.
57
60
  * Prevents accidentally not encrypting sensitive data.
58
-
61
+
59
62
  ## Introduction
60
63
 
61
64
  When Secret Config starts up it reads all configuration entries into memory for all keys under the configured path.
@@ -67,7 +70,7 @@ via a process signal, or by calling it through an event, or via a messaging syst
67
70
  It is suggested that any programmatic lookup to values stored in Secret Config are called every time a value is
68
71
  being used, rather than creating a local copy of the value. This ensures that a refresh of the registry will take effect
69
72
  immediately for any code reading from Secret Config.
70
-
73
+
71
74
  ## API
72
75
 
73
76
  When Secret Config starts up it reads all configuration entries immediately for all keys under the configured path.
@@ -83,7 +86,7 @@ Fetch the value for the supplied key, returning nil if not found:
83
86
  # Key is present:
84
87
  SecretConfig["logger/level"]
85
88
  # => "info"
86
-
89
+
87
90
  # Key is missing:
88
91
  SecretConfig["logger/blah"]
89
92
  # => nil
@@ -95,7 +98,7 @@ Fetch the value for the supplied key, raising `SecretConfig::MissingMandatoryKey
95
98
  # Key is present:
96
99
  SecretConfig.fetch("logger/level")
97
100
  # => "info"
98
-
101
+
99
102
  # Key is missing:
100
103
  SecretConfig.fetch("logger/blah")
101
104
  # => SecretConfig::MissingMandatoryKey (Missing configuration value for /development/logger/blah)
@@ -108,7 +111,7 @@ SecretConfig.fetch("logger/level", default: "info")
108
111
  # => "info"
109
112
  ~~~
110
113
 
111
- Since AWS SSM Parameter store and environment variables only support string values,
114
+ Since AWS SSM Parameter store and environment variables only support string values,
112
115
  it is neccessary to convert the string back to the type required by the program.
113
116
 
114
117
  The following types are supported:
@@ -129,6 +132,20 @@ SecretConfig.fetch("symmetric_encryption/version", type: :integer)
129
132
  # => 0
130
133
  ~~~
131
134
 
135
+ Sometimes it is useful to store arrays of values as a single key.
136
+
137
+ ~~~ruby
138
+ # Example: A list of host names could be stored as: "primary.example.net,secondary.example.net,backup.example.net"
139
+ # To extract it as an array of strings:
140
+ SecretConfig.fetch("address_services/hostnames", separator: ",")
141
+ # => ["primary.example.net", "secondary.example.net", "backup.example.net"]
142
+
143
+ # Example: A list of ports could be stored as: "12345,5343,26815"
144
+ # To extract it as an array of Integers:
145
+ SecretConfig.fetch("address_services/ports", type: :integer, separator: ",")
146
+ # => [12345, 5343, 26815]
147
+ ~~~
148
+
132
149
  When storing binary data, it should be encoded with strict base64 encoding. To automatically convert it back to binary
133
150
  specify the encoding as `:base64`
134
151
 
@@ -136,7 +153,7 @@ specify the encoding as `:base64`
136
153
  # Return a value that was stored in Base64 encoding format:
137
154
  SecretConfig.fetch("symmetric_encryption/iv")
138
155
  # => "FW+/wLubAYM+ZU0bWQj59Q=="
139
-
156
+
140
157
  # Base64 decode a value that was stored in Base64 encoding format:
141
158
  SecretConfig.fetch("symmetric_encryption/iv", encoding: :base64)
142
159
  # => "\x15o\xBF\xC0\xBB\x9B\x01\x83>eM\eY\b\xF9\xF5"
@@ -180,7 +197,7 @@ SecretConfig.refresh!
180
197
  ~~~
181
198
 
182
199
  Example, refresh the registry any time a SIGUSR2 is raised, add the following code on startup:
183
-
200
+
184
201
  ~~~ruby
185
202
  Signal.trap('USR2') do
186
203
  SecretConfig.refresh!
@@ -197,7 +214,7 @@ Where `1234` above is the process PID.
197
214
  ## Development and Test use
198
215
 
199
216
  In the development environment create the file `config/application.yml` within which to store local development credentials.
200
- Depending on your team setup you may want to use the same file for all developers so can check it into you change control system.
217
+ Depending on your team setup you may want to use the same file for all developers so can check it into you change control system.
201
218
 
202
219
  For example: `config/application.yml`
203
220
 
@@ -337,7 +354,7 @@ Then the application that uses the above library / gem just needs to add the rel
337
354
 
338
355
  ~~~yaml
339
356
  http_client:
340
- url: https://test.example.com
357
+ url: https://test.example.com
341
358
  pool_size: 20
342
359
  read_timeout: 300
343
360
  ~~~
@@ -346,7 +363,7 @@ This avoids a custom config file just for the above library.
346
363
 
347
364
  Additionally the values can be overridden with environment variables at any time:
348
365
 
349
- export HTTP_CLIENT_URL=https://production.example.com
366
+ export HTTP_CLIENT_URL=https://production.example.com
350
367
 
351
368
  ## Configuration
352
369
 
@@ -357,15 +374,15 @@ Add the following line to Gemfile
357
374
  Out of the box Secret Config will look in the local file system for the file `config/application.yml`
358
375
  as covered above. By default it will use env var `RAILS_ENV` to define the path to look under for settings.
359
376
 
360
- The default settings are great for getting started in development and test, but should not be used in production.
377
+ The default settings are great for getting started in development and test, but should not be used in production.
361
378
 
362
379
  To ensure Secret Config is configured and available for use within any of the config files, add
363
- the following lines to the very top of `application.rb` under the line `class Application < Rails::Application`:
380
+ the following lines to the very top of `application.rb` under the line `class Application < Rails::Application`:
364
381
 
365
382
  ~~~ruby
366
383
  module MyApp
367
384
  class Application < Rails::Application
368
-
385
+
369
386
  # Add the following lines to configure Secret Config:
370
387
  if Rails.env.development? || Rails.env.test?
371
388
  # Use 'config/application.yml'
@@ -373,8 +390,8 @@ module MyApp
373
390
  else
374
391
  # Read configuration from AWS SSM Parameter Store
375
392
  config.secret_config.use :ssm, path: "/#{Rails.env}/my_app"
376
- end
377
-
393
+ end
394
+
378
395
  # ....
379
396
  end
380
397
  end
@@ -388,7 +405,7 @@ By placing the secret config configuration as the very first configuration item,
388
405
  configuration item to access the centralized configuration in AWS System Manager Parameter Store.
389
406
 
390
407
  The environment variable `SECRET_CONFIG_PROVIDER` can be used to override the provider when needed.
391
- For example:
408
+ For example:
392
409
  `export SECRET_CONFIG_PROVIDER=ssm`
393
410
  Or,
394
411
  `export SECRET_CONFIG_PROVIDER=file`
@@ -398,31 +415,31 @@ multiple paths. For example:
398
415
 
399
416
  /production1/my_application
400
417
  /production2/my_application
401
-
418
+
402
419
  /production/instance1/my_application
403
420
  /production/instance2/my_application
404
-
421
+
405
422
  The `path` is completely flexible, but must be unique for every AWS account under which the application will run.
406
423
  The same `path` can be used in different AWS accounts though. It is also not replicated across regions.
407
424
 
408
- When writing settings to the parameter store, it is recommended to use a custom KMS key to encrypt the values.
409
- To supply the key to encrypt the values with, add the `key_id` parameter:
425
+ When writing settings to the parameter store, it is recommended to use a custom KMS key to encrypt the values, if you don't specify a key ID, the system uses the default key associated with your AWS account `alias/aws/ssm`.
426
+ To supply the key to encrypt the values with, add the `key_id` parameter:
410
427
 
411
428
  ~~~ruby
412
429
  module MyApp
413
430
  class Application < Rails::Application
414
-
431
+
415
432
  # Add the following lines to configure Secret Config:
416
433
  if Rails.env.development? || Rails.env.test?
417
434
  # Use 'config/application.yml'
418
435
  config.secret_config.use :file
419
436
  else
420
437
  # Read configuration from AWS SSM Parameter Store
421
- config.secret_config.use :ssm,
438
+ config.secret_config.use :ssm,
422
439
  path: "/#{Rails.env}/my_app",
423
440
  key_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
424
- end
425
-
441
+ end
442
+
426
443
  # ....
427
444
  end
428
445
  end
@@ -430,8 +447,33 @@ end
430
447
 
431
448
  Note: The relevant KMS key must be created first prior to using it here.
432
449
 
433
- The `key_id` is only used when writing settings to the AWS Parameter store and can be left off when that instance
434
- will only read from the parameter store.
450
+ `ssm` provider supports various configuration parameters that can be provided as keyword arguments for `config.secret_config.use :ssm, path, **args`
451
+
452
+ Args hash:
453
+ * **:key_id** (String) - The `key_id` is only used when writing settings to the AWS Parameter store and can be left off when that instance will only read from the parameter store. Can be configred with environment variable `SECRET_CONFIG_KEY_ID`.
454
+ * **:retry_count** (Integer, default=10) - Max number of retries in case of execution failure.
455
+ * **:retry_max_ms** (Integer, default=3_000) - Interval in ms between retries, `sleep` is used to facilitate throttling.
456
+ * any options suported by [Aws::SSM::Client](https://docs.aws.amazon.com/sdkforruby/api/Aws/SSM/Client.html#initialize-instance_method) e.g. **:credentials**:
457
+ ~~~ruby
458
+ config.secret_config.use :ssm,
459
+ path: "/#{Rails.env}/my_app",
460
+ key_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
461
+ credentials: Aws::AssumeRoleCredentials.new(
462
+ role_arn: "arn:aws:iam::111111122222222:role/assume_role_name",
463
+ role_session_name: "session-name-to-identify-#{SecureRandom.uuid}"
464
+ ))
465
+ ~~~
466
+
467
+ ### Secret Config Environment variables
468
+
469
+ Priority describes when environment variable is used as a default value, preceds configuration value or overrides.
470
+
471
+ Name | Desctiption | Priority
472
+ ------------------------- | --------------------------------------------------------------- | --------
473
+ `SECRET_CONFIG_PATH` | path from which the configuration data will be read | precede
474
+ `SECRET_CONFIG_PROVIDER` | override the provider configured for `config.secret_config.use` | override
475
+ `SECRET_CONFIG_KEY_ID` | encryption `key_id` | default
476
+ `SECRET_CONFIG_ACCOUNT_ID`| used in `rspec` to configure AWS Account Id for role assuming | required
435
477
 
436
478
  ### Shared configuration for development and test
437
479
 
@@ -441,7 +483,7 @@ configuration file `application.yml` can be shared. Update the lines above to:
441
483
  ~~~ruby
442
484
  module MyApp
443
485
  class Application < Rails::Application
444
-
486
+
445
487
  # Add the following lines:
446
488
  if Rails.env.development? || Rails.env.test?
447
489
  # Use 'config/application.yml'
@@ -449,8 +491,8 @@ module MyApp
449
491
  else
450
492
  # Read configuration from AWS SSM Parameter Store
451
493
  config.secret_config.use :ssm, path: "/#{Rails.env}/my_app"
452
- end
453
-
494
+ end
495
+
454
496
  # ....
455
497
  end
456
498
  end
@@ -503,7 +545,7 @@ development:
503
545
  ~~~
504
546
 
505
547
  Available interpolations:
506
-
548
+
507
549
  * %{date}
508
550
  * Current date in the format of "%Y%m%d" (CCYYMMDD)
509
551
  * %{date:format}
@@ -603,7 +645,7 @@ Key:
603
645
 
604
646
  #### Export SSM parameters
605
647
 
606
- In AWS SSM Parameter store it can be difficult to
648
+ In AWS SSM Parameter store it can be difficult to
607
649
  Export the values from a specific path into a yaml or json file so that they are easier to read.
608
650
 
609
651
  Export from a path in AWS SSM Parameter Store to a yaml file, where passwords are filtered:
@@ -632,10 +674,10 @@ Copy configuration from one path in AWS SSM Parameter Store to another path in A
632
674
 
633
675
  In the multi-tenant example above, we may want to generate a secure random password for each tenant.
634
676
  In the source file or registry, set the value to `$random`, this will ensure that during the `import` or `copy`
635
- that the destination will receive a secure random value.
677
+ that the destination will receive a secure random value.
636
678
 
637
- By default the length of the randomized value is 32 bytes, use `--random_size` to adjust the length of
638
- the randomized string.
679
+ By default the length of the randomized value is 32 bytes, use `--random_size` to adjust the length of
680
+ the randomized string.
639
681
 
640
682
  ## Docker
641
683
 
@@ -646,7 +688,7 @@ any changes. The only difference being the path that container uses to read its
646
688
  Another important benefit is that the docker image does not contain any production or test credentials since
647
689
  these are all stored in AWS SSM Parameter Store.
648
690
 
649
- When a Ruby / Rails application is using Secret Config for its configuration settings, it only requires the
691
+ When a Ruby / Rails application is using Secret Config for its configuration settings, it only requires the
650
692
  following environment variables when starting up the container in for example AWS ECS or AWS Fargate:
651
693
 
652
694
  ~~~shell
@@ -696,14 +738,14 @@ end
696
738
  Specifically for docker containers it is necessary to turn off file logging and turn on logging to standard out
697
739
  so that AWS Cloud Watch can pick up the log data.
698
740
 
699
- To start with `logger/destination` of `stdout` will work with regular non-colorized output. When feeding the
741
+ To start with `logger/destination` of `stdout` will work with regular non-colorized output. When feeding the
700
742
  log output into something that can process JSON, set `logger/formatter` to `json`.
701
743
 
702
744
  The benefit with the above approach is that a developer can pull the exact same container image that is running
703
745
  in production and configure it to run locally on their laptop. For example, set `logger/destination` to `file`.
704
746
 
705
747
  The above code can be modified as necessary to add any Semantic Logger appender to write directly to external
706
- centralized logging systems, instead of writing to standard out or local files.
748
+ centralized logging systems, instead of writing to standard out or local files.
707
749
 
708
750
  ### Email Server and Assets
709
751
 
@@ -748,7 +790,7 @@ end
748
790
  Using this approach the file `config/symmetric-encryption.yml` can be removed once the keys have been moved to
749
791
  the registry.
750
792
 
751
- To extract existing keys from the config file so that they can be imported into the registry,
793
+ To extract existing keys from the config file so that they can be imported into the registry,
752
794
  run the code below inside a console in each of the respective environments.
753
795
 
754
796
  ~~~ruby
data/Rakefile CHANGED
@@ -1,21 +1,21 @@
1
- require 'rake/testtask'
2
- require_relative 'lib/secret_config/version'
1
+ require "rake/testtask"
2
+ require_relative "lib/secret_config/version"
3
3
 
4
4
  task :gem do
5
- system 'gem build secret_config.gemspec'
5
+ system "gem build secret_config.gemspec"
6
6
  end
7
7
 
8
- task :publish => :gem do
8
+ task publish: :gem do
9
9
  system "git tag -a v#{SecretConfig::VERSION} -m 'Tagging #{SecretConfig::VERSION}'"
10
- system 'git push --tags'
10
+ system "git push --tags"
11
11
  system "gem push secret_config-#{SecretConfig::VERSION}.gem"
12
12
  system "rm secret_config-#{SecretConfig::VERSION}.gem"
13
13
  end
14
14
 
15
15
  Rake::TestTask.new(:test) do |t|
16
- t.pattern = 'test/**/*_test.rb'
16
+ t.pattern = "test/**/*_test.rb"
17
17
  t.verbose = true
18
18
  t.warning = true
19
19
  end
20
20
 
21
- task :default => :test
21
+ task default: :test
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'secret_config'
3
+ require "secret_config"
4
4
 
5
5
  SecretConfig::CLI.run!(ARGV)
@@ -18,6 +18,8 @@ module SecretConfig
18
18
  end
19
19
 
20
20
  autoload :CLI, "secret_config/cli"
21
+ autoload :Config, "secret_config/config"
22
+ autoload :Parser, "secret_config/parser"
21
23
  autoload :SettingInterpolator, "secret_config/setting_interpolator"
22
24
  autoload :StringInterpolator, "secret_config/string_interpolator"
23
25
  autoload :Utils, "secret_config/utils"
@@ -30,7 +32,6 @@ module SecretConfig
30
32
  def_delegator :registry, :[]
31
33
  def_delegator :registry, :[]=
32
34
  def_delegator :registry, :key?
33
- def_delegator :registry, :fetch
34
35
  def_delegator :registry, :set
35
36
  def_delegator :registry, :delete
36
37
  def_delegator :registry, :refresh!
@@ -42,6 +43,34 @@ module SecretConfig
42
43
  @registry = SecretConfig::Registry.new(path: path, provider: provider, provider_args: args)
43
44
  end
44
45
 
46
+ # Fetch configuration in a block by supplying the root path once.
47
+ #
48
+ # Example:
49
+ # SecretConfig.configure("suppliers/kafka_service") do |config|
50
+ # Kafka::Client.new(
51
+ # seed_brokers: config.fetch("brokers", separator: ","),
52
+ # delivery_interval: config.fetch("delivery_interval", type: :integer, default: 0),
53
+ # delivery_threshold: config.fetch("delivery_threshold", type: :integer, default: 0),
54
+ # max_queue_size: config.fetch("max_queue_size", type: :integer, default: 10_000),
55
+ # max_retries: config.fetch("max_retries", type: :integer, default: -1),
56
+ # retry_backoffs: config.fetch("retry_backoff", type: :integer, default: 0),
57
+ # )
58
+ # end
59
+ #
60
+ # If `SecretConfig.configure` was not used it would have looked like:
61
+ # Kafka::Client.new(
62
+ # seed_brokers: SecretConfig.fetch("suppliers/kafka_service/brokers", separator: ","),
63
+ # delivery_interval: SecretConfig.fetch("suppliers/kafka_service/delivery_interval", type: :integer, default: 0),
64
+ # delivery_threshold: SecretConfig.fetch("suppliers/kafka_service/delivery_threshold", type: :integer, default: 0),
65
+ # max_queue_size: SecretConfig.fetch("suppliers/kafka_service/max_queue_size", type: :integer, default: 10_000),
66
+ # max_retries: SecretConfig.fetch("suppliers/kafka_service/max_retries", type: :integer, default: -1),
67
+ # retry_backoffs: SecretConfig.fetch("suppliers/kafka_service/retry_backoff", type: :integer, default: 0),
68
+ # )
69
+ def self.configure(path)
70
+ config = Config.new(path, registry)
71
+ yield(config)
72
+ end
73
+
45
74
  # Returns the global registry.
46
75
  # Unless `.use` was called above, it will default to a file provider.
47
76
  def self.registry
@@ -0,0 +1,44 @@
1
+ module SecretConfig
2
+ class Config
3
+ extend Forwardable
4
+ def_delegator :registry, :configuration
5
+ def_delegator :registry, :refresh!
6
+
7
+ def initialize(path, registry)
8
+ @path = path
9
+ @registry = registry
10
+ end
11
+
12
+ def fetch(sub_path, **options)
13
+ registry.fetch(join_path(sub_path), **options)
14
+ end
15
+
16
+ def [](sub_path)
17
+ registry[join_path(sub_path)]
18
+ end
19
+
20
+ def []=(sub_path, value)
21
+ registry[join_path(sub_path)] = value
22
+ end
23
+
24
+ def key?(sub_path)
25
+ registry.key?(join_path(sub_path))
26
+ end
27
+
28
+ def set(sub_path, value)
29
+ registry.set(join_path(sub_path), value)
30
+ end
31
+
32
+ def delete(sub_path)
33
+ registry.delete(join_path(sub_path))
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :path, :registry
39
+
40
+ def join_path(sub_path)
41
+ File.join(path, sub_path)
42
+ end
43
+ end
44
+ end