sunitparekh / data-anonymization

Want to use production data for testing, data-anonymization can help you.
MIT License
459 stars 92 forks source link

Data::Anonymization

Afraid of using production data due to privacy issues? Data Anonymization is a tool that helps you build anonymized production data dumps which you can use for performance testing, security testing, debugging and development.

Java/Kotlin version

Java/Kotlin version of tool supporting RDBMS databases is available with similar easy to use DSL.


Coverage Status Gem Version

Getting started

Install gem using:

$ gem install data-anonymization

Install required database adapter library for active record:

$ gem install sqlite3

Create ruby program using data-anonymization DSL as following my_dsl.rb:

require 'data-anonymization'

database 'DatabaseName' do
  strategy DataAnon::Strategy::Blacklist  # whitelist (default) or blacklist

  # database config as active record connection hash
  source_db :adapter => 'sqlite3', :database => 'sample-data/chinook-empty.sqlite'

  # User -> table name (case sensitive)
  table 'User' do
    # id, DateOfBirth, FirstName, LastName, UserName, Password -> table column names (case sensitive)
    primary_key 'id' # composite key is also supported
    anonymize 'DateOfBirth','FirstName','LastName' # uses default anonymization based on data types
    anonymize('UserName').using FieldStrategy::StringTemplate.new('user#{row_number}')
    anonymize('Password') { |field| "password" }
  end

  ...

end

Run using:

$ ruby my_dsl.rb

Liked it? please share

Examples

SQLite database

  1. Whitelist
  2. Blacklist

MongoDB

  1. Whitelist
  2. Blacklist

Postgresql database having composite primary key

  1. Whitelist
  2. Blacklist

Changelog

0.8.10 (Mar 20, 2024)

  1. Upgraded to rails 7.1

0.8.7 (Jan 14, 2022)

  1. Upgraded to rails 7.x

0.8.5 (May 28, 2020)

  1. Upgraded to rails 6.x

0.8.1 (Aug 19, 2017)

  1. Multi-threading support added by stanislav-tyutin using Pull Request.
  2. Fixed to work with Ruby 2.4.x, issue with Integer data type

0.8.0 (Oct 31, 2016)

  1. Upgraded to rails 5.x

0.7.4 (Oct 29, 2016)

  1. Continue to work on rails 4.x. Minor changes based on feedback.

0.8.0.rc1 (Sep 5, 2016)

  1. Upgraded to rails 5.0, please report any issue or use case not working.

0.7.3 (Feb 5, 2016)

  1. Fixed issue with batchsize. Thanks to Jan Raasch for sending pull request.

0.7.2 (Sep 26, 2015)

  1. Upgraded MongoDB to latest gem version 2.1.0 and tested with MongoDB 3.x version.
  2. Upgraded gems to latest version
  3. Adding limit functionality - Merge pull request #27 from yanismydj/master

0.7.1 (Jun 13, 2015)

  1. Fixed issues with empty array data for MongoDB
  2. Added feature to skip and continue records during anaonymisation, this is useful to apply different strategies for different types of records.

0.7.0 (Mar 9, 2015)

  1. Removed downcase from field name since it was causing issues with upper case field names. So now for databsae where case matters field name case should be maintained.
  2. Upgraded gems to latest version

0.6.7 (Jan 17, 2015)

  1. Upgraded gems to latest version including activerecord to 4.2. Please try it out and provide feedback.

0.6.6 (Oct 31, 2014)

  1. Upgraded gems to latest version.

0.6.5 (Jun 02, 2014)

  1. Upgraded most of the gems to latest version. major change is rails activerecord gem to latest versions 4.1.1, please provide feedback.

0.6.0 (Dec 09, 2013)

  1. Upgraded rails activerecord gem to latest versions 4.0.2, please provide feedback.

0.5.5 (Dec 4, 2013)

  1. Upgraded gems to latest versions

0.5.2 (Jan 29, 2013)

  1. Fixed issue #17
  2. Upgraded Thor dependency to latest version

0.5.2 (Jan 20, 2013)

  1. Upgraded all gem to latest and greatest including Rails activerecord and activesupport.

0.5.1 (Oct 26, 2012)

  1. Minor fixes release, no major functionality or feature added.

