Skip to content

Commit

Permalink
Fixes #13870 - Add the ability to encrypt specific settings values
Browse files Browse the repository at this point in the history
  • Loading branch information
amirfefer committed Mar 17, 2016
1 parent 1680e94 commit ef83285
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 80 deletions.
75 changes: 75 additions & 0 deletions app/models/concerns/encrypt_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
module EncryptField
ENCRYPTION_PREFIX = "encrypted-"
def matches_prefix?(str)
ENCRYPTION_PREFIX == str.to_s[0..(ENCRYPTION_PREFIX.length - 1)]
end

def encryption_key
return ENV['ENCRYPTION_KEY'] if ENV['ENCRYPTION_KEY'].present?
return EncryptionKey::ENCRYPTION_KEY if defined? EncryptionKey::ENCRYPTION_KEY
nil
end

def is_encryptable?(str)
if !encryption_key.present?
puts_and_logs "Missing ENCRYPTION_KEY configuration, so #{self.class.name} #{name} could not be encrypted", Logger::WARN
false
elsif str.blank?
puts_and_logs "String is blank', so #{self.class.name} #{name} was not encrypted", Logger::DEBUG
false
elsif matches_prefix?(str)
puts_and_logs "String starts with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not encrypted again", Logger::DEBUG
false
else
true
end
end

def is_decryptable?(str)
if !matches_prefix?(str)
puts_and_logs "String does not start with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not decrypted", Logger::DEBUG
false
elsif !encryption_key.present?
puts_and_logs "Missing ENCRYPTION_KEY configuration, so #{self.class.name} #{name} could not be decrypted", Logger::WARN
false
else
true
end
end

def encrypt_field(str)
return str unless is_encryptable?(str)
encryptor = ActiveSupport::MessageEncryptor.new(encryption_key)
begin
# add prefix to encrypted string
str_encrypted = "#{ENCRYPTION_PREFIX}#{encryptor.encrypt_and_sign(str)}"
puts_and_logs "Successfully encrypted field for #{self.class.name} #{name}"
str = str_encrypted
rescue
puts_and_logs "WARNING: Encryption failed for string. Please check that the ENCRYPTION_KEY has not changed.", Logger::WARN
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_decrypted = encryptor.decrypt_and_verify(str_no_prefix)
puts_and_logs "Successfully decrypted field for #{self.class.name} #{name}"
str = str_decrypted
rescue ActiveSupport::MessageVerifier::InvalidSignature
puts_and_logs "WARNING: Decryption failed for string. Please check that the ENCRYPTION_KEY has not changed.", Logger::WARN
end
str
end

private

def puts_and_logs(msg, level = Logger::INFO)
logger.add level, msg
puts msg if Foreman.in_rake? && !Rails.env.test? && level >= Logger::INFO
end
end
77 changes: 1 addition & 76 deletions app/models/concerns/encryptable.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module Encryptable
extend ActiveSupport::Concern

ENCRYPTION_PREFIX = "encrypted-"

include EncryptField
included do
before_save :encrypt_setters
end
Expand Down Expand Up @@ -45,77 +43,4 @@ def encrypt_setters
end
end
end

def matches_prefix?(str)
ENCRYPTION_PREFIX == str.to_s[0..(ENCRYPTION_PREFIX.length - 1)]
end

def encryption_key
return ENV['ENCRYPTION_KEY'] if ENV['ENCRYPTION_KEY'].present?
return EncryptionKey::ENCRYPTION_KEY if defined? EncryptionKey::ENCRYPTION_KEY
nil
end

def is_encryptable?(str)
if !encryption_key.present?
puts_and_logs "Missing ENCRYPTION_KEY configuration, so #{self.class.name} #{name} could not be encrypted", Logger::WARN
false
elsif str.blank?
puts_and_logs "String is blank', so #{self.class.name} #{name} was not encrypted", Logger::DEBUG
false
elsif matches_prefix?(str)
puts_and_logs "String starts with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not encrypted again", Logger::DEBUG
false
else
true
end
end

def is_decryptable?(str)
if !matches_prefix?(str)
puts_and_logs "String does not start with the prefix '#{ENCRYPTION_PREFIX}', so #{self.class.name} #{name} was not decrypted", Logger::DEBUG
false
elsif !encryption_key.present?
puts_and_logs "Missing ENCRYPTION_KEY configuration, so #{self.class.name} #{name} could not be decrypted", Logger::WARN
false
else
true
end
end

