Validate that proper keys are configured when declaring attributes

This enables to disable deterministic encryption by just not setting
deterministic_key.
This commit is contained in:
Jorge Manrubia 2021-04-01 15:25:16 +02:00
parent 575a2c6ce0
commit e24fb5524a
4 changed files with 65 additions and 7 deletions

@ -24,7 +24,7 @@ def initialize(key_provider: nil, key: nil, deterministic: nil, downcase: nil, i
@previous_schemes = Array.wrap(previous_schemes)
@context_properties = context_properties
validate!
validate_config!
end
def ignore_case?
@ -45,7 +45,10 @@ def fixed?
end
def key_provider
@key_provider ||= @key_provider_param || build_key_provider
@key_provider ||= begin
validate_keys!
@key_provider_param || build_key_provider
end
end
def merge(other_scheme)
@ -66,11 +69,24 @@ def with_context(&block)
end
private
def validate!
def validate_config!
raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
end
def validate_keys!
validate_credential :key_derivation_salt
validate_credential :primary_key, "needs to be configured to use non-deterministic encryption" unless @deterministic
validate_credential :deterministic_key, "needs to be configured to use deterministic encryption" if @deterministic
end
def validate_credential(key, error_message = "is not configured")
unless ActiveRecord::Encryption.config.public_send(key).present?
raise Errors::Configuration, "#{key} #{error_message}. Please configure it via credential"\
"active_record_encryption.#{key} or by setting config.active_record.encryption.#{key}"
end
end
def build_key_provider
return DerivedSecretKeyProvider.new(@key) if @key.present?

@ -141,8 +141,8 @@ class ActiveRecord::EncryptionTestCase < ActiveRecord::TestCase
include ActiveRecord::Encryption::EncryptionHelpers, ActiveRecord::Encryption::PerformanceHelpers
# , PerformanceHelpers
ENCRYPTION_ATTRIBUTES_TO_RESET = %i[ primary_key deterministic_key store_key_references key_derivation_salt support_unencrypted_data
encrypt_fixtures ]
ENCRYPTION_ATTRIBUTES_TO_RESET = %i[ primary_key deterministic_key key_derivation_salt store_key_references
key_derivation_salt support_unencrypted_data encrypt_fixtures ]
setup do
ENCRYPTION_ATTRIBUTES_TO_RESET.each do |property|

@ -4,7 +4,7 @@
require "models/book"
class ActiveRecord::Encryption::SchemeTest < ActiveRecord::EncryptionTestCase
test "validate config options when declaring encrypted attributes" do
test "validates config options when declaring encrypted attributes" do
assert_invalid_declaration deterministic: false, ignore_case: true
assert_invalid_declaration key: "1234", key_provider: ActiveRecord::Encryption::DerivedSecretKeyProvider.new("my secret")
@ -13,6 +13,38 @@ class ActiveRecord::Encryption::SchemeTest < ActiveRecord::EncryptionTestCase
assert_valid_declaration key_provider: ActiveRecord::Encryption::DerivedSecretKeyProvider.new("my secret")
end
test "validates primary_key is set for non deterministic encryption" do
ActiveRecord::Encryption.config.primary_key = nil
assert_raise ActiveRecord::Encryption::Errors::Configuration do
declare_and_use_class
end
assert_nothing_raised do
declare_and_use_class deterministic: true
end
end
test "validates deterministic_key is set for non deterministic encryption" do
ActiveRecord::Encryption.config.deterministic_key = nil
assert_raise ActiveRecord::Encryption::Errors::Configuration do
declare_and_use_class deterministic: true
end
assert_nothing_raised do
declare_and_use_class
end
end
test "validates key_derivation_salt is set" do
ActiveRecord::Encryption.config.key_derivation_salt = nil
assert_raise ActiveRecord::Encryption::Errors::Configuration do
declare_and_use_class
end
end
private
def assert_invalid_declaration(**options)
assert_raises ActiveRecord::Encryption::Errors::Configuration do
@ -21,11 +53,19 @@ def assert_invalid_declaration(**options)
end
def assert_valid_declaration(**options)
assert_nothing_raised do
assert_nothing_raised do
declare_encrypts_with(options)
end
end
def declare_and_use_class(**options)
encrypted_book_class = Class.new(Book) do
encrypts :name, **options
end
encrypted_book_class.create! name: "Some name"
end
def declare_encrypts_with(options)
Class.new(Book) do
encrypts :name, **options

@ -80,6 +80,8 @@ The recommendation is using the default (non deterministic) unless you need to q
NOTE: In non-deterministic mode, encryption is done using AES-GCM with a 256-bits key and a random initialization vector. In deterministic mode, it uses AES-GCM too but the initialization vector is generated as a HMAC-SHA-256 digest of the key and contents to encrypt.
NOTE: You can disable deterministic encryption just by not configuring a `deterministic_key`.
## Features
### Action Text