From 20b541524463a1e4ed9e65a18b2c454affb3bbd4 Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Fri, 20 Jan 2023 12:20:07 -0600 Subject: [PATCH] Load environment config in credentials commands This commit changes the credentials commands (e.g. `bin/rails credentials:edit`) to load `config/environments/#{Rails.env}.rb`. Thus, `config.credentials.content_path` and `config.credentials.key_path` can be set in `config/environments/*.rb`, in addition to the currently supported `config/application.rb`. The `load_environment_config` initializer, which is run via `Rails.application.initialize!`, is responsible for loading the appropriate `config/environments/*.rb` file. Normally, when booting an app, `Rails.application.initialize!` is called without arguments by `config/environment.rb`, which is loaded via `Rails.application.require_environment!`. Doing so runs all initializers. Running all initializers is problematic for credentials commands because (1) initializers might try to access resources that aren't available (e.g. a production database), and (2) initializers might try to read credentials values that have not yet been set in the current environment's credentials (see #34789). Thus, the credentials commands call `Rails.application.initialize!` directly with a dummy group argument, so that initializers in the `:all` group -- including `load_environment_config` -- are run, but not initializers in the default group, such as `active_record.initialize_database` and `load_config_initializers`. Closes #40778. Co-authored-by Brian Thoman <> --- guides/source/configuring.md | 4 ++-- railties/CHANGELOG.md | 17 +++----------- railties/lib/rails/command/actions.rb | 22 +++++++++---------- .../rails/commands/console/console_command.rb | 2 +- .../credentials/credentials_command.rb | 6 ++--- .../commands/dbconsole/dbconsole_command.rb | 2 +- .../rails/commands/destroy/destroy_command.rb | 4 ++-- .../commands/encrypted/encrypted_command.rb | 4 ++-- .../commands/generate/generate_command.rb | 4 ++-- .../initializers/initializers_command.rb | 2 +- .../lib/rails/commands/notes/notes_command.rb | 2 +- .../rails/commands/routes/routes_command.rb | 2 +- .../rails/commands/runner/runner_command.rb | 2 +- .../rails/commands/secrets/secrets_command.rb | 2 +- .../unused_routes/unused_routes_command.rb | 2 +- railties/test/commands/credentials_test.rb | 22 +++++++++++++++++++ 16 files changed, 54 insertions(+), 45 deletions(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index ea91bcbcb5451..06576ff4890e7 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -266,7 +266,7 @@ Defaults to `config/credentials/#{Rails.env}.yml.enc` if it exists, or `config/credentials.yml.enc` otherwise. NOTE: In order for the `bin/rails credentials` commands to recognize this value, -it must be set in `config/application.rb`. +it must be set in `config/application.rb` or `config/environments/#{Rails.env}.rb`. #### `config.credentials.key_path` @@ -276,7 +276,7 @@ Defaults to `config/credentials/#{Rails.env}.key` if it exists, or `config/master.key` otherwise. NOTE: In order for the `bin/rails credentials` commands to recognize this value, -it must be set in `config/application.rb`. +it must be set in `config/application.rb` or `config/environments/#{Rails.env}.rb`. #### `config.debug_exception_response_format` diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 7c21d89c62ca0..19ca214772a81 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,6 +1,6 @@ * Credentials commands (e.g. `bin/rails credentials:edit`) now respect `config.credentials.content_path` and `config.credentials.key_path` when set - in `config/application.rb`. + in `config/application.rb` or `config/environments/#{Rails.env}.rb`. Before: @@ -21,19 +21,8 @@ would load for the current `RAILS_ENV`. * `bin/rails credentials:edit` respects `config.credentials.content_path` - and `config.credentials.key_path` when set in `config/application.rb`. - Using `RAILS_ENV`, environment-specific paths can be set, such as: - - ```ruby - # config/application.rb - module MyCoolApp - class Application < Rails::Application - config.credentials.content_path = "my_credentials/#{Rails.env}.yml.enc" - - config.credentials.key_path = "path/to/production.key" if Rails.env.production? - end - end - ``` + and `config.credentials.key_path` when set in `config/application.rb` + or `config/environments/#{Rails.env}.rb`. * `bin/rails credentials:edit --environment foo` will create and edit `config/credentials/foo.yml.enc` _if_ `config.credentials.content_path` diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb index 50651ad61a9e9..105a4bc4f5a66 100644 --- a/railties/lib/rails/command/actions.rb +++ b/railties/lib/rails/command/actions.rb @@ -10,23 +10,21 @@ def set_application_directory! Dir.chdir(File.expand_path("../..", APP_PATH)) unless File.exist?(File.expand_path("config.ru")) end - def require_application_and_environment! - require_application! - require_environment! - end - def require_application! require ENGINE_PATH if defined?(ENGINE_PATH) + require APP_PATH if defined?(APP_PATH) + end - if defined?(APP_PATH) - require APP_PATH - end + def boot_application! + require_application! + Rails.application.require_environment! if defined?(APP_PATH) end - def require_environment! - if defined?(APP_PATH) - Rails.application.require_environment! - end + def load_environment_config! + require_application! + # Only run initializers that are in the :all group, which includes the + # :load_environment_config initializer. + Rails.application.initialize!(:_) if defined?(APP_PATH) end if defined?(ENGINE_PATH) diff --git a/railties/lib/rails/commands/console/console_command.rb b/railties/lib/rails/commands/console/console_command.rb index 37fe06e54dfc7..19ac6a3eeb828 100644 --- a/railties/lib/rails/commands/console/console_command.rb +++ b/railties/lib/rails/commands/console/console_command.rb @@ -97,7 +97,7 @@ def initialize(args = [], local_options = {}, config = {}) end def perform - require_application_and_environment! + boot_application! Rails::Console.start(Rails.application, options) end end diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb index 8a8a9ffb5d60d..01df6a5f03919 100644 --- a/railties/lib/rails/commands/credentials/credentials_command.rb +++ b/railties/lib/rails/commands/credentials/credentials_command.rb @@ -24,7 +24,7 @@ def help desc "edit", "Opens the decrypted credentials in `$EDITOR` for editing" def edit - require_application! + load_environment_config! load_generators if environment_specified? @@ -41,7 +41,7 @@ def edit desc "show", "Shows the decrypted credentials" def show - require_application! + load_environment_config! say credentials.read.presence || missing_credentials_message end @@ -56,7 +56,7 @@ def show def diff(content_path = nil) if @content_path = content_path self.environment = extract_environment_from_path(content_path) - require_application! + load_environment_config! say credentials.read.presence || credentials.content_path.read else diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb index 5682199f1e757..bce9367db3526 100644 --- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb +++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb @@ -89,7 +89,7 @@ class DbconsoleCommand < Base # :nodoc: desc: "Specifies the database to use." def perform - require_application_and_environment! + boot_application! Rails::DBConsole.start(options) end end diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb index 1973302c1de39..0512cdccc4484 100644 --- a/railties/lib/rails/commands/destroy/destroy_command.rb +++ b/railties/lib/rails/commands/destroy/destroy_command.rb @@ -7,7 +7,7 @@ module Command class DestroyCommand < Base # :nodoc: no_commands do def help - require_application_and_environment! + boot_application! load_generators Rails::Generators.help self.class.command_name @@ -19,7 +19,7 @@ def perform(*) generator = args.shift return help unless generator - require_application_and_environment! + boot_application! load_generators Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails::Command.root diff --git a/railties/lib/rails/commands/encrypted/encrypted_command.rb b/railties/lib/rails/commands/encrypted/encrypted_command.rb index 381d3cd319c0f..56534d0c5332f 100644 --- a/railties/lib/rails/commands/encrypted/encrypted_command.rb +++ b/railties/lib/rails/commands/encrypted/encrypted_command.rb @@ -22,7 +22,7 @@ def help desc "edit", "Opens the decrypted file in `$EDITOR` for editing" def edit(*) - require_application! + load_environment_config! ensure_encryption_key_has_been_added ensure_encrypted_configuration_has_been_added @@ -32,7 +32,7 @@ def edit(*) desc "show", "Shows the decrypted contents of the file" def show(*) - require_application! + load_environment_config! say encrypted_configuration.read.presence || missing_encrypted_configuration_message end diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb index 09110a254725c..45611949b8573 100644 --- a/railties/lib/rails/commands/generate/generate_command.rb +++ b/railties/lib/rails/commands/generate/generate_command.rb @@ -7,7 +7,7 @@ module Command class GenerateCommand < Base # :nodoc: no_commands do def help - require_application_and_environment! + boot_application! load_generators Rails::Generators.help self.class.command_name @@ -18,7 +18,7 @@ def perform(*) generator = args.shift return help unless generator - require_application_and_environment! + boot_application! load_generators ARGV.replace(args) # set up ARGV for third-party libraries diff --git a/railties/lib/rails/commands/initializers/initializers_command.rb b/railties/lib/rails/commands/initializers/initializers_command.rb index b8ad7bf76f4b8..06934cbcb703c 100644 --- a/railties/lib/rails/commands/initializers/initializers_command.rb +++ b/railties/lib/rails/commands/initializers/initializers_command.rb @@ -9,7 +9,7 @@ class InitializersCommand < Base # :nodoc: desc "initializers", "Print out all defined initializers in the order they are invoked by Rails." def perform - require_application_and_environment! + boot_application! Rails.application.initializers.tsort_each do |initializer| say "#{initializer.context_class}.#{initializer.name}" diff --git a/railties/lib/rails/commands/notes/notes_command.rb b/railties/lib/rails/commands/notes/notes_command.rb index cea7b1c90f0ef..f53358e45895f 100644 --- a/railties/lib/rails/commands/notes/notes_command.rb +++ b/railties/lib/rails/commands/notes/notes_command.rb @@ -9,7 +9,7 @@ class NotesCommand < Base # :nodoc: desc "notes", "Shows comments in your code annotated with FIXME, OPTIMIZE, and TODO" def perform(*) - require_application_and_environment! + boot_application! display_annotations end diff --git a/railties/lib/rails/commands/routes/routes_command.rb b/railties/lib/rails/commands/routes/routes_command.rb index 50c120b204af6..a285b13274e5b 100644 --- a/railties/lib/rails/commands/routes/routes_command.rb +++ b/railties/lib/rails/commands/routes/routes_command.rb @@ -22,7 +22,7 @@ def invoke_command(*) desc "routes", "Lists all the defined routes" def perform(*) - require_application_and_environment! + boot_application! require "action_dispatch/routing/inspector" say inspector.format(formatter, routes_filter) diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb index 4f6b9cb580324..8e6eb14366ab6 100644 --- a/railties/lib/rails/commands/runner/runner_command.rb +++ b/railties/lib/rails/commands/runner/runner_command.rb @@ -25,7 +25,7 @@ def perform(code_or_file = nil, *command_argv) exit 1 end - require_application_and_environment! + boot_application! Rails.application.load_runner ARGV.replace(command_argv) diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb index 3160f80299486..d08bbb9a376a5 100644 --- a/railties/lib/rails/commands/secrets/secrets_command.rb +++ b/railties/lib/rails/commands/secrets/secrets_command.rb @@ -24,7 +24,7 @@ def setup desc "edit", "Opens the secrets in `$EDITOR` for editing" def edit - require_application_and_environment! + boot_application! using_system_editor do Rails::Secrets.read_for_editing { |tmp_path| system_editor(tmp_path) } diff --git a/railties/lib/rails/commands/unused_routes/unused_routes_command.rb b/railties/lib/rails/commands/unused_routes/unused_routes_command.rb index 8f56deb05ed90..9fd4fbff14890 100644 --- a/railties/lib/rails/commands/unused_routes/unused_routes_command.rb +++ b/railties/lib/rails/commands/unused_routes/unused_routes_command.rb @@ -40,7 +40,7 @@ def action_missing? end def perform(*) - require_application_and_environment! + boot_application! require "action_dispatch/routing/inspector" say(inspector.format(formatter, routes_filter)) diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb index 25017dd2ab43c..a8dc2b468de29 100644 --- a/railties/test/commands/credentials_test.rb +++ b/railties/test/commands/credentials_test.rb @@ -303,6 +303,28 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase assert_credentials_paths "config/credentials/production.yml.enc", key_path, environment: "production" end + test "respects config.credentials.content_path when set in config/environments/*.rb" do + content_path = "my_secrets/credentials.yml.enc" + add_to_env_config "production", "config.credentials.content_path = #{content_path.inspect}" + + with_rails_env "production" do + assert_credentials_paths content_path, "config/master.key" + end + + assert_credentials_paths content_path, "config/credentials/production.key", environment: "production" + end + + test "respects config.credentials.key_path when set in config/environments/*.rb" do + key_path = "my_secrets/master.key" + add_to_env_config "production", "config.credentials.key_path = #{key_path.inspect}" + + with_rails_env "production" do + assert_credentials_paths "config/credentials.yml.enc", key_path + end + + assert_credentials_paths "config/credentials/production.yml.enc", key_path, environment: "production" + end + private DEFAULT_CREDENTIALS_PATTERN = /access_key_id: 123\n.*secret_key_base: \h{128}\n/m