ravioli 0.1.3 → 0.1.7

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: 572198c31192d87b63d13d1e672813f7c9e37c9cc3a9f9e17622b5f86c882b34
4
- data.tar.gz: 626a0d7ec6f697f00fabc011884fefec7fed2c0a55141638f3096a657fcb0a82
3
+ metadata.gz: a42c50f1ae58ea21c70956c3a2fda57084dc20042efc3e163c26b80a8ecc8c58
4
+ data.tar.gz: 2fafc358079f1bab16f25e36563dc5f843fedd7d85e32b8e34ca337b2c3c23ee
5
5
  SHA512:
6
- metadata.gz: cfb0f8416dd26cb6150a6d0c3cb93a908fd75d7571ccbccc138f5141ff5b888d75a90cfe9dd3438748519f6078ba300875600aa0b3f7cb980ab426c3e2223330
7
- data.tar.gz: e1316a4be7b03f9825f7324f70798c2c41ce937b04da209fc2df59a3fa4d1730efb94de12f74b53636ef6069faa27564bc9388951b7ed29f26b1ab73ed4f1400
6
+ metadata.gz: bf02967cc19dfba1c24a0db7da932999896c278635a8be0c45d6966d4d9e76d2517290770a8d608842b4978beb9c05621b532023ffd2bbd8eed484155c3327b8
7
+ data.tar.gz: 77bdfbaeb3210f7033e9837d7c52003118fc45edaec020595276f455260cb13a186c1c1239c3bf96e5e4965acb3c3f1b250fc0766e1f5d6957c89d662c633d5e
data/README.md CHANGED
@@ -216,7 +216,7 @@ Ravioli will then check for [encrypted credentials](https://guides.rubyonrails.o
216
216
  2. Then, it loads and applies `config/credentials/RAILS_ENV.yml.enc` over top of what it has already loaded
217
217
  3. Finally, IF `Rails.config.staging?` IS TRUE, it loads and applies `config/credentials/staging.yml.enc`
218
218
 
219
- This allows you to use your secure credentials stores without duplicating information; you can simply layer environment-specific values over top of
219
+ This allows you to use your secure credentials stores without duplicating information; you can simply layer environment-specific values over top of a "root" `config/credentials.yml.enc` file.
220
220
 
221
221
  ### All put together, it does this:
222
222
 
@@ -417,47 +417,47 @@ staging:
417
417
 
418
418
  ### Encryption keys in ENV
419
419
 
420
- Because Ravioli merges environment-specific credentials over top of the root credentials file, you'll need to provide encryption keys for two (or, if you have a staging setup, three) different files in ENV vars. As such, Ravioli looks for decryption keys in a fallback-specific way. Here's where it looks for each file:
420
+ Here are a few facts about credentials in Rails and how they're deployed:
421
421
 
422
- <table><thead><tr><th>File</th><th>First it tries...</th><th>Then it tries...</th></tr></thead><tbody><tr><td>
422
+ 1. Rails assumes you want to use the file that matches your environment, if it exists (e.g. `RAILS_ENV=production` will look for `config/credentials/production.yml.enc`)
423
+ 2. Rails does _not_ support environment-specfic keys, but it _does_ now aggressively loads credentials at boot time.
423
424
 
424
- `config/credentials.yml.enc`
425
+ **This means `RAILS_MASTER_KEY` MUST be the decryption key for your environment-specific credential file, if one exists.**
425
426
 
426
- </td><td>
427
+ But, because Ravioli merges environment-specific credentials over top of the root credentials file, you'll need to provide encryption keys for two (or, if you have a staging setup, three) different files in ENV vars. As such, Ravioli looks for decryption keys in a way that mirrors Rails' assumptions, but allows progressive layering of credentials.
427
428
 
428
- `ENV["RAILS_BASE_KEY"]`
429
+ Here are a few examples
429
430
 
430
- </td><td>
431
+ <table><thead><tr><th>File</th><th>First it tries...</th><th>Then it tries...</th></tr></thead><tbody>
431
432
 
432
- `ENV["RAILS_MASTER_KEY"]`
433
+ <tr>
434
+ <td><code>config/credentials.yml.enc</code></td>
435
+ <td><code>ENV["RAILS_MASTER_KEY"]</code></td>
436
+ <td><code>ENV["RAILS_ROOT_KEY"]</code></td>
437
+ </tr>
433
438
 
434
- </td></tr><tr><td>
439
+ <tr>
440
+ <td><code>config/credentials/#{RAILS_ENV}.yml.enc</code></td>
441
+ <td><code>ENV["RAILS_MASTER_KEY"]</code></td>
442
+ <td><code>ENV["RAILS_#{RAILS_ENV}_KEY"]</code></td>
443
+ </tr>
435
444
 
436
- `config/credentials/production.yml.enc`
437
445
 
438
- </td><td>
446
+ <tr>
447
+ <td><code>config/credentials/staging.yml.enc</code></td>
448
+ <td><code>ENV["RAILS_MASTER_KEY"]</code></td>
449
+ <td><code>ENV["RAILS_STAGING_KEY"]</code></td>
450
+ </tr>
439
451
 
440
- `ENV["RAILS_PRODUCTION_KEY"]`
452
+ </tbody></table>
441
453
 
442
- </td><td>
443
-
444
- `ENV["RAILS_MASTER_KEY"]`
445
-
446
- </td></tr><tr><td>
447
-
448
- `config/credentials/staging.yml.enc` (only if running on staging)
449
-
450
- </td><td>
451
-
452
- `ENV["RAILS_STAGING_KEY"]`
453
-
454
- </td><td>
455
-
456
- `ENV["RAILS_MASTER_KEY"]`
454
+ Credentials are loaded in that order, too, so that you can have a base setup on `config/credentials.yml.enc`, overlay that with production-specific stuff from `config/credentials/production.yml.enc`, and then short-circuit or redirect some stuff in `config/credentials/staging.yml.enc` for staging environments.
457
455
 
458
- </td></tr></tbody></table>
456
+ #### TLDR:
459
457
 
460
- Credentials are loaded in that order, too, so that you can have a base setup on `config/credentials.yml.enc`, overlay that with production-specific stuff from `config/credentials/production.yml.enc`, and then short-circuit or redirect some stuff in `config/credentials/staging.yml.enc` for staging environments.
458
+ 1. Set `RAILS_MASTER_KEY` to the key for your specific environment
459
+ 2. Set `RAILS_STAGING_KEY` to the key for your staging credentials (if deploying to staging AND you have staging-specific credentials)
460
+ 3. Set `RAILS_ROOT_KEY` to the key for your root credentials (if you have anything in `config/credentials.yml.enc`)
461
461
 
462
462
  ## License
463
463
 
data/lib/ravioli.rb CHANGED
@@ -40,4 +40,4 @@ module Ravioli
40
40
  end
41
41
  end
42
42
 
43
- require_relative "ravioli/engine" if defined?(Rails)
43
+ require_relative "ravioli/railtie" if defined?(Rails)
@@ -81,15 +81,6 @@ module Ravioli
81
81
  def add_staging_flag!(is_staging = Rails.env.production? && ENV["STAGING"].present?)
82
82
  is_staging = is_staging.present?
83
83
  configuration.staging = is_staging
84
- Rails.env.class_eval <<-EOC, __FILE__, __LINE__ + 1
85
- def staging?
86
- config = Rails.try(:config)
87
- return false unless config&.is_a?(Ravioli::Configuration)
88
-
89
- config.staging?
90
- end
91
- EOC
92
- is_staging
93
84
  end
94
85
 
95
86
  # Iterates through the config directory (including nested folders) and
@@ -104,14 +95,28 @@ module Ravioli
104
95
 
105
96
  # Loads Rails encrypted credentials that it can. Checks for corresponding private key files, or ENV vars based on the {Ravioli::Builder credentials preadmlogic}
106
97
  def auto_load_credentials!
107
- # Load the base config
108
- load_credentials(key_path: "config/master.key", env_name: "base")
109
-
110
- # Load any environment-specific configuration on top of it
111
- load_credentials("config/credentials/#{Rails.env}", key_path: "config/credentials/#{Rails.env}.key", env_name: "master")
98
+ # Load the root config (supports using the master key or `RAILS_ROOT_KEY`)
99
+ load_credentials(
100
+ key_path: "config/master.key",
101
+ env_names: %w[master root],
102
+ )
103
+
104
+ # Load any environment-specific configuration on top of it. Since Rails will try
105
+ # `RAILS_MASTER_KEY` from the environment, we assume the same
106
+ load_credentials(
107
+ "config/credentials/#{Rails.env}",
108
+ key_path: "config/credentials/#{Rails.env}.key",
109
+ env_names: ["master"],
110
+ )
112
111
 
113
112
  # Apply staging configuration on top of THAT, if need be
114
- load_credentials("config/credentials/staging", key_path: "config/credentials/staging.key") if configuration.staging?
113
+ if configuration.staging?
114
+ load_credentials(
115
+ "config/credentials/staging",
116
+ env_names: %w[staging master],
117
+ key_path: "config/credentials/staging.key",
118
+ )
119
+ end
115
120
  end
116
121
 
117
122
  # When the builder is done working, lock the configuration and return it
@@ -140,11 +145,30 @@ module Ravioli
140
145
  end
141
146
 
142
147
  # Load secure credentials using a key either from a file or the ENV
143
- def load_credentials(path = "credentials", key_path: path, env_name: path.split("/").last)
144
- credentials = parse_credentials(path, env_name: env_name, key_path: key_path)
145
- configuration.append(credentials) if credentials.present?
146
- rescue => error
147
- warn "Could not decrypt `#{path}.yml.enc' with key file `#{key_path}' or `ENV[\"#{env_name}\"]'", error, critical: false
148
+ def load_credentials(path = "credentials", key_path: path, env_names: path.split("/").last)
149
+ error = nil
150
+ env_names = Array(env_names).map { |env_name| parse_env_name(env_name) }
151
+ env_names.each do |env_name|
152
+ credentials = parse_credentials(path, env_name: env_name, key_path: key_path)
153
+ if credentials.present?
154
+ configuration.append(credentials)
155
+ return credentials
156
+ end
157
+ rescue => e
158
+ error = e
159
+ end
160
+
161
+ if error
162
+ attempted_names = ["key file `#{key_path}'"]
163
+ attempted_names.push(*env_names.map { |env_name| "`ENV[\"#{env_name}\"]'" })
164
+ attempted_names = attempted_names.to_sentence(two_words_connector: " or ", last_word_connector: ", or ")
165
+ warn(
166
+ "Could not decrypt `#{path}.yml.enc' with #{attempted_names}",
167
+ error,
168
+ critical: false,
169
+ )
170
+ end
171
+
148
172
  {}
149
173
  end
150
174
 
@@ -189,7 +213,7 @@ module Ravioli
189
213
  @reload_credentials.add(@current_credentials)
190
214
  end
191
215
 
192
- configuration.send(*args, &block)
216
+ configuration.fetch_env_key_for(args) { configuration.send(*args, &block) }
193
217
  end
194
218
  # rubocop:enable Style/MissingRespondToMissing
195
219
  # rubocop:enable Style/MethodMissingSuper
@@ -233,13 +257,17 @@ module Ravioli
233
257
  {name => config}
234
258
  end
235
259
 
260
+ def parse_env_name(env_name)
261
+ env_name = env_name.to_s
262
+ env_name.match?(/^RAILS_/) ? env_name : "RAILS_#{env_name.upcase}_KEY"
263
+ end
264
+
236
265
  def parse_credentials(path, key_path: path, env_name: path.split("/").last)
237
266
  @current_credentials = path
238
- env_name = env_name.to_s
239
- env_name = "RAILS_#{env_name.upcase}_KEY" unless env_name.upcase == env_name
267
+ env_name = parse_env_name(env_name)
240
268
  key_path = path_to_config_file_path(key_path, extnames: "key", quiet: true)
241
- options = {key_path: key_path}
242
- options[:env_key] = ENV[env_name].present? ? env_name : SecureRandom.hex(6)
269
+ options = {key_path: key_path.to_s}
270
+ options[:env_key] = ENV[env_name].present? ? env_name : "__RAVIOLI__#{SecureRandom.hex(6)}"
243
271
 
244
272
  path = path_to_config_file_path(path, extnames: "yml.enc")
245
273
  (Rails.application.encrypted(path, **options)&.config || {}).tap do
@@ -288,7 +316,7 @@ module Ravioli
288
316
  if @strict && critical
289
317
  raise BuildError.new(message, error)
290
318
  else
291
- Rails.logger.warn(message) if defined? Rails
319
+ Rails.logger.try(:warn, message) if defined? Rails
292
320
  $stderr.write message # rubocop:disable Rails/Output
293
321
  end
294
322
  end
@@ -45,6 +45,11 @@ module Ravioli
45
45
  dig(*keys) || yield
46
46
  end
47
47
 
48
+ def fetch_env_key_for(keys, &block)
49
+ env_key = key_path_for(keys).join("_").upcase
50
+ ENV.fetch(env_key, &block)
51
+ end
52
+
48
53
  def pretty_print(printer = nil)
49
54
  table.pretty_print(printer)
50
55
  end
@@ -80,11 +85,6 @@ module Ravioli
80
85
  end
81
86
  end
82
87
 
83
- def fetch_env_key_for(keys, &block)
84
- env_key = key_path_for(keys).join("_").upcase
85
- ENV.fetch(env_key, &block)
86
- end
87
-
88
88
  def key_path_for(keys)
89
89
  Array(key_path) + Array(keys)
90
90
  end
@@ -1,15 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Ravioli
4
- class Engine < ::Rails::Engine
5
- # Bootstrap Ravioli onto the Rails app
6
- initializer "ravioli", before: :load_environment_config do |app|
7
- Rails.extend Ravioli::RailsConfig unless Rails.respond_to?(:config)
8
- end
9
- end
10
-
11
- private
3
+ require_relative "./staging_inquirer"
12
4
 
5
+ module Ravioli
13
6
  module RailsConfig
14
7
  def config
15
8
  Ravioli.default || Ravioli.build(namespace: Rails.application&.class&.module_parent, strict: Rails.env.production?) do |config|
@@ -19,4 +12,10 @@ module Ravioli
19
12
  end
20
13
  end
21
14
  end
15
+
16
+ class Railtie < ::Rails::Railtie
17
+ # Bootstrap Ravioli onto the Rails app
18
+ Rails.env.class.prepend Ravioli::StagingInquirer
19
+ Rails.extend Ravioli::RailsConfig unless Rails.respond_to?(:config)
20
+ end
22
21
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ravioli
4
+ # A module that we mix in to the `Rails.env` inquirer class to add some extra staging-related
5
+ # metadata
6
+ module StagingInquirer
7
+ # Add a `name` method to `Rails.env` that will return "staging" for staging environments, and
8
+ # otherwise the string's value
9
+ def name
10
+ staging? ? "staging" : to_s
11
+ end
12
+
13
+ # Add a `strict:` keyword to reduce `Rails.env.production && !Rails.env.staging` calls
14
+ def production?(strict: false)
15
+ is_production = super()
16
+ return is_production unless strict && is_production
17
+
18
+ is_production && !staging?
19
+ end
20
+
21
+ # Override staging inquiries to check against the current configuration
22
+ def staging?
23
+ Rails.try(:config)&.staging?
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ravioli
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ravioli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flip Sasser
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-28 00:00:00.000000000 Z
11
+ date: 2021-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -270,7 +270,8 @@ files:
270
270
  - lib/ravioli.rb
271
271
  - lib/ravioli/builder.rb
272
272
  - lib/ravioli/configuration.rb
273
- - lib/ravioli/engine.rb
273
+ - lib/ravioli/railtie.rb
274
+ - lib/ravioli/staging_inquirer.rb
274
275
  - lib/ravioli/version.rb
275
276
  homepage: https://github.com/flipsasser/ravioli
276
277
  licenses: