Revision 08f6e65b
Added by Joseph Magen almost 11 years ago
.gitignore | ||
---|---|---|
config/email.yaml
|
||
config/database.yml
|
||
config/initializers/local_secret_token.rb
|
||
config/initializers/encryption_key.rb
|
||
Gemfile.lock
|
||
bundler.d/Gemfile.local.rb
|
||
*.sw?
|
app/models/concerns/encryptable.rb | ||
---|---|---|
module Encryptable
|
||
extend ActiveSupport::Concern
|
||
include EncryptionKey if defined?(EncryptionKey)
|
||
# Set encryption key for tests only if case it's not set
|
||
ENCRYPTION_KEY = '25d224dd383e92a7e0c82b8bf7c985e815f34cf5' if Rails.env.test?
|
||
|
||
if const_defined?(:ENCRYPTION_KEY)
|
||
|
||
included do
|
||
ENCRYPTION_PREFIX = "encrypted-"
|
||
before_save :encrypt_setters
|
||
end
|
||
|
||
def encrypt_setters
|
||
self.encryptable_fields.each do |field|
|
||
if send("#{field}_changed?")
|
||
self.send("#{field}=", encrypt_field(read_attribute(field.to_sym)))
|
||
end
|
||
end
|
||
end
|
||
|
||
def matches_prefix?(str)
|
||
ENCRYPTION_PREFIX == str[0..(ENCRYPTION_PREFIX.length - 1)]
|
||
end
|
||
|
||
def puts_and_logs(msg)
|
||
logger.info msg
|
||
puts msg if defined?(Rake)
|
||
end
|
||
|
||
def is_encryptable?(str)
|
||
return true unless matches_prefix?(str)
|
||
puts_and_logs "String starts with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not encrypted again"
|
||
false
|
||
end
|
||
|
||
def is_decryptable?(str)
|
||
return true if matches_prefix?(str)
|
||
puts_and_logs "String does not start with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not decrypted"
|
||
false
|
||
end
|
||
|
||
def encrypt_field(str)
|
||
return str unless is_encryptable?(str)
|
||
encryptor = ActiveSupport::MessageEncryptor.new(ENCRYPTION_KEY)
|
||
begin
|
||
# add prefix to encrypted string
|
||
str = "#{ENCRYPTION_PREFIX}#{encryptor.encrypt_and_sign(str)}"
|
||
puts_and_logs "Successfully encrypted field for #{self.class.name} #{name}"
|
||
rescue
|
||
puts_and_logs "WARNING: Encryption failed for string. Please check that the ENCRYPTION_KEY has not changed."
|
||
end
|
||
str
|
||
end
|
||
|
||
def decrypt_field(str)
|
||
return str unless is_decryptable?(str)
|
||
encryptor = ActiveSupport::MessageEncryptor.new(ENCRYPTION_KEY)
|
||
begin
|
||
# remove prefix before decrypting string
|
||
str_no_prefix = str.gsub(ENCRYPTION_PREFIX, "")
|
||
str = encryptor.decrypt_and_verify(str_no_prefix)
|
||
puts_and_logs "Successfully decrypted field for #{self.class.name} #{name}"
|
||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||
puts_and_logs "WARNING: Decryption failed for string. Please check that the ENCRYPTION_KEY has not changed."
|
||
end
|
||
str
|
||
end
|
||
|
||
module ClassMethods
|
||
|
||
def encrypts(*fields)
|
||
class_attribute :encryptable_fields
|
||
self.encryptable_fields = fields.map(&:to_sym)
|
||
define_getter_in_db(fields.map(&:to_sym))
|
||
define_auto_decrypt_getter(fields.map(&:to_sym))
|
||
end
|
||
|
||
def encrypts?(field)
|
||
encryptable_fields.include?(field.to_sym)
|
||
end
|
||
|
||
def define_getter_in_db(fields)
|
||
fields.each do |field|
|
||
define_method "#{field}_in_db" do
|
||
read_attribute(field.to_sym)
|
||
end
|
||
end
|
||
end
|
||
|
||
def define_auto_decrypt_getter(fields)
|
||
fields.each do |field|
|
||
define_method field do
|
||
decrypt_field(send("#{field}_in_db".to_sym))
|
||
end
|
||
end
|
||
end
|
||
|
||
end
|
||
|
||
else
|
||
included do
|
||
logger.info "ENCRYPTION_KEY is not defined, so encryption is turned off for #{model_name}."
|
||
end
|
||
|
||
module ClassMethods
|
||
def encrypts(*fields)
|
||
end
|
||
end
|
||
end
|
||
end
|
config/initializers/encryption_key.rb.example | ||
---|---|---|
# Be sure to restart your server when you modify this file.
|
||
|
||
# Your encryption key for encrypting and decrypting database fields.
|
||
# If you change this key, all encrypted data will NOT be able to be decrypted by Foreman!
|
||
# Make sure the key is at least 32 bytes such as SecureRandom.hex(20)
|
||
|
||
# You can use `security:generate_encryption_key` to regenerate this file.
|
||
|
||
#module EncryptionKey
|
||
# ENCRYPTION_KEY = ENV['ENCRYPTION_KEY'] || 'uncomment and insert encryption key here'
|
||
#end
|
lib/foreman/util.rb | ||
---|---|---|
def secure_token
|
||
SecureRandom.base64(96).tr('+/=', '-_*')
|
||
end
|
||
|
||
# recommended to make encryption_key at least 32 bytes. Ex. SecureRandom.hex(20)
|
||
def secure_encryption_key
|
||
SecureRandom.hex(20)
|
||
end
|
||
|
||
end
|
||
end
|
lib/tasks/encrypt.rake | ||
---|---|---|
require 'foreman/util'
|
||
|
||
namespace :security do
|
||
desc 'Generate new encryption key'
|
||
task :generate_encryption_key do
|
||
include Foreman::Util
|
||
File.open(Rails.root.join('config', 'initializers', 'encryption_key.rb'), "w") do |fd|
|
||
fd.write("# Be sure to restart your server when you modify this file.
|
||
|
||
# Your encryption key for encrypting and decrypting database fields.
|
||
# If you change this key, all encrypted data will NOT be able to be decrypted by Foreman!
|
||
# Make sure the key is at least 32 bytes such as SecureRandom.hex(20)
|
||
|
||
# You can use `rake security:generate_encryption_key` to regenerate this file.
|
||
|
||
module EncryptionKey
|
||
ENCRYPTION_KEY = ENV['ENCRYPTION_KEY'] || '#{secure_encryption_key}'
|
||
end
|
||
")
|
||
puts "Encryption key generated in file config/initializers/local_encryption_key.rb"
|
||
puts "Restart the server and then run rake db:compute_resources:encrypt"
|
||
end
|
||
end
|
||
end
|
Also available in: Unified diff
fixes #2424 - add Encryptable module and encryption_key generation