Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap active_record_encryption.configuration on an after initialize #48520

Conversation

duduribeiro
Copy link
Contributor

@duduribeiro duduribeiro commented Jun 19, 2023

Motivation / Background

This commit changes the active_record_encryption.configuration to be set on an after initialize block. After PR #44873, we can configure now hash_digest_class that will be used on ActiveRecord::Encryption. But the docs said that we could change it via an initializer by setting
Rails.application.config.active_record.encryption.hash_digest_class. This wasn't working because the railtie
"active_record_encryption.configuration" was being set before the initializer was loaded. This changes this behave to configure the active_record encryption after the initialize was loaded.

Additional information

I found this while debugging #48204 and I noticed that the PR #44873 added an option to configure the hash_class used to encrypt the attribute by setting the ActiveRecord::Encryption.config.hash_digest_class on the initializer. But while debugging my issue, I found that the code to use this hash_digest_class wasn't using the different hash_digest_class that I set and I noticed that the ActiveRecord::Encryption was only configured only at the active_record railtie before the custom initializers I have.

    initializer "active_record_encryption.configuration" do |app|
      ActiveRecord::Encryption.configure \
         primary_key: app.credentials.dig(:active_record_encryption, :primary_key),
         deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key),
         key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt),
         **config.active_record.encryption

and my new_framework_defaults_7_1 was loaded after that.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Changes that are unrelated should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

This commit changes the active_record_encryption.configuration
to be set on an after initialize block. After PR rails#44873, we can
configure now hash_digest_class that will be used on
ActiveRecord::Encryption. But the docs said that we could change
it via an initializer by setting
Rails.application.config.active_record.encryption.hash_digest_class.
This wasn't working because the railtie
"active_record_encryption.configuration" was being set before the
initializer was loaded. This changes this behave to configure
the active_record encryption after the initialize was loaded.
if ActiveRecord::Encryption.config.add_to_filter_parameters
ActiveRecord::Encryption.install_auto_filtered_parameters_hook(app)
# Filtered params
if ActiveRecord::Encryption.config.add_to_filter_parameters
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorgemanrubia the current failure https://buildkite.com/rails/rails/builds/97281#0188d44a-def3-4a39-9f69-1cfd4ab8d4cd/1089-1099 is happening because now ActiveRecord::Encryption.install_auto_filtered_parameters_hook is invoked after the initialize, but the code for install_auto_filtered_parameters_hook is a hook that relies on ActiveRecord::Encryption.on_encrypted_attribute_declared and the attributes were declared before the config.after_initialize do |app| block is run.

is there an easy way that I can get all encrypted classes / attributes so I can run this block:

            filter = [("#{klass.model_name.element}" if klass.name), encrypted_attribute_name.to_s].compact.join(".")
            unless excluded_from_filter_parameters?(filter)
              app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
              klass.filter_attributes += [encrypted_attribute_name]
            end

on the after initialize by Getting all encrypted attributes / classes instead to rely on the hook?

My first thought was to keep the hook, change install_auto_filtered_parameters_hook to instead of filtering attribute to only add that the klass / attribute was encrypted and then on an after_initialize I really modify the filtering. But I want to see your thoughts on this.

Also another options was to run

        ActiveRecord::Encryption.configure \
            primary_key: app.credentials.dig(:active_record_encryption, :primary_key),
            deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key),
            key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt),
            **config.active_record.encryption
            
        # Filtered params
        if ActiveRecord::Encryption.config.add_to_filter_parameters
          ActiveRecord::Encryption.install_auto_filtered_parameters_hook(app)
        end

both on the non after_initialize / after_initialize block. But this looks weird for me 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jorgemanrubia I made a POC of this suggestion:

Here is a version using a hash, which required me two loops to setup the attributes and this which I made with an array to avoid this two loops.

what are your thoughts?

Copy link
Contributor

@jorgemanrubia jorgemanrubia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments but the patch looks good @duduribeiro, thanks!

@@ -9,6 +9,7 @@ module Configurable
included do
mattr_reader :config, default: Config.new
mattr_accessor :encrypted_attribute_declaration_listeners
mattr_reader :encrypted_attributes, default: Concurrent::Array.new
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the hash version better you refer at 95b3e01.

Also, I'd consider renaming :encrypted_attributes to entrypted_attributes_by_class to clarify a little bit what's contained there.

@@ -1,3 +1,14 @@
* Wrap active_record_encryption.configuration on an after initialize
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the first line here, instead of describing the internals of the change itself, I'd try to clarify what's being fixed here.

Example: Support configuring the digest class in Active Record encryption via initializer.

@@ -4331,6 +4331,16 @@ def new(app); self; end
assert_match(/Cannot assign to `load_defaults`, it is a configuration method/, error.message)
end

test "allows initializer to set active_record_encryption.configuration" do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call adding a test here 👏

@jorgemanrubia
Copy link
Contributor

Also, the PR description could contain fix #48204 to get that one closed when this gets merged.

@duduribeiro
Copy link
Contributor Author

the failing test https://buildkite.com/rails/rails/builds/97291 is the one that is also failing on main branch https://buildkite.com/rails/rails/builds/97274

@duduribeiro
Copy link
Contributor Author

duduribeiro commented Jun 19, 2023

Also, the PR description could contain fix #48204 to get that one closed when this gets merged.

@jorgemanrubia I will not do this because #48204 is something that is still happening with me and I will investigate why. I added a reproduction on a rails 7.0.5 to 7.1.0 app here #48204 (comment) and will try to investigate more but we can merge this PR as it is related with another thing.

This PR fixes the problem where the initializer couldn't set the hash_digest_class and the bug is related with maybe another problem. I will investigate more.

thanks


ActiveSupport.on_load(:active_record) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActiveSupport.on_load(:active_record) should only run during eager loading phase, that is after_initialize, so this change isn't necessary. Why do you need to do this?

Why don't you move the ActiveRecord::Encryption.configure call to inside ActiveSupport.on_load(:active_record) do? I don't think we need to use after_initialize here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @rafaelfranca

thanks for the tip! We are moving this PR to #48530, which also fixes the initialization problem.

about setting ActiveRecord::Encryption.configure on ActiveSupport.on_load(:active_record), I found that on on my app my ActiveRecord was loaded before my initializers got executed. See this and this for the @jorgemanrubia thoughts.

It it possible that some initializer in this app is loading an Active Record model? That will trigger the AR encryption initialization logic. application.rb runs before any initializer runs, so the fact that it works makes me suspect that something is triggering that initialization before the new defaults' initializer is running.

After some debugging I found that when I use https://github.com/basecamp/name_of_person gem it loads the active_record before running any initializer. See this

So I think @jorgemanrubia will rely on after_initialize too on his new PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a bug in your application and in the name_of_person gem that should be fixed. Rails should not change its architecture because of bugs in other libraries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants