Skip to content

Commit f8af42d

Browse files
committed
Init
1 parent c711274 commit f8af42d

8 files changed

+229
-0
lines changed

.dockerignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/data/

Dockerfile

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM ruby:2.6
2+
3+
ENV LANG C.UTF-8
4+
5+
WORKDIR /masking
6+
7+
ADD Gemfile /masking/Gemfile
8+
ADD Gemfile.lock /masking/Gemfile.lock
9+
10+
RUN bundle install
11+
12+
COPY . .

Gemfile

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source 'https://rubygems.org'
2+
3+
gem 'ffaker'
4+
gem 'mongo'
5+
gem 'irb'
6+
gem 'thor'

Gemfile.lock

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
bson (4.4.2)
5+
ffaker (2.10.0)
6+
irb (1.0.0)
7+
mongo (2.7.0)
8+
bson (>= 4.4.2, < 5.0.0)
9+
thor (0.20.3)
10+
11+
PLATFORMS
12+
ruby
13+
14+
DEPENDENCIES
15+
ffaker
16+
irb
17+
mongo
18+
thor
19+
20+
BUNDLED WITH
21+
1.17.1

README.md

+34
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,36 @@
11
# Mongodb-data-masking
22
Masking your data in mongodb
3+
4+
# Usage
5+
```
6+
bundle
7+
ruby masker mask mask.yml
8+
```
9+
10+
# Example mask.yml
11+
```yaml
12+
version: 1
13+
db_url: mongodb://mongodb:27017/development
14+
models:
15+
- name: users
16+
condition:
17+
email:
18+
"$not": !ruby/regexp '/@basicinc\.jp$/'
19+
fields:
20+
email: FFaker::Internet.safe_email
21+
- name: users
22+
fields:
23+
reset_password_token: String.new
24+
confirmation_token: String.new
25+
- name: sitesconta
26+
fields:
27+
title: FFaker::NameJA.name
28+
description: FFaker::LoremJA.sentence
29+
domain: FFaker::Internet.domain_name
30+
external_service: :external_services
31+
- name: external_services
32+
fields:
33+
_type: "'ExternalService'"
34+
facebook: nil
35+
google: nil
36+
```

docker-compose.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: '3'
2+
services:
3+
mongodb:
4+
image: mongo:3.4.2
5+
volumes:
6+
- mongo-data:/data/db
7+
- ./data:/tmp/data
8+
9+
app:
10+
build: ./
11+
links:
12+
- mongodb
13+
volumes:
14+
- ./lib:/masking/lib
15+
stdin_open: true
16+
tty: true
17+
18+
volumes:
19+
mongo-data:

lib/masker.rb

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
require 'yaml'
2+
require 'ffaker'
3+
require 'mongo'
4+
5+
class Masker
6+
def initialize(config = nil)
7+
configure config unless config.nil?
8+
end
9+
10+
def db
11+
@db ||= Mongo::Client.new(@config['db_url'] || 'mongodb://mongodb:27017/development')
12+
end
13+
14+
def mask(config = nil)
15+
configure config unless config.nil?
16+
raise 'Please provide mask' if @config.nil?
17+
18+
track_time do
19+
@config['models'].each do |model|
20+
mask_document model
21+
end
22+
puts 'Done!' unless @config['silent']
23+
end
24+
end
25+
26+
def configure(config)
27+
@config = config.is_a?(String) ? load_from_yaml(config) : config
28+
end
29+
30+
private
31+
32+
def track_time
33+
start_at = Time.now
34+
yield
35+
finish_at = Time.now
36+
puts "Elapsed time: #{format_time_diff(start_at, finish_at)}" unless @config['silent']
37+
end
38+
39+
def format_time_diff(start_at, finish_at)
40+
output = Time.at(finish_at - start_at).strftime '%H hours %M minutes %S seconds'
41+
output.gsub(/^0+ hours /, '').gsub(/^0+ minutes /, '')
42+
end
43+
44+
def mask_document(model)
45+
scope = prepair_scope model
46+
47+
if model['delete']
48+
delete_documents scope, model
49+
else
50+
mask_each_document scope, model
51+
end
52+
rescue StandardError => e
53+
puts "\nCan't mask #{model['name']}" unless @config['silent']
54+
raise e
55+
ensure
56+
puts '' unless @config['silent']
57+
end
58+
59+
def delete_documents(scope, model)
60+
puts "Deleting #{model['name']}"
61+
scope.delete_many
62+
end
63+
64+
def mask_each_document(scope, model)
65+
total = scope.count()
66+
67+
scope.each_with_index do |document, index|
68+
print "Masking #{model['name']} (#{index + 1}/#{total})\r" unless @config['silent']
69+
mask = create_mask model['fields']
70+
71+
document.update(mask)
72+
end
73+
end
74+
75+
def prepair_scope(model)
76+
scope = db[model['name']]
77+
scope.find(model['condition'] || {})
78+
end
79+
80+
def parse_condition condition
81+
return nil if condition.nil?
82+
83+
condition.each do |op, value|
84+
if value.is_a?(String) && value.match?(/^BSON::ObjectId('[A-Za-z0-9]+')$/)
85+
condition[op] = eval(value)
86+
elsif value.is_a?(Hash)
87+
condition[op] = parse_condition value
88+
end
89+
end
90+
condition
91+
end
92+
93+
def create_mask(fields)
94+
mask = {}
95+
fields.each do |field, value|
96+
if value.is_a?(Symbol)
97+
sub_mask = create_mask @config['models'].find{|model| model['name'] == value.to_s}['fields']
98+
mask[field] = sub_mask
99+
next
100+
end
101+
102+
mask[field] = evalute_field_value(value)
103+
end
104+
mask
105+
end
106+
107+
def apply_mask(document, mask)
108+
mask.reject do |field, _value|
109+
document[field].nil?
110+
end
111+
end
112+
113+
def evalute_field_value(value)
114+
eval(value)
115+
rescue StandardError
116+
raise "Can't eval `#{value}`"
117+
end
118+
119+
def load_from_yaml(config_path)
120+
YAML.load_file config_path
121+
end
122+
end

masker.rb

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require './lib/masker'
2+
require 'thor'
3+
4+
class Masker::Cli < Thor
5+
include Thor::Actions
6+
7+
desc 'mask MASKER_FILE', 'Mask database'
8+
def mask(masker_file)
9+
masker = Masker.new(masker_file)
10+
masker.mask
11+
end
12+
end
13+
14+
Masker::Cli.start

0 commit comments

Comments
 (0)