From 900bfd94a9c3c45484d88aa69071b7a52c5b04b4 Mon Sep 17 00:00:00 2001 From: schneems Date: Fri, 14 Aug 2015 11:31:33 -0500 Subject: Prevent destructive action on production database This PR introduces a key/value type store to Active Record that can be used for storing internal values. It is an alternative implementation to #21237 cc @sgrif @matthewd. It is possible to run your tests against your production database by accident right now. While infrequently, but as an anecdotal data point, Heroku receives a non-trivial number of requests for a database restore due to this happening. In these cases the loss can be large. To prevent against running tests against production we can store the "environment" version that was used when migrating the database in a new internal table. Before executing tests we can see if the database is a listed in `protected_environments` and abort. There is a manual escape valve to force this check from happening with environment variable `DISABLE_DATABASE_ENVIRONMENT_CHECK=1`. --- activerecord/lib/active_record/tasks/database_tasks.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'activerecord/lib/active_record/tasks') diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index c0c29a618c..6318a0725d 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -42,6 +42,13 @@ module ActiveRecord LOCAL_HOSTS = ['127.0.0.1', 'localhost'] + def check_protected_environments! + if !ENV['DISABLE_DATABASE_internal_metadata'] && ActiveRecord::Migrator.protected_environment? + raise ActiveRecord::ProtectedEnvironmentError.new(ActiveRecord::Migrator.last_stored_environment) + end + rescue ActiveRecord::NoDatabaseError + end + def register_task(pattern, task) @tasks ||= {} @tasks[pattern] = task @@ -204,6 +211,8 @@ module ActiveRecord else raise ArgumentError, "unknown format #{format.inspect}" end + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetata.store("environment" => ActiveRecord::ConnectionHandling::DEFAULT_ENV.call) end def load_schema_for(*args) -- cgit v1.2.3 From a76c4233a9ee9ffbf413c4b8353e73e8ffbeb3a5 Mon Sep 17 00:00:00 2001 From: schneems Date: Thu, 7 Jan 2016 17:40:38 -0600 Subject: Add EnvironmentMismatchError Raise an error when a destructive action is made on a database where the current environment is different from the environment stored in the database. --- activerecord/lib/active_record/tasks/database_tasks.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/tasks') diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 6318a0725d..92f6f44de9 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -43,8 +43,17 @@ module ActiveRecord LOCAL_HOSTS = ['127.0.0.1', 'localhost'] def check_protected_environments! - if !ENV['DISABLE_DATABASE_internal_metadata'] && ActiveRecord::Migrator.protected_environment? - raise ActiveRecord::ProtectedEnvironmentError.new(ActiveRecord::Migrator.last_stored_environment) + unless ENV['DISABLE_DATABASE_internal_metadata'] + current = ActiveRecord::Migrator.current_environment + stored = ActiveRecord::Migrator.last_stored_environment + + if ActiveRecord::Migrator.protected_environment? + raise ActiveRecord::ProtectedEnvironmentError.new(stored) + end + + if current != stored + raise EnvironmentMismatchError.new(current: current, stored: stored) + end end rescue ActiveRecord::NoDatabaseError end -- cgit v1.2.3 From de2cb20117af68cef4126d8998cc63e178c58187 Mon Sep 17 00:00:00 2001 From: schneems Date: Fri, 8 Jan 2016 09:27:25 -0600 Subject: Use hash like syntax for InternalMetadata Discussion: https://github.com/rails/rails/pull/22967#discussion_r49137035 --- activerecord/lib/active_record/tasks/database_tasks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/tasks') diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 92f6f44de9..d51f38c93f 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -221,7 +221,7 @@ module ActiveRecord raise ArgumentError, "unknown format #{format.inspect}" end ActiveRecord::InternalMetadata.create_table - ActiveRecord::InternalMetata.store("environment" => ActiveRecord::ConnectionHandling::DEFAULT_ENV.call) + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment end def load_schema_for(*args) -- cgit v1.2.3 From d70c68d76abcbc24ef0e56b7a7b580d0013255dd Mon Sep 17 00:00:00 2001 From: schneems Date: Fri, 8 Jan 2016 11:53:25 -0600 Subject: Fixing tests and re-locating error checking. --- activerecord/lib/active_record/tasks/database_tasks.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/tasks') diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index d51f38c93f..7b8f27699a 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -51,8 +51,8 @@ module ActiveRecord raise ActiveRecord::ProtectedEnvironmentError.new(stored) end - if current != stored - raise EnvironmentMismatchError.new(current: current, stored: stored) + if stored && stored != current + raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored) end end rescue ActiveRecord::NoDatabaseError -- cgit v1.2.3