mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #33962 from kaspth/restructure-environment-credentials
Restructure credentials after environment overrides.
This commit is contained in:
commit
54d4a518d9
7 changed files with 126 additions and 118 deletions
|
@ -116,12 +116,20 @@
|
||||||
|
|
||||||
*Richard Schneeman*
|
*Richard Schneeman*
|
||||||
|
|
||||||
* Support environment specific credentials file.
|
* Support environment specific credentials overrides.
|
||||||
|
|
||||||
For `production` environment look first for `config/credentials/production.yml.enc` file that can be decrypted by
|
So any environment will look for `config/credentials/#{Rails.env}.yml.enc` and fall back
|
||||||
`ENV["RAILS_MASTER_KEY"]` or `config/credentials/production.key` master key.
|
to `config/credentials.yml.enc`.
|
||||||
Edit given environment credentials file by command `rails credentials:edit --environment production`.
|
|
||||||
Default paths can be overwritten by setting `config.credentials.content_path` and `config.credentials.key_path`.
|
The encryption key can be in `ENV["RAILS_MASTER_KEY"]` or `config/credentials/production.key`.
|
||||||
|
|
||||||
|
Environment credentials overrides can be edited with `rails credentials:edit --environment production`.
|
||||||
|
If no override is setup for the passed environment, it will be created.
|
||||||
|
|
||||||
|
Additionally, the default lookup paths can be overwritten with these configs:
|
||||||
|
|
||||||
|
- `config.credentials.content_path`
|
||||||
|
- `config.credentials.key_path`
|
||||||
|
|
||||||
*Wojciech Wnętrzak*
|
*Wojciech Wnętrzak*
|
||||||
|
|
||||||
|
|
|
@ -293,25 +293,25 @@ module Rails
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def credentials_available_for_current_env?
|
|
||||||
File.exist?("#{root}/config/credentials/#{Rails.env}.yml.enc")
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_credentials_content_path
|
def default_credentials_content_path
|
||||||
if credentials_available_for_current_env?
|
if credentials_available_for_current_env?
|
||||||
File.join(root, "config", "credentials", "#{Rails.env}.yml.enc")
|
root.join("config", "credentials", "#{Rails.env}.yml.enc")
|
||||||
else
|
else
|
||||||
File.join(root, "config", "credentials.yml.enc")
|
root.join("config", "credentials.yml.enc")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_credentials_key_path
|
def default_credentials_key_path
|
||||||
if credentials_available_for_current_env?
|
if credentials_available_for_current_env?
|
||||||
File.join(root, "config", "credentials", "#{Rails.env}.key")
|
root.join("config", "credentials", "#{Rails.env}.key")
|
||||||
else
|
else
|
||||||
File.join(root, "config", "master.key")
|
root.join("config", "master.key")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def credentials_available_for_current_env?
|
||||||
|
File.exist?(root.join("config", "credentials", "#{Rails.env}.yml.enc"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,9 +41,18 @@ from leaking.
|
||||||
|
|
||||||
=== Environment Specific Credentials
|
=== Environment Specific Credentials
|
||||||
|
|
||||||
It is possible to have credentials for each environment. If the file for current environment exists it will take
|
The `credentials` command supports passing an `--environment` option to create an
|
||||||
precedence over `config/credentials.yml.enc`, thus for `production` environment first look for
|
environment specific override. That override will takes precedence over the
|
||||||
`config/credentials/production.yml.enc` that can be decrypted using master key taken from `ENV["RAILS_MASTER_KEY"]`
|
global `config/credentials.yml.enc` file when running in that environment. So:
|
||||||
or stored in `config/credentials/production.key`.
|
|
||||||
To edit given file use command `rails credentials:edit --environment production`
|
rails credentials:edit --environment development
|
||||||
Default paths can be overwritten by setting `config.credentials.content_path` and `config.credentials.key_path`.
|
|
||||||
|
will create `config/credentials/development.yml.enc` with the corresponding
|
||||||
|
encryption key in `config/credentials/development.key` if the credentials file
|
||||||
|
doesn't exist.
|
||||||
|
|
||||||
|
The encryption key can also be put in `ENV["RAILS_MASTER_KEY"]`, which takes
|
||||||
|
precedence over the file encryption key.
|
||||||
|
|
||||||
|
In addition to that, the default credentials lookup paths can be overriden through
|
||||||
|
`config.credentials.content_path` and `config.credentials.key_path`.
|
||||||
|
|
|
@ -24,13 +24,11 @@ module Rails
|
||||||
|
|
||||||
ensure_editor_available(command: "bin/rails credentials:edit") || (return)
|
ensure_editor_available(command: "bin/rails credentials:edit") || (return)
|
||||||
|
|
||||||
encrypted = Rails.application.encrypted(content_path, key_path: key_path)
|
ensure_encryption_key_has_been_added if credentials.key.nil?
|
||||||
|
ensure_credentials_have_been_added
|
||||||
ensure_encryption_key_has_been_added(key_path) if encrypted.key.nil?
|
|
||||||
ensure_encrypted_file_has_been_added(content_path, key_path)
|
|
||||||
|
|
||||||
catch_editing_exceptions do
|
catch_editing_exceptions do
|
||||||
change_encrypted_file_in_system_editor(content_path, key_path)
|
change_credentials_in_system_editor
|
||||||
end
|
end
|
||||||
|
|
||||||
say "File encrypted and saved."
|
say "File encrypted and saved."
|
||||||
|
@ -41,12 +39,38 @@ module Rails
|
||||||
def show
|
def show
|
||||||
require_application_and_environment!
|
require_application_and_environment!
|
||||||
|
|
||||||
encrypted = Rails.application.encrypted(content_path, key_path: key_path)
|
say credentials.read.presence || missing_credentials_message
|
||||||
|
|
||||||
say encrypted.read.presence || missing_encrypted_message(key: encrypted.key, key_path: key_path, file_path: content_path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def credentials
|
||||||
|
Rails.application.encrypted(content_path, key_path: key_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_encryption_key_has_been_added
|
||||||
|
encryption_key_file_generator.add_key_file(key_path)
|
||||||
|
encryption_key_file_generator.ignore_key_file(key_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_credentials_have_been_added
|
||||||
|
encrypted_file_generator.add_encrypted_file_silently(content_path, key_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_credentials_in_system_editor
|
||||||
|
credentials.change do |tmp_path|
|
||||||
|
system("#{ENV["EDITOR"]} #{tmp_path}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_credentials_message
|
||||||
|
if credentials.key.nil?
|
||||||
|
"Missing '#{key_path}' to decrypt credentials. See `rails credentials:help`"
|
||||||
|
else
|
||||||
|
"File '#{content_path}' does not exist. Use `rails credentials:edit` to change that."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def content_path
|
def content_path
|
||||||
options[:environment] ? "config/credentials/#{options[:environment]}.yml.enc" : "config/credentials.yml.enc"
|
options[:environment] ? "config/credentials/#{options[:environment]}.yml.enc" : "config/credentials.yml.enc"
|
||||||
end
|
end
|
||||||
|
@ -56,22 +80,6 @@ module Rails
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def ensure_encryption_key_has_been_added(key_path)
|
|
||||||
encryption_key_file_generator.add_key_file(key_path)
|
|
||||||
encryption_key_file_generator.ignore_key_file(key_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_encrypted_file_has_been_added(file_path, key_path)
|
|
||||||
encrypted_file_generator.add_encrypted_file_silently(file_path, key_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def change_encrypted_file_in_system_editor(file_path, key_path)
|
|
||||||
Rails.application.encrypted(file_path, key_path: key_path).change do |tmp_path|
|
|
||||||
system("#{ENV["EDITOR"]} #{tmp_path}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def encryption_key_file_generator
|
def encryption_key_file_generator
|
||||||
require "rails/generators"
|
require "rails/generators"
|
||||||
require "rails/generators/rails/encryption_key_file/encryption_key_file_generator"
|
require "rails/generators/rails/encryption_key_file/encryption_key_file_generator"
|
||||||
|
@ -85,14 +93,6 @@ module Rails
|
||||||
|
|
||||||
Rails::Generators::EncryptedFileGenerator.new
|
Rails::Generators::EncryptedFileGenerator.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def missing_encrypted_message(key:, key_path:, file_path:)
|
|
||||||
if key.nil?
|
|
||||||
"Missing '#{key_path}' to decrypt credentials. See `rails credentials:help`"
|
|
||||||
else
|
|
||||||
"File '#{file_path}' does not exist. Use `rails credentials:edit` to change that."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
56
railties/test/application/credentials_test.rb
Normal file
56
railties/test/application/credentials_test.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "isolation/abstract_unit"
|
||||||
|
require "env_helpers"
|
||||||
|
|
||||||
|
class Rails::CredentialsTest < ActiveSupport::TestCase
|
||||||
|
include ActiveSupport::Testing::Isolation, EnvHelpers
|
||||||
|
|
||||||
|
setup :build_app
|
||||||
|
teardown :teardown_app
|
||||||
|
|
||||||
|
test "reads credentials from environment specific path" do
|
||||||
|
write_credentials_override(:production)
|
||||||
|
|
||||||
|
app("production")
|
||||||
|
|
||||||
|
assert_equal "revealed", Rails.application.credentials.mystery
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reads credentials from customized path and key" do
|
||||||
|
write_credentials_override(:staging)
|
||||||
|
add_to_env_config("production", "config.credentials.content_path = config.root.join('config/credentials/staging.yml.enc')")
|
||||||
|
add_to_env_config("production", "config.credentials.key_path = config.root.join('config/credentials/staging.key')")
|
||||||
|
|
||||||
|
app("production")
|
||||||
|
|
||||||
|
assert_equal "revealed", Rails.application.credentials.mystery
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reads credentials using environment variable key" do
|
||||||
|
write_credentials_override(:production, with_key: false)
|
||||||
|
|
||||||
|
switch_env("RAILS_MASTER_KEY", credentials_key) do
|
||||||
|
app("production")
|
||||||
|
|
||||||
|
assert_equal "revealed", Rails.application.credentials.mystery
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def write_credentials_override(name, with_key: true)
|
||||||
|
Dir.chdir(app_path) do
|
||||||
|
Dir.mkdir "config/credentials"
|
||||||
|
File.write "config/credentials/#{name}.key", credentials_key if with_key
|
||||||
|
|
||||||
|
# secret_key_base: secret
|
||||||
|
# mystery: revealed
|
||||||
|
File.write "config/credentials/#{name}.yml.enc",
|
||||||
|
"vgvKu4MBepIgZ5VHQMMPwnQNsLlWD9LKmJHu3UA/8yj6x+3fNhz3DwL9brX7UA==--qLdxHP6e34xeTAiI--nrcAsleXuo9NqiEuhntAhw=="
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def credentials_key
|
||||||
|
"2117e775dc2024d4f49ddf3aeb585919"
|
||||||
|
end
|
||||||
|
end
|
|
@ -165,12 +165,12 @@ module ApplicationTests
|
||||||
app.config.some_setting = "a_different_setting"
|
app.config.some_setting = "a_different_setting"
|
||||||
assert_equal "a_different_setting", app.config.some_setting, "The configuration's some_setting should be set."
|
assert_equal "a_different_setting", app.config.some_setting, "The configuration's some_setting should be set."
|
||||||
|
|
||||||
new_config = Rails::Application::Configuration.new("root_of_application")
|
new_config = Rails::Application::Configuration.new(Pathname.new("root_of_application"))
|
||||||
new_config.some_setting = "some_setting_dude"
|
new_config.some_setting = "some_setting_dude"
|
||||||
app.config = new_config
|
app.config = new_config
|
||||||
|
|
||||||
assert_equal "some_setting_dude", app.config.some_setting, "The configuration's some_setting should have changed."
|
assert_equal "some_setting_dude", app.config.some_setting, "The configuration's some_setting should have changed."
|
||||||
assert_equal "root_of_application", app.config.root, "The root should have changed to the new config's root."
|
assert_equal "root_of_application", app.config.root.to_s, "The root should have changed to the new config's root."
|
||||||
assert_equal new_config, app.config, "The application's config should have changed to the new config."
|
assert_equal new_config, app.config, "The application's config should have changed to the new config."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require "isolation/abstract_unit"
|
|
||||||
require "env_helpers"
|
|
||||||
|
|
||||||
class Rails::CredentialsTest < ActiveSupport::TestCase
|
|
||||||
include ActiveSupport::Testing::Isolation, EnvHelpers
|
|
||||||
|
|
||||||
setup :build_app
|
|
||||||
teardown :teardown_app
|
|
||||||
|
|
||||||
test "reads credentials from environment specific path" do
|
|
||||||
with_credentials do |content, key|
|
|
||||||
Dir.chdir(app_path) do
|
|
||||||
Dir.mkdir("config/credentials")
|
|
||||||
File.write("config/credentials/production.yml.enc", content)
|
|
||||||
File.write("config/credentials/production.key", key)
|
|
||||||
end
|
|
||||||
|
|
||||||
app("production")
|
|
||||||
|
|
||||||
assert_equal "revealed", Rails.application.credentials.mystery
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "reads credentials from customized path and key" do
|
|
||||||
with_credentials do |content, key|
|
|
||||||
Dir.chdir(app_path) do
|
|
||||||
Dir.mkdir("config/credentials")
|
|
||||||
File.write("config/credentials/staging.yml.enc", content)
|
|
||||||
File.write("config/credentials/staging.key", key)
|
|
||||||
end
|
|
||||||
|
|
||||||
add_to_env_config("production", "config.credentials.content_path = config.root.join('config/credentials/staging.yml.enc')")
|
|
||||||
add_to_env_config("production", "config.credentials.key_path = config.root.join('config/credentials/staging.key')")
|
|
||||||
app("production")
|
|
||||||
|
|
||||||
assert_equal "revealed", Rails.application.credentials.mystery
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "reads credentials using environment variable key" do
|
|
||||||
with_credentials do |content, key|
|
|
||||||
Dir.chdir(app_path) do
|
|
||||||
Dir.mkdir("config/credentials")
|
|
||||||
File.write("config/credentials/production.yml.enc", content)
|
|
||||||
end
|
|
||||||
|
|
||||||
switch_env("RAILS_MASTER_KEY", key) do
|
|
||||||
app("production")
|
|
||||||
|
|
||||||
assert_equal "revealed", Rails.application.credentials.mystery
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def with_credentials
|
|
||||||
key = "2117e775dc2024d4f49ddf3aeb585919"
|
|
||||||
# secret_key_base: secret
|
|
||||||
# mystery: revealed
|
|
||||||
content = "vgvKu4MBepIgZ5VHQMMPwnQNsLlWD9LKmJHu3UA/8yj6x+3fNhz3DwL9brX7UA==--qLdxHP6e34xeTAiI--nrcAsleXuo9NqiEuhntAhw=="
|
|
||||||
yield(content, key)
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue