module Vault::EncryptedModel
Public Instance Methods
__vault_generate_context(context)
click to toggle source
Generates an Vault
Transit encryption context for use on derived keys.
# File lib/vault/encrypted_model.rb, line 380 def __vault_generate_context(context) case context when String context when Symbol send(context) when Proc context.call(self) else nil end end
__vault_initialize_attributes!()
click to toggle source
Decrypt all the attributes from Vault
. @return [true]
# File lib/vault/encrypted_model.rb, line 230 def __vault_initialize_attributes! if self.class.vault_lazy_decrypt @__vault_loaded = false return end __vault_load_attributes! end
__vault_load_attribute!(attribute, options)
click to toggle source
Decrypt and load a single attribute from Vault
.
# File lib/vault/encrypted_model.rb, line 254 def __vault_load_attribute!(attribute, options) key = options[:key] path = options[:path] serializer = options[:serializer] column = options[:encrypted_column] context = options[:context] default = options[:default] transform = options[:transform_secret] # Load the ciphertext ciphertext = read_attribute(column) # If the user provided a value for the attribute, do not try to load # it from Vault if attributes[attribute.to_s] return end # Generate context if needed generated_context = __vault_generate_context(context) if transform # If this is a secret encrypted with FPE, we do not need to decrypt with vault # This prevents a double encryption via standard vault encryption and FPE. # FPE is decrypted later as part of the serializer plaintext = ciphertext else # Load the plaintext value plaintext = Vault::Rails.decrypt( path, key, ciphertext, context: generated_context ) end # Deserialize the plaintext value, if a serializer exists if serializer plaintext = serializer.decode(plaintext) end # Set to default if needed if default && plaintext == nil plaintext = default end # Write the virtual attribute with the plaintext value instance_variable_set("@#{attribute}", plaintext) @attributes.write_from_database attribute.to_s, plaintext end
__vault_load_attributes!(attribute_to_read = nil)
click to toggle source
# File lib/vault/encrypted_model.rb, line 239 def __vault_load_attributes!(attribute_to_read = nil) self.class.__vault_attributes.each do |attribute, options| # skip loading certain keys in one of two cases: # 1- the attribute has already been loaded # 2- the single decrypt option is set AND this is not the attribute we're requesting to decrypt next if instance_variable_get("@#{attribute}") || (self.class.vault_single_decrypt && attribute_to_read != attribute) self.__vault_load_attribute!(attribute, options) end @__vault_loaded = self.class.__vault_attributes.all? { |attribute, __| instance_variable_defined?("@#{attribute}") } return true end
__vault_persist_attribute!(attribute, options)
click to toggle source
Encrypt a single attribute using Vault
and persist back onto the encrypted attribute value.
# File lib/vault/encrypted_model.rb, line 327 def __vault_persist_attribute!(attribute, options) key = options[:key] path = options[:path] serializer = options[:serializer] column = options[:encrypted_column] context = options[:context] transform = options[:transform_secret] # Only persist changed attributes to minimize requests - this helps # minimize the number of requests to Vault. if ActiveRecord.gem_version >= Gem::Version.new("6.0") return unless previous_changes.include?(attribute) elsif ActiveRecord.gem_version >= Gem::Version.new("5.2") return unless previous_changes_include?(attribute) elsif ActiveRecord.gem_version >= Gem::Version.new("5.1") return unless saved_change_to_attribute?(attribute.to_s) else return unless attribute_changed?(attribute) end # Get the current value of the plaintext attribute plaintext = attributes[attribute.to_s] # Apply the serialize to the plaintext value, if one exists if serializer plaintext = serializer.encode(plaintext) end # Generate context if needed generated_context = __vault_generate_context(context) if transform # If this is a secret encrypted with FPE, we should not encrypt it in vault # This prevents a double encryption via standard vault encryption and FPE. # FPE was performed earlier as part of the serialization process. ciphertext = plaintext else # Generate the ciphertext and store it back as an attribute ciphertext = Vault::Rails.encrypt( path, key, plaintext, context: generated_context ) end # Write the attribute back, so that we don't have to reload the record # to get the ciphertext write_attribute(column, ciphertext) # Return the updated column so we can save { column => ciphertext } end
__vault_persist_attributes!()
click to toggle source
Encrypt all the attributes using Vault
and set the encrypted values back on this model. @return [true]
# File lib/vault/encrypted_model.rb, line 306 def __vault_persist_attributes! changes = {} self.class.__vault_attributes.each do |attribute, options| if c = self.__vault_persist_attribute!(attribute, options) changes.merge!(c) end end # If there are any changes to the model, update them all at once, # skipping any callbacks and validation. This is okay, because we are # already in a transaction due to the callback. if !changes.empty? self.update_columns(changes) end return true end
reload(*)
click to toggle source
Override the reload method to reload the Vault
attributes. This will ensure that we always have the most recent data from Vault
when we reload a record from the database.
Calls superclass method
# File lib/vault/encrypted_model.rb, line 396 def reload(*) super.tap do # Unset all the instance variables to force the new data to be pulled # from Vault self.class.__vault_attributes.each do |attribute, _| self.instance_variable_set("@#{attribute}", nil) @attributes.write_from_database attribute.to_s, nil end self.__vault_initialize_attributes! end end