def encrypt_field(str)
return str unless is_encryptable?(str)
encryptor = ActiveSupport::MessageEncryptor.new(encryption_key)
begin
# add prefix to encrypted string
str_encrypted = "#{ENCRYPTION_PREFIX}#{encryptor.encrypt_and_sign(str)}"
puts_and_logs "Successfully encrypted field for #{self.class.name} #{name}"
str = str_encrypted
rescue
puts_and_logs "WARNING: Encryption failed for string. Please check that the ENCRYPTION_KEY has not changed.", Logger::WARN
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_decrypted = encryptor.decrypt_and_verify(str_no_prefix)
puts_and_logs "Successfully decrypted field for #{self.class.name} #{name}"
str = str_decrypted
rescue ActiveSupport::MessageVerifier::InvalidSignature
puts_and_logs "WARNING: Decryption failed for string. Please check that the ENCRYPTION_KEY has not changed.", Logger::WARN
end
str
end

private

def puts_and_logs(msg, level = Logger::INFO)
logger.add level, msg
puts msg if Foreman.in_rake? && !Rails.env.test? && level >= Logger::INFO
end
end
11 changes: 7 additions & 4 deletions app/models/setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class Setting < ActiveRecord::Base
extend FriendlyId
friendly_id :name
include ActiveModel::Validations
include EncryptField
self.inheritance_column = 'category'

TYPES= %w{ integer boolean hash array string }
Expand All @@ -19,7 +20,7 @@ def validate(record)
end
end

attr_accessible :name, :value, :description, :category, :settings_type, :default, :full_name
attr_accessible :name, :value, :description, :category, :settings_type, :default, :full_name, :encrypted

validates_lengths_from_database
# audit the changes to this model
Expand Down Expand Up @@ -100,11 +101,13 @@ def self.method_missing(method, *args)

def value=(v)
v = v.to_yaml unless v.nil?
v = encrypt_field(v) if defined? encrypted and encrypted
write_attribute :value, v
end

def value
v = read_attribute(:value)
v = decrypt_field(v) if defined? encrypted and encrypted
v.nil? ? default : YAML.load(v)
end
alias_method :value_before_type_cast, :value
Expand Down Expand Up @@ -196,7 +199,7 @@ def self.convert_array_to_regexp(array)

def self.create_existing(s, opts)
bypass_readonly(s) do
attrs = column_check([:default, :description, :full_name])
attrs = column_check([:default, :description, :full_name, :encrypted])
to_update = Hash[opts.select { |k,v| attrs.include? k }]
to_update.merge!(:value => SETTINGS[opts[:name].to_sym]) if SETTINGS.key?(opts[:name].to_sym)
s.update_attributes(to_update)
Expand Down Expand Up @@ -270,8 +273,8 @@ def self.load_defaults
true
end

def self.set(name, description, default, full_name = nil, value = nil)
{:name => name, :value => value, :description => description, :default => default, :full_name => full_name}
def self.set(name, description, default, full_name = nil, value = nil, encrypted = nil)
{:name => name, :value => value, :description => description, :default => default, :full_name => full_name, :encrypted => encrypted }
end

def self.model_name
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20160315161936_add_encrypted_to_settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddEncryptedToSettings < ActiveRecord::Migration
def change
add_column :settings, :encrypted, :boolean
end
end
6 changes: 6 additions & 0 deletions test/fixtures/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,9 @@ attributes59:
category: Setting::Puppet
default: "false"
description: 'All hosts will show a configuration status even when a Puppet smart proxy is not assigned'
attributes60:
name: password
category: Setting::Puppet
default: nil
description: 'Encrypted password'
encrypted: true
30 changes: 30 additions & 0 deletions test/unit/setting_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,36 @@ def test_should_not_find_a_value_if_doesnt_exists
assert_nil Setting["no_such_thing"]
end

test "encrypted value is saved encrypted when created" do
setting = Setting.create(:name => "foo", :value => 5, :default => 5, :description => "test foo", :encrypted => true)
setting.expects(:encryption_key).at_least_once.returns('25d224dd383e92a7e0c82b8bf7c985e815f34cf5')
setting.value = "123456"
as_admin do
assert setting.save
end
assert setting.read_attribute(:value).include? 'encrypted'
refute_equal setting.read_attribute(:value), "123456"
end

test "when reading encrypted value, the value is decrypted" do
setting = settings(:attributes60)
setting.value = "123456"
as_admin do
assert setting.save
end
assert_equal setting.value, "123456"
end

test "encrypted value is saved encrypted when updated" do
setting = settings(:attributes60)
setting.value = "123456"
as_admin do
assert setting.save
end
assert setting.read_attribute(:value).include? 'encrypted'
refute_equal setting.read_attribute(:value), "123456"
end

def test_should_provide_default_if_no_value_defined
assert Setting.create(:name => "foo", :default => 5, :description => "test foo")
assert_equal 5, Setting["foo"]
Expand Down

0 comments on commit ef83285

Please sign in to comment.