db_obfuscation 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +83 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/TODO +15 -0
- data/bin/console +25 -0
- data/bin/db_obfuscation +121 -0
- data/bin/obfuscation_test +54 -0
- data/cli/db_dump.rb +29 -0
- data/cli/migrator.rb +26 -0
- data/cli/seeder.rb +52 -0
- data/db_obfuscation.gemspec +25 -0
- data/features/bin/dump.feature +21 -0
- data/features/bin/obfuscation.feature +12 -0
- data/features/bin/test_database_tasks.feature +16 -0
- data/features/support.rb +1 -0
- data/lib/db_obfuscation.rb +50 -0
- data/lib/db_obfuscation/batch_formulator.rb +26 -0
- data/lib/db_obfuscation/config.rb +43 -0
- data/lib/db_obfuscation/database.rb +8 -0
- data/lib/db_obfuscation/environment.rb +14 -0
- data/lib/db_obfuscation/filtering.rb +56 -0
- data/lib/db_obfuscation/filtering/column.rb +40 -0
- data/lib/db_obfuscation/filtering/truncation.rb +18 -0
- data/lib/db_obfuscation/obfuscation_strategy.rb +22 -0
- data/lib/db_obfuscation/obfuscator.rb +65 -0
- data/lib/db_obfuscation/query_builder.rb +62 -0
- data/lib/db_obfuscation/truncation.rb +39 -0
- data/lib/db_obfuscation/util/trigger.rb +83 -0
- data/lib/db_obfuscation/version.rb +4 -0
- data/spec/cli/db_dump_spec.rb +33 -0
- data/spec/cli/migrator_spec.rb +59 -0
- data/spec/cli/seeder_spec.rb +33 -0
- data/spec/config/database.yml +5 -0
- data/spec/config/table_strategies/table_1.yml +3 -0
- data/spec/config/table_strategies/table_2.yml +4 -0
- data/spec/config/table_strategies/truncation_table_1.yml +3 -0
- data/spec/config/table_strategies/whitelisted_table_1.yml +3 -0
- data/spec/config/truncation_patterns.yml +2 -0
- data/spec/config/whitelisted_tables.yml +1 -0
- data/spec/db_obfuscation/batch_formulator_spec.rb +36 -0
- data/spec/db_obfuscation/config_spec.rb +60 -0
- data/spec/db_obfuscation/database_spec.rb +10 -0
- data/spec/db_obfuscation/filtering/column_spec.rb +82 -0
- data/spec/db_obfuscation/filtering/truncation_spec.rb +41 -0
- data/spec/db_obfuscation/filtering_spec.rb +39 -0
- data/spec/db_obfuscation/obfuscation_strategy_spec.rb +43 -0
- data/spec/db_obfuscation/obfuscator_spec.rb +150 -0
- data/spec/db_obfuscation/query_builder_spec.rb +259 -0
- data/spec/db_obfuscation/truncation_spec.rb +31 -0
- data/spec/db_obfuscation/util/trigger_spec.rb +126 -0
- data/spec/integration/obfuscation_spec.rb +69 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/test_db_setup/migrations/1_add_table_1.rb +18 -0
- data/spec/test_db_setup/migrations/2_add_table_2.rb +19 -0
- data/spec/test_db_setup/migrations/3_add_truncation_table_1.rb +14 -0
- data/spec/test_db_setup/migrations/4_add_whitelisted_table_1.rb +14 -0
- data/spec/test_db_setup/migrations/5_add_table_without_any_user_defined_obfuscation_strategies.rb +18 -0
- data/spec/test_db_setup/migrations/6_add_table_without_any_obfuscatable_columns.rb +15 -0
- data/spec/test_db_setup/migrations/7_add_audit_truncation_table.rb +13 -0
- data/spec/test_db_setup/seeds/audit_truncation_table.yml +7 -0
- data/spec/test_db_setup/seeds/table_1.yml +13 -0
- data/spec/test_db_setup/seeds/table_2.yml +15 -0
- data/spec/test_db_setup/seeds/table_without_any_obfuscatable_columns.yml +7 -0
- data/spec/test_db_setup/seeds/table_without_any_user_defined_obfuscation_strategies.yml +13 -0
- data/spec/test_db_setup/seeds/truncation_table_1.yml +9 -0
- data/spec/test_db_setup/seeds/whitelisted_table_1.yml +9 -0
- metadata +159 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'db_obfuscation/database'
|
2
|
+
|
3
|
+
module DbObfuscation
|
4
|
+
module ObfuscationStrategy
|
5
|
+
def self.strategy(column_name)
|
6
|
+
case column_name.to_sym
|
7
|
+
when /ssn/
|
8
|
+
:ssn_strategy
|
9
|
+
when /email/
|
10
|
+
:email_strategy
|
11
|
+
when :first_name
|
12
|
+
:first_name_strategy
|
13
|
+
when :last_name
|
14
|
+
:last_name_strategy
|
15
|
+
when :name
|
16
|
+
:name_strategy
|
17
|
+
else
|
18
|
+
:default_strategy
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'ffaker'
|
2
|
+
module DbObfuscation
|
3
|
+
module Obfuscator
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def obfuscate(strategy_name)
|
7
|
+
case strategy_name
|
8
|
+
when :address_strategy
|
9
|
+
address
|
10
|
+
when :date_strategy
|
11
|
+
rand(31..240)
|
12
|
+
when :default_strategy
|
13
|
+
FFaker::Lorem.word
|
14
|
+
when :driving_license_strategy
|
15
|
+
FFaker::Identification.drivers_license
|
16
|
+
when :email_strategy
|
17
|
+
FFaker::Internet.safe_email
|
18
|
+
when :first_name_strategy
|
19
|
+
FFaker::Name.first_name
|
20
|
+
when :gender_strategy
|
21
|
+
'Unknown'
|
22
|
+
when :last_name_strategy
|
23
|
+
FFaker::Name.last_name
|
24
|
+
when :medicaid_id_strategy
|
25
|
+
FFaker.numerify('############')
|
26
|
+
when :name_strategy
|
27
|
+
FFaker::Name.name
|
28
|
+
when :nil_strategy
|
29
|
+
nil
|
30
|
+
when :paragraph_strategy
|
31
|
+
FFaker::Lorem.paragraph
|
32
|
+
when :phone_number_strategy
|
33
|
+
FFaker::PhoneNumber.phone_number
|
34
|
+
when :school_strategy
|
35
|
+
FFaker::Education.school
|
36
|
+
when :sentence_strategy
|
37
|
+
FFaker::Lorem.sentence
|
38
|
+
when :ssn_strategy
|
39
|
+
FFaker::SSN.ssn
|
40
|
+
when :suffix_strategy
|
41
|
+
FFaker::Name.suffix
|
42
|
+
when :unique_name_strategy
|
43
|
+
full_name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def address
|
49
|
+
<<-ADDRESS.gsub(/\s{2,}/,' ').strip
|
50
|
+
#{FFaker::Address.street_address}
|
51
|
+
#{FFaker::Address.city}
|
52
|
+
#{FFaker::AddressUS.state_abbr}
|
53
|
+
#{FFaker::AddressUS.zip_code}
|
54
|
+
ADDRESS
|
55
|
+
end
|
56
|
+
|
57
|
+
def full_name
|
58
|
+
<<-name.gsub(/\s{2,}/,' ').strip
|
59
|
+
#{FFaker::Name.first_name}
|
60
|
+
#{FFaker.letterify('??????????')}
|
61
|
+
#{FFaker::Name.last_name}
|
62
|
+
name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module DbObfuscation
|
2
|
+
module QueryBuilder
|
3
|
+
class << self
|
4
|
+
def multi_update_sql(table, columns, date_columns=[])
|
5
|
+
<<-SQL.gsub(/\s{2,}/,' ').strip
|
6
|
+
UPDATE "#{table}"
|
7
|
+
#{case_sql(columns, date_columns)}
|
8
|
+
#{where_sql(columns)}
|
9
|
+
SQL
|
10
|
+
end
|
11
|
+
|
12
|
+
def where_sql(columns)
|
13
|
+
column_name = columns.first.keys.last
|
14
|
+
values = columns.map { |column| "'#{column.values.last}'" }.join(',')
|
15
|
+
%Q{WHERE "#{column_name}" IN (#{values})}
|
16
|
+
end
|
17
|
+
|
18
|
+
def case_sql(columns, date_columns)
|
19
|
+
column_names = columns.first.keys
|
20
|
+
constraint_name = column_names.last
|
21
|
+
column_names -= [constraint_name]
|
22
|
+
column_names.each_with_object('SET ') do |name, sql_query|
|
23
|
+
sql_query << %Q("#{name}" = CASE)
|
24
|
+
columns.each_with_object(sql_query) do |column, query|
|
25
|
+
type = (date_columns.include?(name) ? :date : :string)
|
26
|
+
query << when_sql(name, column, constraint_name, type)
|
27
|
+
end
|
28
|
+
sql_query << %Q( ELSE "#{name}" END, )
|
29
|
+
end.chomp(", ")
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_sql(table, id, columns, date_columns=[])
|
33
|
+
"UPDATE #{table} #{column_sql(columns, date_columns)} WHERE id=#{id};"
|
34
|
+
end
|
35
|
+
|
36
|
+
def column_sql(columns, date_columns)
|
37
|
+
columns.each_with_object("SET ") do |column, query|
|
38
|
+
column_name, column_value = column.first, column.last
|
39
|
+
if date_columns.include?(column.first)
|
40
|
+
query << "#{column_name}=#{column_name} + INTERVAL '#{column_value} DAYS', "
|
41
|
+
else
|
42
|
+
query << "#{column_name}=#{column_value}, "
|
43
|
+
end
|
44
|
+
end.chomp(", ")
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def when_sql(column, table_row, constraint, type)
|
49
|
+
when_clause = <<-SQL.gsub(/\s{2,}/,' ').chomp
|
50
|
+
WHEN "#{constraint}" = '#{table_row[constraint]}'
|
51
|
+
AND "#{column}" IS NOT NULL
|
52
|
+
SQL
|
53
|
+
if type == :date
|
54
|
+
when_clause << %Q( THEN "#{column}" + INTERVAL '#{table_row[column]} DAYS')
|
55
|
+
else
|
56
|
+
when_clause << %Q( AND "#{column}" != '' THEN #{table_row[column]})
|
57
|
+
end
|
58
|
+
when_clause
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'db_obfuscation/database'
|
2
|
+
require 'db_obfuscation/filtering/truncation'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
class Truncation
|
6
|
+
|
7
|
+
|
8
|
+
def self.truncate
|
9
|
+
new.send(:truncate)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.tables
|
13
|
+
new.send(:tables)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def truncate
|
19
|
+
tables.each do |table|
|
20
|
+
begin
|
21
|
+
DbObfuscation.logging.info "Truncating #{table}"
|
22
|
+
DB[table].truncate
|
23
|
+
rescue => e
|
24
|
+
DbObfuscation.logging.error "#{table} encountered #{e.message}"
|
25
|
+
DbObfuscation.logging.error "#{e.backtrace}"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def tables
|
32
|
+
Filtering::Truncation.matches_patterns(DB.tables, patterns)
|
33
|
+
end
|
34
|
+
|
35
|
+
def patterns
|
36
|
+
Config.truncation_patterns
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'db_obfuscation/database'
|
2
|
+
|
3
|
+
module DbObfuscation
|
4
|
+
module Util
|
5
|
+
module Trigger
|
6
|
+
extend self
|
7
|
+
def tables
|
8
|
+
query = <<-sql
|
9
|
+
SELECT tables.relname as table_name
|
10
|
+
FROM pg_trigger triggers, pg_class tables
|
11
|
+
WHERE triggers.tgrelid = tables.oid
|
12
|
+
AND tables.relname !~ '^pg_'
|
13
|
+
AND triggers.tgname LIKE '#{prefix}%'
|
14
|
+
sql
|
15
|
+
DbObfuscation::DB[query].map(:table_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def drop(table)
|
19
|
+
table == :all ? drop_triggers : drop_trigger(table)
|
20
|
+
end
|
21
|
+
|
22
|
+
def disable(table)
|
23
|
+
table == :all ? disable_triggers : disable_trigger(table)
|
24
|
+
end
|
25
|
+
|
26
|
+
def enable(table)
|
27
|
+
table == :all ? enable_triggers : enable_trigger(table)
|
28
|
+
end
|
29
|
+
|
30
|
+
def exists?(table)
|
31
|
+
tables.include?(table.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def disable_triggers
|
36
|
+
tables.each do |table|
|
37
|
+
disable_trigger(table)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def disable_trigger(table)
|
42
|
+
DbObfuscation::DB.run <<-sql
|
43
|
+
ALTER TABLE #{table}
|
44
|
+
DISABLE TRIGGER #{name(table)}
|
45
|
+
sql
|
46
|
+
end
|
47
|
+
|
48
|
+
def enable_triggers
|
49
|
+
tables.each do |table|
|
50
|
+
enable_trigger(table)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def enable_trigger(table)
|
55
|
+
DbObfuscation::DB.run <<-sql
|
56
|
+
ALTER TABLE #{table}
|
57
|
+
ENABLE TRIGGER #{name(table)}
|
58
|
+
sql
|
59
|
+
end
|
60
|
+
|
61
|
+
def drop_triggers
|
62
|
+
tables.each do |table|
|
63
|
+
drop_trigger(table)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def drop_trigger(table)
|
68
|
+
DbObfuscation::DB.run <<-sql
|
69
|
+
DROP TRIGGER #{name(table)}
|
70
|
+
ON #{table}
|
71
|
+
sql
|
72
|
+
end
|
73
|
+
|
74
|
+
def prefix
|
75
|
+
'audit_'
|
76
|
+
end
|
77
|
+
|
78
|
+
def name(table)
|
79
|
+
"#{prefix}#{table}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'db_obfuscation/environment'
|
2
|
+
require 'db_dump'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
module Cli
|
6
|
+
describe DbDump do
|
7
|
+
|
8
|
+
it 'dump a db into a dump file' do
|
9
|
+
filename = 'db.yml'
|
10
|
+
db_config = {
|
11
|
+
'username' => 'fake_username',
|
12
|
+
'password' => 'fake_password',
|
13
|
+
'database' => 'fake_database',
|
14
|
+
'host' => 'fake_host'
|
15
|
+
}
|
16
|
+
|
17
|
+
allow(YAML).to receive(:load_file).with(filename).
|
18
|
+
and_return(db_config)
|
19
|
+
|
20
|
+
dump_name = 'dumped_database'
|
21
|
+
|
22
|
+
dump_cmd = <<-dump_cmd.gsub(/\s{2,}/,' ').strip
|
23
|
+
PGPASSWORD=fake_password pg_dump -h fake_host -U fake_username -w
|
24
|
+
-Fc -f dumped_database fake_database
|
25
|
+
dump_cmd
|
26
|
+
|
27
|
+
expect(Kernel).to receive(:system).with(dump_cmd)
|
28
|
+
described_class.dump(filename, dump_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'migrator'
|
3
|
+
require 'db_obfuscation/database'
|
4
|
+
|
5
|
+
module DbObfuscation
|
6
|
+
module Cli
|
7
|
+
describe Migrator do
|
8
|
+
describe '.migrate' do
|
9
|
+
before do
|
10
|
+
system("dropdb -e obfuscation_test")
|
11
|
+
system("createdb -e obfuscation_test")
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'creates table in the test database' do
|
15
|
+
expect(DbObfuscation::DB.tables).to eq []
|
16
|
+
|
17
|
+
db_yml = File.expand_path('../../config/database.yml',
|
18
|
+
__FILE__)
|
19
|
+
migration_path = File.expand_path('../../test_db_setup/migrations',
|
20
|
+
__FILE__)
|
21
|
+
|
22
|
+
described_class.migrate(db_yml, migration_path)
|
23
|
+
|
24
|
+
expect(DbObfuscation::DB.tables).to include :table_1
|
25
|
+
expect(DbObfuscation::DB.tables).to include :table_2
|
26
|
+
expect(DbObfuscation::DB.tables).to include :truncation_table_1
|
27
|
+
expect(DbObfuscation::DB.tables).to include :whitelisted_table_1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '.down_migrate' do
|
32
|
+
before do
|
33
|
+
system("dropdb -e obfuscation_test")
|
34
|
+
system("createdb -e obfuscation_test")
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'creates table in the test database' do
|
38
|
+
db_yml = File.expand_path('../../config/database.yml',
|
39
|
+
__FILE__)
|
40
|
+
migration_path = File.expand_path('../../test_db_setup/migrations',
|
41
|
+
__FILE__)
|
42
|
+
|
43
|
+
described_class.migrate(db_yml, migration_path)
|
44
|
+
expect(DbObfuscation::DB.tables).to include :table_1
|
45
|
+
expect(DbObfuscation::DB.tables).to include :table_2
|
46
|
+
expect(DbObfuscation::DB.tables).to include :truncation_table_1
|
47
|
+
expect(DbObfuscation::DB.tables).to include :whitelisted_table_1
|
48
|
+
|
49
|
+
described_class.down_migrate(db_yml, migration_path)
|
50
|
+
|
51
|
+
expect(DbObfuscation::DB.tables).to eq([:schema_info])
|
52
|
+
|
53
|
+
#rescue operation to keep the test db in a clean state
|
54
|
+
described_class.migrate(db_yml, migration_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'seeder'
|
3
|
+
require 'db_obfuscation/database'
|
4
|
+
require 'migrator'
|
5
|
+
|
6
|
+
module DbObfuscation
|
7
|
+
module Cli
|
8
|
+
describe Seeder do
|
9
|
+
let(:db_yml) {
|
10
|
+
File.expand_path('../../config/database.yml', __FILE__)
|
11
|
+
}
|
12
|
+
let(:migrations_path) {
|
13
|
+
File.expand_path('../../test_db_setup/migrations', __FILE__)
|
14
|
+
}
|
15
|
+
let(:seeds_path) {
|
16
|
+
File.expand_path('../../test_db_setup/seeds', __FILE__)
|
17
|
+
}
|
18
|
+
|
19
|
+
describe '.seed' do
|
20
|
+
before do
|
21
|
+
Migrator.down_migrate(db_yml, migrations_path)
|
22
|
+
Migrator.migrate(db_yml, migrations_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'seeds the database with seed data' do
|
26
|
+
expect(DbObfuscation::DB[:table_1].all).to eq []
|
27
|
+
Seeder.seed(db_yml, seeds_path)
|
28
|
+
expect(DbObfuscation::DB[:table_1].all).to_not eq([])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
- whitelisted_table_1
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/batch_formulator'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
describe BatchFormulator do
|
6
|
+
|
7
|
+
describe '.batch_for' do
|
8
|
+
it 'returns a batch of obfuscated row values for a set of ids' do
|
9
|
+
config = {
|
10
|
+
field_1: :default_strategy,
|
11
|
+
field_2: :default_strategy,
|
12
|
+
date_field: :default_strategy
|
13
|
+
}
|
14
|
+
|
15
|
+
ids = [1,2]
|
16
|
+
|
17
|
+
expected_batch = [
|
18
|
+
{ field_1: "'obfuscated_value'",
|
19
|
+
field_2: "'obfuscated_value'",
|
20
|
+
date_field: "'obfuscated_value'",
|
21
|
+
id: 1 },
|
22
|
+
{ field_1: "'obfuscated_value'",
|
23
|
+
field_2: "'obfuscated_value'",
|
24
|
+
date_field: "'obfuscated_value'",
|
25
|
+
id: 2 }
|
26
|
+
]
|
27
|
+
|
28
|
+
allow(DbObfuscation::Obfuscator).to receive(:obfuscate)
|
29
|
+
.and_return('obfuscated_value')
|
30
|
+
obfuscated_batch = described_class.batch_for(config, ids)
|
31
|
+
|
32
|
+
expect(obfuscated_batch).to eq expected_batch
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|