/attr_masker

A fork of attr_encrypted, repurposed to handle data masking in test databases

Primary LanguageRubyMIT LicenseMIT

attr_masker

Gem Version Build Status Code Climate Test Coverage Documentation on RubyDoc.info

Mask ActiveRecord/Mongoid data with ease!

Introduction

This gem is intended to mask sensitive data so that production database dumps can be used in staging or test environments.

  • Works with Rails 4.2+ and modern Rubies

  • Supports ActiveRecord and Mongoid models

Usage instructions

Installation

Add attr_masker to your Gemfile:

gem "attr_masker"
# or the HEAD version of the gem
# gem "attr_masker", github: "riboseinc/attr_masker"

Then install the gem:

bundle install

Basic usage

In your models, define attributes which should be masked:

class User
  attr_masker :email, :first_name, :last_name
end

Then, when you want to mask the data, run the db:mask Rake task in some Rails environment other than production, for example:

bundle exec rake db:mask RAILS_ENV=staging
Warning
Data are destructively overwritten. Run rake db:mask with care!

Masking records selectively

You can use :if and :unless options to prevent some records from being altered.

# evaluates given proc for each record, and the record is passed as a proc's
# argument
attr_masker :email :unless => ->(record) { ! record.tester_user? }

# calls #tester_user? method on each record
attr_masker :first_name, :if => :tester_user?

The ActiveRecord’s ::default_scope method has no effect on masking. All table records are updated, provided that :if and :unless filters allow that. For example, if you’re soft-deleting your data (with a gem like Paranoia), records marked as deleted will be masked as well.

Built-in maskers

Attr Masker comes with several built-in maskers.

AttrMasker::Maskers::Simple

Simply replaces any value with the "(redacted)". Only useful for columns containing textual data.

This is a default masker. It is used when :masker option is unspecified.

attr_masker :first_name
attr_masker :last_name, :masker => AttrMasker::Maskers::Simple.new

Would set both first_name and last_name attributes to "(redacted)".

AttrMasker::Maskers::Replacing

Replaces characters with some masker string (single asterisk by default). Can be initialized with options.

Name Default Description

replacement

"*"

Replacement string, can be empty.

alphanum_only

false

When true, only alphanumeric characters are replaced.

rm = AttrMasker::Maskers::Replacing.new(character: "X", alphanum_only: true)
attr_masker :phone, :masker => rm

Would mask "123-456-7890" as "XXX-XXX-XXXX".

Using custom maskers

Apart from built-in maskers, any object which responds to #call can be used, e.g. some lambda or Method instance. For instance, you may want to produce unique values basing on other attributes, to mask selectively, or to use tool like Well Read Faker to generate random replacement values:

require "well_read_faker"

attr_masker :email, masker: ->(model:, **) { "user#{model.id}@example.com" }
attr_masker :phone, masker: ->(value:, **) { "******" + value[-3..-1] }
attr_masker :bio, masker: ->(**) { WellReadFaker.paragraph }

Masker is called with following keyword arguments:

value

Original value of the field which is about to be masked

model

Model instance

attribute_name

Name of the attribute which is about to be masked

masking_options

Hash of options which were passed in #attr_masker call

This list is likely to be extended in future versions, and that will not be considered a breaking change, therefore it is strongly recommended to always use a splat (**) at end of argument list of masker’s #call method.

Configuration file

It is also possible to contain all the maskers configuration in one file. Just place it in config/attr_masker.rb, and it will be loaded from a Rake task after the application is fully loaded. That means you can re-open classes and add masker definitions there, for example:

# config/attr_masker.rb

class User
  attr_masker :first_name, :last_name
end

class Email
  attr_masker :address, ->(model:, **) { "mail#{model.id}@example.com" }
end

Roadmap & TODOs

  • documentation

  • spec tests

  • Make the Rails.env (in which db:mask could be run) configurable

    • maybe by passing ENV vars

  • more masking options!

    • default scrambling algorithms?

    • structured text preserving algorithms

      • e.g., keeping an HTML snippet valid HTML, but with masked inner text

    • structured Object preserving algorithms

      • i.e. generalization of the above HTML scenario

  • I18n of the default "(redacted)" phrase

  • …​

Acknowledgements

attr_encrypted for the initial code structure