Please see the Github 0.5.1 milestone page for more details on changes/fixes in release 0.5.1

0.5.0 (Sep 28, 2012)

Major changes:

  1. MongoDB support
  2. Command line utility to generate whitelist DSL for RDBMS & MongoDB (reduces pain for writing whitelist dsl)
  3. Added support for reporting fields missing mapping in case of whitelist
  4. Errors reported at the end of process. Job doesn't fail for a single error, it fails it more than 100 records failed during anonymization.

Please see the Github 0.5.0 milestone page for more details on changes/fixes in release 0.5.0

0.3.0 (Sep 4, 2012)

Major changes:

  1. Added support for Parallel table execution
  2. Change in default String strategy from LoremIpsum to RandomString based on end user feedback.
  3. Fixed issue with table column name 'type' as this is default name for STI in activerecord.

Please see the Github 0.3.0 milestone page for more details on changes/fixes in release 0.3.0

0.2.0 (August 16, 2012)

  1. Added the progress bar using 'powerbar' gem. Which also shows the ETA for each table.
  2. Added More strategies
  3. Fixed default anonymization strategies for boolean and integer values
  4. Added support for composite primary key

0.1.2 (August 14, 2012)

  1. First initial release

Roadmap

MVP done. Fix defects and support queries, suggestions, enhancements logged in Github issues :-)

Share feedback

Please use Github issues to share feedback, feature suggestions and report issues.

What is data anonymization?

For almost all projects there is a need for production data dump in order to run performance tests, rehearse production releases and debug production issues. However, getting production data and using it is not feasible due to multiple reasons, primary being privacy concerns for user data. And thus the need for data anonymization. This tool helps you to get anonymized production data dump using either Blacklist or Whitelist strategies.

Read more about data anonymization here

Anonymization Strategies

Blacklist

This approach essentially leaves all fields unchanged with the exception of those specified by the user, which are scrambled/anonymized (hence the name blacklist). For Blacklist create a copy of prod database and chooses the fields to be anonymized like e.g. username, password, email, name, geo location etc. based on user specification. Most of the fields have different rules e.g. password should be set to same value for all users, email needs to be valid.

The problem with this approach is that when new fields are added they will not be anonymized by default. Human error in omitting users personal data could be damaging.

database 'DatabaseName' do
  strategy DataAnon::Strategy::Blacklist
  source_db :adapter => 'sqlite3', :database => 'sample-data/chinook-empty.sqlite'
  ...
end

Whitelist

This approach, by default scrambles/anonymizes all fields except a list of fields which are allowed to copied as is. Hence the name whitelist. By default all data needs to be anonymized. So from production database data is sanitized record by record and inserted as anonymized data into destination database. Source database needs to be readonly. All fields would be anonymized using default anonymization strategy which is based on the datatype, unless a special anonymization strategy is specified. For instance special strategies could be used for emails, passwords, usernames etc. A whitelisted field implies that it's okay to copy the data as is and anonymization isn't required. This way any new field will be anonymized by default and if we need them as is, add it to the whitelist explicitly. This prevents any human error and protects sensitive information.

database 'DatabaseName' do
  strategy DataAnon::Strategy::Whitelist
  source_db :adapter => 'sqlite3', :database => 'sample-data/chinook.sqlite'
  destination_db :adapter => 'sqlite3', :database => 'sample-data/chinook-empty.sqlite'
  ...
end

Read more about blacklist and whitelist here

Tips

  1. In Whitelist approach make source database connection READONLY.
  2. Change default field strategies to avoid using same strategy again and again in your DSL.
  3. To run anonymization in parallel at Table level, provided no FK constraint on tables use DataAnon::Parallel::Table strategy
  4. For large table to load them in batches from table set 'batch_size' and it will use RoR's batch mode processing. Checkout example on how to use batch processing.
  5. Make sure to give proper case for fields and table names.
  6. Use skip and continue to apply different strategies for records.
  7. Use 'limit' to limit the number of rows that will be imported in whitelist
  8. RDBMS databases utilizing schemas can be specified via schema_search_path: source_db { ... schema_search_path: 'public,my_special_schema' }

DSL Generation

