hashicorp / vault-rails

A Rails plugin for easily integrating Vault secrets
Mozilla Public License 2.0
337 stars 55 forks source link

Encryption not working with store attributes #138

Open joesiewert opened 10 months ago

joesiewert commented 10 months ago

When trying to use store_accessor on models, the following is happening during encryption. This issue came up while attempting to upgrade to Rails 6.1. It is currently working on Rails 6.0.6.1 for us. šŸ¤”

Expected behavior

A store attribute can be encrypted. Stored values can be read and written with accessors.

Actual behavior

When attempting to use a store accessor, an error is raised:

Failures:

1) Vault::Rails with default options encrypts store attributes
Failure/Error: person = Person.create!(eye_color: "blue", hair_color: "brown")

NoMethodError:
  undefined method `accessor' for #<ActiveModel::Type::Value:0x0000563c30ebdd50>
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/store.rb:217:in `store_accessor_for'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/store.rb:212:in `write_store_attribute'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/store.rb:136:in `block (3 levels) in store_accessor'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activemodel-6.1.7.6/lib/active_model/attribute_assignment.rb:49:in `public_send'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activemodel-6.1.7.6/lib/active_model/attribute_assignment.rb:49:in `_assign_attribute'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/attribute_assignment.rb:21:in `block in _assign_attributes'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/attribute_assignment.rb:13:in `each'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/attribute_assignment.rb:13:in `_assign_attributes'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activemodel-6.1.7.6/lib/active_model/attribute_assignment.rb:34:in `assign_attributes'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/core.rb:525:in `initialize'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/inheritance.rb:72:in `new'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/inheritance.rb:72:in `new'
# /home/cs/cache/bundler/ruby/2.7.0/gems/activerecord-6.1.7.6/lib/active_record/persistence.rb:54:in `create!'
# ./spec/integration/rails_spec.rb:19:in `block (3 levels) in <top (required)>'

Reproduction

Failing test branch at https://github.com/codeship/vault-rails/tree/rails-6.1

Possibly also related to https://github.com/rails/rails/issues/43012

In looking at the store_accessor_for method, with Vault enabled, it returns:

https://github.com/rails/rails/blob/v6.1.7.6/activerecord/lib/active_record/store.rb#L216-L218

pry(#<Person>)> type_for_attribute(store_attribute).accessor
NoMethodError: undefined method `accessor' for #<ActiveModel::Type::Value:0x0000561514633c40>
from (pry):1:in `store_accessor_for'

System configuration

Rails version: 6.1.7.6 Ruby version: 2.7.8 vault-rails gem version: 0.9.0

joesiewert commented 4 months ago

Looking more at the store_accessor_for and type_for_attribute methods:

https://github.com/rails/rails/blob/v6.1.7.7/activerecord/lib/active_record/store.rb#L216-L218

def store_accessor_for(store_attribute)
  type_for_attribute(store_attribute).accessor
end

In our currently working code on Rails 6.0.6.1 I get these back:

pp type_for_attribute(store_attribute)

#<ActiveModel::Type::Value:0x000055792c0d5ac0
 @limit=nil,
 @precision=nil,
 @scale=nil>
=> #<ActiveModel::Type::Value:0x000055792c0d5ac0 @limit=nil, @precision=nil, @scale=nil>

pp type_for_attribute(store_attribute).accessor

ActiveRecord::Store::IndifferentHashAccessor
=> ActiveRecord::Store::IndifferentHashAccessor

Then in Rails 6.1 (6.1.7.7) the same call:

pp type_for_attribute(store_attribute).accessor

NoMethodError: undefined method `accessor' for #<ActiveModel::Type::Value:0x000055792c0d5ac0>
from (pry):3:in `store_accessor_for'

I can force the store_accessor_for method to return ActiveRecord::Store::IndifferentHashAccessor and my example spec here will pass. It's unclear to me what changed between Rails 6.0 and 6.1 that is triggering this change. I've poured over changelogs and issue reports on the Rails repo but have not been able to find anything (or more likely, I've looked at the right issue but lack the full understanding to tie it back to this problem šŸ˜†).