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 +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:
|