We provide a command line tool to generate whitelist scripts for RDBMS and NoSQL databases. The user needs to supply the connection details to the database and a script is generated by analyzing the schema. Below are examples of how to use the tool to generate the scripts for RDBMS and NoSQL datastores

When you install the data-anonymization tool, the datanon command become available on the terminal. If you type datanon --help and execute you should see the below

Tasks:

datanon generate_mongo_dsl -d, --database=DATABASE -h, --host=HOST                        # Generates a base anonymization script(whitelist strategy) for a Mongo DB using the database schema
datanon generate_rdbms_dsl -a, --adapter=ADAPTER -d, --database=DATABASE -h, --host=HOST  # Generates a base anonymization script(whitelist strategy) for a RDBMS database using the database schema
datanon help [TASK]                                                                       # Describe available tasks or one specific task

RDBMS whitelist generation

The gem uses ActiveRecord(AR) abstraction to connect to relational databases. You can generate a whitelist script in seconds for any relational database supported by Active Record. To do so use the following command

datanon generate_rdbms_dsl [options]

The options available are :

  1. adapter(-a) : The activerecord adapter to use to connect to the database (eg. mysql2, postgresql)
  2. host(-h) : DB host name or IP address
  3. database(-d) : The name of the database to generate the whitelist script for
  4. username(-u) : Username for DB authentication
  5. password(-w) : Password for DB authentication
  6. port(-p) : The port the database service is running on. Default port provided by AR will be used if nothing is specififed.

The adapter, host and database options are mandatory. The others are optional.

A few examples of the command is shown below

datanon generate_rdbms_dsl -a mysql2 -h db.host.com -p 3306 -d production_db -u root -w password

datanon generate_rdbms_dsl -a postgresql -h 123.456.7.8 -d production_db

The relevant db gems must be installed so that AR has the adapters required to establish the connection to the databases. The script generates a file named rdbms_whitelist_generated.rb in the same location as the project.

MongoDB whitelist generation

Similar to the the relational databases, a whitelist script for mongo db can be generated by analysing the database structure

datanon generate_mongo_dsl [options]

The options available are :

  1. host(-h) : DB host name or IP address
  2. database(-d) : The name of the database to generate the whitelist script for
  3. username(-u) : Username for DB authentication
  4. password(-w) : Password for DB authentication
  5. port(-p) : The port the database service is running on.
  6. whitelist patterns(-r): A regex expression which can be used to match records in the database to list as whitelisted fields in the generated script.

The host and database options are mandatory. The others are optional.

A few examples of the command is shown below

datanon generate_mongo_dsl -h db.host.com -d production_db -u root -w password

datanon generate_mongo_dsl -h 123.456.7.8 -d production_db

The mongo gem is required in order to install the mongo db drivers. The script generates a file named mongodb_whitelist_generated.rb in the same location as the project.

Running in Parallel

Currently provides capability of running anonymization in parallel at table level provided no FK constraints on tables. It uses Parallel gem provided by Michael Grosser. By default it starts multiple parallel ruby processes processing table one by one.

database 'DellStore' do
  strategy DataAnon::Strategy::Whitelist
  execution_strategy DataAnon::Parallel::Table  # by default sequential table processing
  ...
end

DataAnon::Core::Field

The object that gets passed along with the field strategies.

has following attribute accessor

Field Strategies

