ravioli 0.1.3 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +29 -29
- data/lib/ravioli.rb +1 -1
- data/lib/ravioli/builder.rb +54 -26
- data/lib/ravioli/configuration.rb +5 -5
- data/lib/ravioli/{engine.rb → railtie.rb} +8 -9
- data/lib/ravioli/staging_inquirer.rb +26 -0
- data/lib/ravioli/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a42c50f1ae58ea21c70956c3a2fda57084dc20042efc3e163c26b80a8ecc8c58
|
4
|
+
data.tar.gz: 2fafc358079f1bab16f25e36563dc5f843fedd7d85e32b8e34ca337b2c3c23ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
420
|
+
Here are a few facts about credentials in Rails and how they're deployed:
|
421
421
|
|
422
|
-
|
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
|
-
`
|
425
|
+
**This means `RAILS_MASTER_KEY` MUST be the decryption key for your environment-specific credential file, if one exists.**
|
425
426
|
|
426
|
-
|
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
|
-
|
429
|
+
Here are a few examples
|
429
430
|
|
430
|
-
</
|
431
|
+
<table><thead><tr><th>File</th><th>First it tries...</th><th>Then it tries...</th></tr></thead><tbody>
|
431
432
|
|
432
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
452
|
+
</tbody></table>
|
441
453
|
|
442
|
-
|
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
|
-
|
456
|
+
#### TLDR:
|
459
457
|
|
460
|
-
|
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
data/lib/ravioli/builder.rb
CHANGED
@@ -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
|
108
|
-
load_credentials(
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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,
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
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
|
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
|
-
|
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
|
data/lib/ravioli/version.rb
CHANGED
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.
|
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-
|
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/
|
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:
|