As of Rails 6, there are 3 supported ways to tackle the problem of adding sensitive information to your app’s configuration securely: 1) Secrets, 2) Encrypted Credentials (Rails 5.2) and 3) Multi-Environment Encrypted Credentials (Rails 6).
There are a few gotchas when trying to navigate which approach to use and how each works. Here is a list of gotchas in no particular order:
The documentation in the Rails Guides about Encrypted Credentials is limited and only mentions the Rails 5.2 variant (it does not mention the multi-environment encrypted credentials). Furthermore, the 5.2 variant received a lot of “air time” and most blog posts online related to credentials talk about it, making it is easy to overlook that the 6.0 variant even exists. My suggestion would be to include documentation for multi-env encrypted credentials on the Rails guides.
If you are upgrading from the secrets.yml approach to the Encrypted Credentials approach (Rails 5.2), you might be tempted to add “environment namespacing” with keys inside your config/credentials.yml.enc file. However, when coding locally, you will be surprised when you change your credentials using rails credentials:edit and your rails console / rails server do not pick up the changes even if you re-start them. To make that happen, you need to restart Spring.
(Related to the point above) However, if you use the multi-env encrypted credentials approach with rails credentials:edit --environment development, changes are picked up automatically by Spring and everything works as expected.
The lines between using rails credentials:edit (5.2) and rails credentials:edit --environment production (6.0) are blurry. New developers might think that the automatic fallback from an environment-specific file to a “general” file also applies in this scenario, and it doesn’t. The best documentation I found about this behaviour is: a PR and a blog post, but no mention of this on the Rails Guides.
Say you create a production-specific credentials file with rails credentials:edit --environment production, which in turn creates a config/credentials/production.key. When you deploy to production, you can provide the key by setting the RAILS_PRODUCTION_KEY OR the RAILS_MASTER_KEY environment variables. However, if you only use the config/credentials.yml.enc 5.2 variant, then RAILS_MASTER_KEY should be used to host the master.key. Since both variants can be configured at the same time, but only one (the most specific one) is used when the app loads, the name overloading of RAILS_MASTER_KEY becomes very confusing.
It sounds like this is a problem similar to the current confusion around which front-end methods are preferred/supported nowadays: there are a few different overlapping approaches, the oldest of which is still needed for backwards compatibility because migration between them is difficult.
I’m wondering about a two-pronged approach for tackling this. On the first prong, improving documentation; on the second prong, figuring out ways to smooth migration paths so that we can get back to a single recommended mechanism (with support for other options, such as env var config).
From this writeup, it sounds like you have a pretty good idea what the Rails Guides are missing. As a first step, would you be comfortable writing up an outline for the things you think the Guides ought to include?
This would be great… One of the reasons that I love Rails is because of its Convention of Configuration mantra, and secrets along with the front-end methods right now seems to be the main exceptions to this rule.
Which of these options is more suitable: Option 1: Update the Rails Guides directly and make a PR? Option 2: Make a detailed list of the guides and sections that have gaps and suggest some content for them (and some else makes the changes)?
If you are thinking of Option 2, where do I post the result?
Option 2, with the results posted either here or an issue on the repository, seems better. In the past the maintainers have sometimes had trouble “digesting” long documentation PRs. The longer the PR, the harder to review, the more changes are needed, the more likely it is that everyone will get frustrated. So figuring out ways to break the work up into small chunks upfront will help everyone out a lot.
config.credentials.content_path configures lookup path for encrypted credentials.
Gaps
With the simultaneous support of global encrypted credentials (introduced in Rails 5.2) and multi-environment encrypted credentials (introduced in Rails 6), the credentials file that ends up being decrypted and loaded goes through some defaulting and fallback logic that is non-trivial. It may be worth mentioning that content_path fully overrides that logic.
Mention that content_path overrides the defaulting and fallback logic of finding the encrypted credentials file. (i.e. If set, it takes precedence over "config/credentials/#{Rails.env.downcase}.yml.enc" and its fallback config/credentials.yml.enc).
Clarify possible confusion points: what happens when content_path is set and key_path is not? Is there any interaction between them?
config.credentials.key_path
config.credentials.key_path configures lookup path for encryption key.
Mention that key_path overrides the defaulting and fallback logic of finding the encryption key file. (i.e. If set, it takes precedence over ENV["RAILS_MASTER_KEY"] and its fallback "config/#{Rails.env.downcase}.key").
Possibly mention any interactions that the key_path has with content_path.
secret_key_base
secret_key_base is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering…
This is the only section on all the Rails Guides that touches on encrypted credentials. These are the most relevant documentation gaps:
The section does not explain the problem the feature is solving (why are encrypted credentials important).
The documentation only touches lightly on global encrypted credentials. Multi-environment encrypted credentials are not mentioned.
The documentation suggests that config/master.key is looked for first, and then it defaults to ENV["RAILS_MASTER_KEY"], when the behaviour is the other way around.
There is good documentation about this feature inside rails credentials:help that has not been surfaced to the Rails guides.
Suggestions
Most of the suggestions below can be achieved by using what is already inside rails credentials:help.
Rename the tilte of section 10.1 to Encrypted Credentials (or something similar). This is the name of the feature and is what people Google for.
Use the body of section 10.1 to explain the motivation of this feature (i.e. the problem it solves).
Remove the information on rails secret that is currently in the section. It is not relevant on this section and becomes a potential source of confusion.
Add 2 sub-sections 10.1.1 Global Encrypted Credentials and 10.1.2 Multi-environment Encrypted Credentials and use them to explain each of the features separetely.
On section 10.1.1:
Mention that the feature looks forENV["RAILS_MASTER_KEY"] first, and then falls back to config/master.key.
Mention the overrides that can be done using content_path and key_path and link to the “Section: 3.1 Rails General Configuration” documentation.
On section 10.1.2:
Mention that the feature looks forENV["RAILS_MASTER_KEY"] first, and then for "config/credentials/#{Rails.env.downcase}.key".
Mention that if "config/credentials/#{Rails.env.downcase}.yml.enc" is not found, the feature fallsback to global encrypted credentials using the file config/credentials.yml.enc.
Be sure to mention that the feature loads one or the other. In other words, there is no "if a key is not found in config/credentials/staging.yml.enc then the key is looked for in config/credentials.yml.enc.
Mention the overrides that can be done using content_path and key_path and link to the “Section: 3.1 Rails General Configuration” documentation.
Good citizen: Add a warning along the lines of “if you are having trouble with changes in your credentials not being loaded on local development, try re-starting spring”.
[Ah, I just noticed your updated, final post–looks like we’re in complete agreement. Your post is much more comprehensive than mine! Thank you, @sergio.rodriguez!]
I recently struggled to figure out why my app was crashing in production, and it is indeed confusing.
What I ultimately figured out is that (as far as I can tell) there is no significance to an environment variable called RAILS_PRODUCTION_KEY (or any other Rails environment-flavored environment variable). The only environment variable that Rails will interpret as a decryption key that overrides a decryption key in a file (master.key, production.key, or whatever) is RAILS_MASTER_KEY.
Please let me know if I’m understanding things wrong.