Content Name Description
Text LoremIpsum Generates a random Lorep Ipsum String
Text RandomString Generates a random string of equal length
Text StringTemplate Generates a string based on provided template
Text SelectFromList Randomly selects a string from a provided list
Text SelectFromFile Randomly selects a string from a provided file
Text FormattedStringNumber Randomize digits in a string while maintaining the format
Text SelectFromDatabase Selects randomly from the result of a query on a database
Text RandomUrl Anonymizes a URL while mainting the structure
Content Name Description
Number RandomInteger Generates a random integer between provided limits (default 0 to 100)
Number RandomIntegerDelta Generates a random integer within -delta and delta of original integer
Number RandomFloat Generates a random float between provided limits (default 0.0 to 100.0)
Number RandomFloatDelta Generates a random float within -delta and delta of original float
Number RandomBigDecimalDelta Similar to previous but creates a big decimal object
Content Name Description
Address RandomAddress Randomly selects an address from a geojson flat file [Default US address]
City RandomCity Similar to address, picks a random city from a geojson flafile [Default US cities]
Province RandomProvince Similar to address, picks a random city from a geojson flafile [Default US provinces]
Zip code RandomZipcode Similar to address, picks a random zipcode from a geojson flafile [Default US zipcodes]
Phone number RandomPhoneNumber Randomizes a phone number while preserving locale specific fomatting
Content Name Description
DateTime AnonymizeDateTime Anonymizes each field (except year and seconds) within natural range of the field depending on true/false flag provided
Time AnonymizeTime Exactly similar to above except returned object is of type 'Time'
Date AnonymizeDate Anonymizes day and month within natural ranges based on true/false flag
DateTimeDelta DateTimeDelta Shifts data randomly within given range. Default shifts date within 10 days + or - and shifts time within 30 minutes.
TimeDelta TimeDelta Exactly similar to above except returned object is of type 'Time'
DateDelta DateDelta Shifts date randomly within given delta range. Default shits date within 10 days + or -
Content Name Description
Email RandomEmail Generates email randomly using the given HOSTNAME and TLD.
Email GmailTemplate Generates a valid unique gmail address by taking advantage of the gmail + strategy
Email RandomMailinatorEmail Generates random email using mailinator hostname.
Content Name Description
First name RandomFirstName Randomly picks up first name from the predefined list in the file. Default file is part of the gem.
Last name RandomLastName Randomly picks up last name from the predefined list in the file. Default file is part of the gem.
Full Name RandomFullName Generates full name using the RandomFirstName and RandomLastName strategies.
User name RandomUserName Generates random user name of same length as original user name.

Write you own field strategies

field parameter in following code is DataAnon::Core::Field

class MyFieldStrategy

    # method anonymize is what required
    def anonymize field
      # write your code here
    end

end

write your own anonymous field strategies within DSL,

  table 'User' do
    anonymize('Password') { |field| "password" }
    anonymize('email') do |field|
        "test+#{field.row_number}@gmail.com"
    end
  end

Default field strategies

DEFAULT_STRATEGIES = {:string => FieldStrategy::RandomString.new,
                      :fixnum => FieldStrategy::RandomIntegerDelta.new(5),
                      :bignum => FieldStrategy::RandomIntegerDelta.new(5000),
                      :float => FieldStrategy::RandomFloatDelta.new(5.0),
                      :bigdecimal => FieldStrategy::RandomBigDecimalDelta.new(500.0),
                      :datetime => FieldStrategy::DateTimeDelta.new,
                      :time => FieldStrategy::TimeDelta.new,
                      :date => FieldStrategy::DateDelta.new,
                      :trueclass => FieldStrategy::RandomBoolean.new,
                      :falseclass => FieldStrategy::RandomBoolean.new
}

Overriding default field strategies & can be used to provide default strategy for missing data type.

database 'Chinook' do
  ...
  default_field_strategies  :string => FieldStrategy::RandomString.new
  ...
end

Logging

How do I switch off the progress bar?

# add following line in your ruby file
ENV['show_progress'] = 'false'

Logger provides debug level messages including database queries of active record.

DataAnon::Utils::Logging.logger.level = Logger::INFO

Skip and Continue records

Skip is used to skip records during anonymization when condition returns true. This records are ignored, in blacklist it remains as it is in database and in case of whitelist this records will not be copied to destination database.

table 'customers' do
  skip { |index, record| record['age'] < 18 }

  primary_key 'cust_id'
  anonymize('email').using FieldStrategy::StringTemplate.new('test+#{row_number}@gmail.com')
  anonymize 'terms_n_condition', 'age'
end

Continue is exactly opposite of Skip and it continue with anonymization only if given condition returns true. In case of blacklist records are anonymized for matching conditions and for whitelist records are anonymized and copied to new database for matching conditions.

table 'customers' do
  continue { |index, record| record['age'] > 18 }

  primary_key 'cust_id'
  anonymize('email').using FieldStrategy::StringTemplate.new('test+#{row_number}@gmail.com')
  anonymize 'terms_n_condition', 'age'
end

Want to contribute?

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

License

MIT License

Credits