strike 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +6 -1
- data/Rakefile +5 -1
- data/TODO.md +0 -1
- data/VERSION +1 -1
- data/lib/strike.rb +95 -21
- data/lib/strike/dumper.rb +123 -0
- data/lib/strike/interpreter.rb +2 -2
- data/lib/strike/obfuscator.rb +31 -0
- data/spec/assets/dump.sql +53 -0
- data/spec/assets/dump_profile.rb +3 -0
- data/spec/bin/strike_spec.rb +21 -0
- data/spec/lib/strike/dumper_spec.rb +94 -0
- data/spec/lib/strike/interpreter_spec.rb +60 -0
- data/spec/lib/strike/obfuscator_spec.rb +33 -0
- data/spec/lib/strike/table_spec.rb +44 -0
- data/{test → spec}/minitest_helper.rb +0 -0
- data/strike.gemspec +2 -5
- metadata +22 -59
- data/lib/strike/agent.rb +0 -106
- data/test/lib/strike/interpreter_test.rb +0 -44
- data/test/lib/strike/table_test.rb +0 -35
data/README.md
CHANGED
@@ -24,6 +24,12 @@ To generate a new dump, use the following command:
|
|
24
24
|
|
25
25
|
$ strike dump mysql://root@localhost/db_production --profile=tables.rb > obfuscated_dump.sql
|
26
26
|
|
27
|
+
To obfuscate an existing sql dump, use the following command:
|
28
|
+
|
29
|
+
$ cat original_dump.sql | strike obfuscate --profile=tables.rb > obfuscated_dump.sql
|
30
|
+
|
31
|
+
It is very important to generate the mysql dump with the `-c` option.
|
32
|
+
|
27
33
|
This command dumps the `database_url` following the tables defined in the `profile`
|
28
34
|
file (defaults to `Strikefile`). The default dump output is STDOUT.
|
29
35
|
|
@@ -69,7 +75,6 @@ end
|
|
69
75
|
|
70
76
|
* `mysqldump`: to create the dump to manipulate.
|
71
77
|
* [my_obfuscate][my_obfuscate]: the core of this utility.
|
72
|
-
* [Sequel][sequel]: extracts the info for the non defined tables.
|
73
78
|
* [Thor][thor]: cli utilities.
|
74
79
|
|
75
80
|
## Notes
|
data/Rakefile
CHANGED
data/TODO.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/strike.rb
CHANGED
@@ -4,16 +4,29 @@ require 'thor'
|
|
4
4
|
|
5
5
|
class Strike < Thor
|
6
6
|
require 'strike/interpreter'
|
7
|
-
require 'strike/
|
7
|
+
require 'strike/obfuscator'
|
8
|
+
require 'strike/dumper'
|
8
9
|
|
9
10
|
include Thor::Actions
|
10
11
|
|
12
|
+
class_option :profile,
|
13
|
+
aliases: '-p',
|
14
|
+
type: :string,
|
15
|
+
default: 'Strikefile',
|
16
|
+
required: false,
|
17
|
+
desc: 'Profile with the table definitions.'
|
18
|
+
class_option :output,
|
19
|
+
aliases: '-o',
|
20
|
+
type: :string,
|
21
|
+
required: false,
|
22
|
+
desc: 'Output file. If none is given, outputs to STDOUT.'
|
23
|
+
|
11
24
|
desc 'version', 'Show version'
|
12
25
|
def version
|
13
26
|
$stdout.puts "v#{IO.read(File.expand_path('../../VERSION', __FILE__))}"
|
14
27
|
end
|
15
28
|
|
16
|
-
desc 'dump <database_url>', 'Dump the <database_url> to
|
29
|
+
desc 'dump <database_url>', 'Dump the obfuscated <database_url> to --output.'
|
17
30
|
long_desc <<-DESC
|
18
31
|
Dump the <database_url> following the table definitions defined in the <profile>
|
19
32
|
(defaults to `Strikefile`). The default dump output is STDOUT.
|
@@ -40,34 +53,95 @@ class Strike < Thor
|
|
40
53
|
\x5\t t.email :email
|
41
54
|
\x5\tend
|
42
55
|
DESC
|
43
|
-
method_option :profile,
|
44
|
-
aliases: '-p',
|
45
|
-
type: :string,
|
46
|
-
default: 'Strikefile',
|
47
|
-
required: true,
|
48
|
-
desc: 'Profile with the table definitions.'
|
49
|
-
method_option :output,
|
50
|
-
aliases: '-o',
|
51
|
-
type: :string,
|
52
|
-
required: false,
|
53
|
-
desc: 'Output file. If none is given, outputs to STDOUT.'
|
54
56
|
def dump(database_url)
|
55
|
-
|
57
|
+
with_profile do |profile|
|
58
|
+
with_output do |output|
|
59
|
+
Dumper.new.call(self, database_url) do |dump_file|
|
60
|
+
_obfuscate(profile, dump_file, output)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
desc 'obfuscate', 'Obfuscate a mysqldump (with -c option) from --input to --output.'
|
67
|
+
long_desc <<-DESC
|
68
|
+
Obfuscate the database dump following the table definitions defined in the <profile>
|
69
|
+
(defaults to `Strikefile`). The default obfuscate output is STDOUT and the input
|
70
|
+
is STDIN.
|
71
|
+
|
72
|
+
IMPORTANT! The mysqldump must has been generated with the `-c` option.
|
56
73
|
|
57
|
-
|
58
|
-
|
59
|
-
|
74
|
+
Usage example:
|
75
|
+
|
76
|
+
$ strike obfuscate < dump.sql > obfuscated-dump.sql
|
77
|
+
|
78
|
+
$ cat dump.sql | strike obfuscate > obfuscated-dump.sql --profile=tables.rb
|
79
|
+
|
80
|
+
$ strike obfuscate --input=dump.sql --output=obfuscated-dump.sql
|
81
|
+
|
82
|
+
The tables are defined with a DSL, which is a wrapper
|
83
|
+
arround the obfuscation types defined in the MyObfuscate gem.
|
84
|
+
|
85
|
+
Example:
|
86
|
+
\x5\t# tables.rb
|
87
|
+
\x5\ttable :users do |t|
|
88
|
+
\x5\t # t.column_name :obfuscation_type
|
89
|
+
\x5\t t.name :first_name
|
90
|
+
\x5\t t.email :email
|
91
|
+
\x5\tend
|
92
|
+
DESC
|
93
|
+
method_option :input,
|
94
|
+
aliases: '-i',
|
95
|
+
type: :string,
|
96
|
+
required: false,
|
97
|
+
desc: 'Input file. If none is given, is read from STDIN.'
|
98
|
+
def obfuscate
|
99
|
+
with_profile do |profile|
|
100
|
+
with_input do |input|
|
101
|
+
with_output do |output|
|
102
|
+
_obfuscate(profile, input, output)
|
103
|
+
end
|
104
|
+
end
|
60
105
|
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def _obfuscate(profile, input, output)
|
111
|
+
tables = Interpreter.new.parse(profile.read)
|
112
|
+
Obfuscator.new.call(tables, input, output)
|
113
|
+
end
|
114
|
+
|
115
|
+
def with_input
|
116
|
+
input = options[:input] ? File.open(options[:input]) : $stdin
|
117
|
+
|
118
|
+
yield input
|
119
|
+
ensure
|
120
|
+
input.close if input != $stdin
|
121
|
+
end
|
122
|
+
|
123
|
+
def with_output
|
124
|
+
output = if options[:output]
|
125
|
+
modes = File::CREAT|File::TRUNC|File::RDWR
|
126
|
+
File.new(options[:output], modes, 0644)
|
127
|
+
else
|
128
|
+
$stdout
|
129
|
+
end
|
130
|
+
|
131
|
+
yield output
|
132
|
+
ensure
|
133
|
+
output.close if output != $stdout
|
134
|
+
end
|
135
|
+
|
136
|
+
def with_profile
|
137
|
+
file = options[:profile]
|
61
138
|
|
62
139
|
if file && File.exist?(file)
|
63
140
|
File.open(file) do |profile|
|
64
|
-
|
65
|
-
Agent.new.call(self, database_url, tables, output || $stdout)
|
141
|
+
yield profile
|
66
142
|
end
|
67
143
|
else
|
68
144
|
$stderr.puts "Profile Error: No such file #{file}"
|
69
145
|
end
|
70
|
-
ensure
|
71
|
-
output.close if output
|
72
146
|
end
|
73
147
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
class Strike
|
7
|
+
class Dumper
|
8
|
+
def initialize(config = {})
|
9
|
+
@dumpfile_source = config[:dumpfile_source]
|
10
|
+
end
|
11
|
+
|
12
|
+
# Dumps the data from the given database to a tmp file.
|
13
|
+
#
|
14
|
+
# @param [#run] cli the cli program that responds to `#run`.
|
15
|
+
# @param [String] database_url the connection info. @see `#parse_url`.
|
16
|
+
# @param [Proc] optional block in which the tmp file will be used.
|
17
|
+
#
|
18
|
+
# @return [nil]
|
19
|
+
def call(cli, database_url)
|
20
|
+
tempfile do |file|
|
21
|
+
begin
|
22
|
+
dump_data(cli, parse_url(database_url), file)
|
23
|
+
yield(file) if block_given?
|
24
|
+
ensure
|
25
|
+
file.unlink
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Converts a database_url to Hash with all the db data.
|
31
|
+
#
|
32
|
+
# Example:
|
33
|
+
# parse_url('mysql://user:pass@localhost:100/test_db')
|
34
|
+
#
|
35
|
+
# @param [String] database_url the connection info.
|
36
|
+
#
|
37
|
+
# @return [Hash] the database configuration with the following fields.
|
38
|
+
# {
|
39
|
+
# db_type: String || nil,
|
40
|
+
# host: String || nil,
|
41
|
+
# port: String || nil,
|
42
|
+
# user: String || nil,
|
43
|
+
# password: String || nil,
|
44
|
+
# database: String || nil,
|
45
|
+
# }
|
46
|
+
def parse_url(database_url)
|
47
|
+
uri = URI.parse(database_url)
|
48
|
+
|
49
|
+
{
|
50
|
+
db_type: uri.scheme.gsub(/^mysql2/, 'mysql'),
|
51
|
+
host: uri.host,
|
52
|
+
port: uri.port.to_s,
|
53
|
+
user: uri.user,
|
54
|
+
password: uri.password,
|
55
|
+
database: uri.path.gsub(/^\//, ''),
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create a tmp file
|
60
|
+
#
|
61
|
+
# @param [Proc] yields the file.
|
62
|
+
#
|
63
|
+
# @return [nil, Tempfile]
|
64
|
+
def tempfile
|
65
|
+
tmp = dumpfile_source.call(['original_dump', 'sql'])
|
66
|
+
block_given? ? yield(tmp) : tmp
|
67
|
+
end
|
68
|
+
protected :tempfile
|
69
|
+
|
70
|
+
# Tmp file generator
|
71
|
+
#
|
72
|
+
# @return [Proc] a lambda that generates the file.
|
73
|
+
def dumpfile_source
|
74
|
+
@dumpfile_source ||= Tempfile.public_method(:new)
|
75
|
+
end
|
76
|
+
protected :dumpfile_source
|
77
|
+
|
78
|
+
# Dump the data from the database configuration and
|
79
|
+
# outputs it to the given file.
|
80
|
+
#
|
81
|
+
# @param [#run] cli the cli program that responds to `#run`.
|
82
|
+
# @param [Hash] db_config database configuration from `#parse_url`.
|
83
|
+
# @param [Tempfile, IO, File] file the file to write the dump.
|
84
|
+
#
|
85
|
+
# @return [nil]
|
86
|
+
def dump_data(cli, db_config, file)
|
87
|
+
dump_options = %w(-c
|
88
|
+
--add-drop-table
|
89
|
+
--add-locks
|
90
|
+
--single-transaction
|
91
|
+
--set-charset
|
92
|
+
--create-options
|
93
|
+
--disable-keys
|
94
|
+
--quick).join(' ')
|
95
|
+
dump_options << " -u #{db_config[:user]}" if db_config[:user]
|
96
|
+
dump_options << " -h #{db_config[:host]}" if db_config[:host]
|
97
|
+
if db_config[:port] && !db_config[:port].empty?
|
98
|
+
dump_options << " -P #{db_config[:port]}"
|
99
|
+
end
|
100
|
+
dump_options << " -p#{db_config[:password]}" if db_config[:password]
|
101
|
+
dump_options << " #{db_config[:database]}"
|
102
|
+
|
103
|
+
run cli, dump_cmd(dump_options, file)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Dump cli command
|
107
|
+
def dump_cmd(options, file)
|
108
|
+
"mysqldump #{options} > #{file.path}"
|
109
|
+
end
|
110
|
+
protected :dump_cmd
|
111
|
+
|
112
|
+
# Run the command with the cli
|
113
|
+
#
|
114
|
+
# @param [#run] cli the cli program that responds to `#run`.
|
115
|
+
# @param [String] cmd the command to run.
|
116
|
+
#
|
117
|
+
# @return [nil]
|
118
|
+
def run(cli, cmd)
|
119
|
+
cli.run cmd, verbose: false, capture: true
|
120
|
+
end
|
121
|
+
protected :run
|
122
|
+
end
|
123
|
+
end
|
data/lib/strike/interpreter.rb
CHANGED
@@ -8,7 +8,7 @@ class Strike
|
|
8
8
|
|
9
9
|
def initialize(table_source = nil)
|
10
10
|
@table_source = table_source
|
11
|
-
@tables ||= Hash.new { |h, k| h[k] =
|
11
|
+
@tables ||= Hash.new { |h, k| h[k] = :keep }
|
12
12
|
end
|
13
13
|
|
14
14
|
# Parse the given profile and generate the tables defined in it.
|
@@ -27,7 +27,7 @@ class Strike
|
|
27
27
|
def table(name, &block)
|
28
28
|
table = table_source.call(&block)
|
29
29
|
|
30
|
-
@tables[name.to_sym] = table
|
30
|
+
@tables[name.to_sym] = table.call
|
31
31
|
end
|
32
32
|
|
33
33
|
def table_source
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'my_obfuscate'
|
4
|
+
|
5
|
+
class Strike
|
6
|
+
class Obfuscator
|
7
|
+
def initialize(config = {})
|
8
|
+
@adapter_source = config[:adapter_source]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Obfuscates the data from input to output with the given information.
|
12
|
+
#
|
13
|
+
# @param [Hash] tables the tables definitions
|
14
|
+
# @param [IO] input the input source to read from.
|
15
|
+
# @param [IO] output the output source to write to.
|
16
|
+
#
|
17
|
+
# @return [nil]
|
18
|
+
def call(tables, input, output)
|
19
|
+
adapter = adapter_source.call(tables)
|
20
|
+
adapter.globally_kept_columns = %w(id created_at updated_at)
|
21
|
+
|
22
|
+
adapter.obfuscate(input, output)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Adapter generator.
|
26
|
+
def adapter_source
|
27
|
+
@adapter_source ||= MyObfuscate.public_method(:new)
|
28
|
+
end
|
29
|
+
protected :adapter_source
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
-- MySQL dump 10.13 Distrib 5.5.29, for osx10.8 (i386)
|
2
|
+
--
|
3
|
+
-- Host: localhost Database: example
|
4
|
+
-- ------------------------------------------------------
|
5
|
+
-- Server version 5.5.29
|
6
|
+
|
7
|
+
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
8
|
+
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
9
|
+
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
10
|
+
/*!40101 SET NAMES utf8 */;
|
11
|
+
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
12
|
+
/*!40103 SET TIME_ZONE='+00:00' */;
|
13
|
+
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
14
|
+
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
15
|
+
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
16
|
+
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
17
|
+
|
18
|
+
--
|
19
|
+
-- Table structure for table `devices`
|
20
|
+
--
|
21
|
+
|
22
|
+
DROP TABLE IF EXISTS `devices`;
|
23
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
24
|
+
/*!40101 SET character_set_client = utf8 */;
|
25
|
+
CREATE TABLE `devices` (
|
26
|
+
`id` int(11) NOT NULL AUTO_INCREMENT,
|
27
|
+
`name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
|
28
|
+
`created_at` datetime DEFAULT NULL,
|
29
|
+
`updated_at` datetime DEFAULT NULL,
|
30
|
+
PRIMARY KEY (`id`),
|
31
|
+
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
32
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
33
|
+
|
34
|
+
--
|
35
|
+
-- Dumping data for table `devices`
|
36
|
+
--
|
37
|
+
|
38
|
+
LOCK TABLES `devices` WRITE;
|
39
|
+
/*!40000 ALTER TABLE `devices` DISABLE KEYS */;
|
40
|
+
INSERT INTO `devices` (`id`, `name`, `created_at`, `updated_at`) VALUES (1,'Original name','2010-10-20 13:40:20','2012-08-24 06:32:31'),(2,'Original name','2010-10-20 13:40:32','2012-10-24 07:39:02'),(3,'Original name','2010-10-20 13:40:46','2012-09-13 10:17:37');
|
41
|
+
/*!40000 ALTER TABLE `devices` ENABLE KEYS */;
|
42
|
+
UNLOCK TABLES;
|
43
|
+
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
44
|
+
|
45
|
+
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
46
|
+
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
47
|
+
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
48
|
+
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
49
|
+
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
50
|
+
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
51
|
+
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
52
|
+
|
53
|
+
-- Dump completed on 2013-01-11 15:47:10
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../minitest_helper'
|
4
|
+
require 'strike'
|
5
|
+
|
6
|
+
describe Strike, 'Use of Strike in the cli' do
|
7
|
+
describe '#obfuscate' do
|
8
|
+
let(:assets_path) { File.join(File.dirname(__FILE__), '..', 'assets') }
|
9
|
+
let(:input) { "#{assets_path}/dump.sql" }
|
10
|
+
let(:profile) { "#{assets_path}/dump_profile.rb" }
|
11
|
+
let(:params) { %W(obfuscate --input=#{input} --profile=#{profile}) }
|
12
|
+
|
13
|
+
it 'should obfuscate the sql dump' do
|
14
|
+
out = capture_io { Strike.start(params) }.join('')
|
15
|
+
|
16
|
+
out.wont_match /Original name/
|
17
|
+
# dump_profile.rb sets this string in each name
|
18
|
+
out.must_match /Obfuscated name/
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../../minitest_helper'
|
4
|
+
require 'strike/dumper'
|
5
|
+
|
6
|
+
describe Strike::Dumper do
|
7
|
+
let(:database_url) { 'mysql://test:pass@localhost:3005/test' }
|
8
|
+
let(:database_config) do
|
9
|
+
{
|
10
|
+
db_type: 'mysql',
|
11
|
+
host: 'localhost',
|
12
|
+
port: '3005',
|
13
|
+
user: 'test',
|
14
|
+
password: 'pass',
|
15
|
+
database: 'test',
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:options) do
|
20
|
+
dump_options = %w(-c
|
21
|
+
--add-drop-table
|
22
|
+
--add-locks
|
23
|
+
--single-transaction
|
24
|
+
--set-charset
|
25
|
+
--create-options
|
26
|
+
--disable-keys
|
27
|
+
--quick).join(' ')
|
28
|
+
dump_options << " -u #{database_config[:user]}"
|
29
|
+
dump_options << " -h #{database_config[:host]}"
|
30
|
+
dump_options << " -P #{database_config[:port]}"
|
31
|
+
dump_options << " -p#{database_config[:password]}"
|
32
|
+
dump_options << " #{database_config[:database]}"
|
33
|
+
|
34
|
+
dump_options
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:cli) do
|
38
|
+
MiniTest::Mock.new.expect(
|
39
|
+
:run,
|
40
|
+
true,
|
41
|
+
["mysqldump #{options} > path", { verbose: false, capture: true }]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:dumper) { Strike::Dumper.new }
|
46
|
+
|
47
|
+
subject { dumper }
|
48
|
+
|
49
|
+
describe '#parse_url' do
|
50
|
+
it 'should parse the given connection as a url' do
|
51
|
+
subject.parse_url(database_url).must_equal database_config
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#dump_data' do
|
56
|
+
let(:output) { MiniTest::Mock.new.expect(:path, 'path') }
|
57
|
+
|
58
|
+
it 'should dump data with the default options' do
|
59
|
+
subject.dump_data(cli, database_config, output).must_equal true
|
60
|
+
end
|
61
|
+
|
62
|
+
after do
|
63
|
+
output.verify
|
64
|
+
cli.verify
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#call' do
|
69
|
+
let(:output) do
|
70
|
+
MiniTest::Mock.new.
|
71
|
+
expect(:path, 'path').
|
72
|
+
expect(:unlink, true).
|
73
|
+
expect(:must_equal, true, [MiniTest::Mock])
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:dumpfile_source) do
|
77
|
+
MiniTest::Mock.new.expect(:call, output, [['original_dump', 'sql']])
|
78
|
+
end
|
79
|
+
|
80
|
+
let(:dumper) { Strike::Dumper.new(dumpfile_source: dumpfile_source) }
|
81
|
+
|
82
|
+
it 'should generate dump file with default options and a database_url' do
|
83
|
+
subject.call(cli, database_url) do |file|
|
84
|
+
file.must_equal output
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
after do
|
89
|
+
dumpfile_source.verify
|
90
|
+
output.verify
|
91
|
+
cli.verify
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../../minitest_helper'
|
4
|
+
require 'strike/interpreter'
|
5
|
+
|
6
|
+
describe Strike::Interpreter do
|
7
|
+
|
8
|
+
let(:table_users) { { name: :keep } }
|
9
|
+
let(:table_movies) { :keep }
|
10
|
+
|
11
|
+
let(:table_source) do
|
12
|
+
->(&block) { block ? block.call(table_mock) : -> { table_movies } }
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:table_users) { {name: :keep } }
|
16
|
+
|
17
|
+
let(:table_mock) do
|
18
|
+
tb = MiniTest::Mock.new.expect(:call, table_users)
|
19
|
+
|
20
|
+
MiniTest::Mock.new.expect(:name, tb, [:first_name])
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:profile) do
|
24
|
+
<<-PROFILE
|
25
|
+
table :users do |t|
|
26
|
+
t.name :first_name
|
27
|
+
end
|
28
|
+
|
29
|
+
table :movies
|
30
|
+
PROFILE
|
31
|
+
end
|
32
|
+
|
33
|
+
subject { Strike::Interpreter.new(table_source) }
|
34
|
+
|
35
|
+
describe '#parse' do
|
36
|
+
let(:tables) { subject.parse(profile) }
|
37
|
+
|
38
|
+
it 'should parse all profile tables' do
|
39
|
+
tables.count.must_equal 2
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should parse tables with a block' do
|
43
|
+
tables[:users].must_equal table_users
|
44
|
+
table_mock.verify
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should parse tables without a block' do
|
48
|
+
tables[:movies].must_equal table_movies
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#tables' do
|
53
|
+
let(:tables) { subject.tables }
|
54
|
+
|
55
|
+
it 'should have default tables' do
|
56
|
+
tables[:test].must_equal :keep
|
57
|
+
tables[:test2].must_equal :keep
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../../minitest_helper'
|
4
|
+
require 'strike/obfuscator'
|
5
|
+
|
6
|
+
describe Strike::Obfuscator do
|
7
|
+
let(:input) { Object.new }
|
8
|
+
let(:output) { Object.new }
|
9
|
+
let(:tables) { { users: :keep, movies: :keep } }
|
10
|
+
let(:adapter_mock) do
|
11
|
+
MiniTest::Mock.new.
|
12
|
+
expect(:globally_kept_columns=, true, [%w(id created_at updated_at)]).
|
13
|
+
expect(:obfuscate, true, [input, output])
|
14
|
+
end
|
15
|
+
let(:adapter_source) do
|
16
|
+
MiniTest::Mock.new.expect(:call, adapter_mock, [tables])
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:obfuscator) { Strike::Obfuscator.new(adapter_source: adapter_source) }
|
20
|
+
|
21
|
+
subject { obfuscator }
|
22
|
+
|
23
|
+
describe '#call' do
|
24
|
+
it 'should prepare the adapter and call it' do
|
25
|
+
subject.call(tables, input, output).must_equal true
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
adapter_source.verify
|
30
|
+
adapter_mock.verify
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative '../../minitest_helper'
|
4
|
+
require 'strike/table'
|
5
|
+
|
6
|
+
describe Strike::Table do
|
7
|
+
let(:hash) { { name: :test } }
|
8
|
+
let(:table) { Strike::Table.new }
|
9
|
+
|
10
|
+
subject { table }
|
11
|
+
|
12
|
+
describe '#method_missing' do
|
13
|
+
it 'should respond to missing methods' do
|
14
|
+
subject.name(:test).wont_be_nil
|
15
|
+
subject.test.wont_be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#to_hash' do
|
20
|
+
before { subject.name(:test) }
|
21
|
+
|
22
|
+
it 'should save method calls as hash' do
|
23
|
+
subject.to_hash.must_equal hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#initialize' do
|
28
|
+
let(:table) do
|
29
|
+
Strike::Table.new do |t|
|
30
|
+
t.name :test
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should accept a block' do
|
35
|
+
subject.to_hash.must_equal hash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#call' do
|
40
|
+
it 'should respond with a Hash' do
|
41
|
+
subject.call.must_equal Hash.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
File without changes
|
data/strike.gemspec
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.authors = ['Juan Hernández']
|
5
5
|
gem.email = ['juan.hernandez@wuaki.tv']
|
6
|
-
gem.description = %q{
|
7
|
-
gem.summary = %q{Dump a mysql database with sensitive data
|
6
|
+
gem.description = %q{Obfuscate a mysql dump database with sensitive data}
|
7
|
+
gem.summary = %q{Dump and obfuscate a mysql database with sensitive data}
|
8
8
|
gem.homepage = 'https://github.com/wuakitv/strike'
|
9
9
|
|
10
10
|
gem.files = `git ls-files`.split($\)
|
@@ -16,8 +16,5 @@ Gem::Specification.new do |gem|
|
|
16
16
|
|
17
17
|
gem.add_runtime_dependency 'rake', '~> 0.9'
|
18
18
|
gem.add_runtime_dependency 'my_obfuscate', '~> 0.3.7'
|
19
|
-
gem.add_runtime_dependency 'sequel', '~> 3.39'
|
20
|
-
gem.add_runtime_dependency 'mysql2', '~> 0.3.11'
|
21
|
-
gem.add_runtime_dependency 'sqlite3', '~> 1.3.6'
|
22
19
|
gem.add_runtime_dependency 'thor', '~> 0.16.0'
|
23
20
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strike
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -43,54 +43,6 @@ dependencies:
|
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 0.3.7
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: sequel
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - ~>
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '3.39'
|
54
|
-
type: :runtime
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '3.39'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: mysql2
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ~>
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 0.3.11
|
70
|
-
type: :runtime
|
71
|
-
prerelease: false
|
72
|
-
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
|
-
requirements:
|
75
|
-
- - ~>
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: 0.3.11
|
78
|
-
- !ruby/object:Gem::Dependency
|
79
|
-
name: sqlite3
|
80
|
-
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
|
-
requirements:
|
83
|
-
- - ~>
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
version: 1.3.6
|
86
|
-
type: :runtime
|
87
|
-
prerelease: false
|
88
|
-
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
|
-
requirements:
|
91
|
-
- - ~>
|
92
|
-
- !ruby/object:Gem::Version
|
93
|
-
version: 1.3.6
|
94
46
|
- !ruby/object:Gem::Dependency
|
95
47
|
name: thor
|
96
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,7 +59,7 @@ dependencies:
|
|
107
59
|
- - ~>
|
108
60
|
- !ruby/object:Gem::Version
|
109
61
|
version: 0.16.0
|
110
|
-
description:
|
62
|
+
description: Obfuscate a mysql dump database with sensitive data
|
111
63
|
email:
|
112
64
|
- juan.hernandez@wuaki.tv
|
113
65
|
executables:
|
@@ -125,13 +77,19 @@ files:
|
|
125
77
|
- VERSION
|
126
78
|
- bin/strike
|
127
79
|
- lib/strike.rb
|
128
|
-
- lib/strike/
|
80
|
+
- lib/strike/dumper.rb
|
129
81
|
- lib/strike/interpreter.rb
|
82
|
+
- lib/strike/obfuscator.rb
|
130
83
|
- lib/strike/table.rb
|
84
|
+
- spec/assets/dump.sql
|
85
|
+
- spec/assets/dump_profile.rb
|
86
|
+
- spec/bin/strike_spec.rb
|
87
|
+
- spec/lib/strike/dumper_spec.rb
|
88
|
+
- spec/lib/strike/interpreter_spec.rb
|
89
|
+
- spec/lib/strike/obfuscator_spec.rb
|
90
|
+
- spec/lib/strike/table_spec.rb
|
91
|
+
- spec/minitest_helper.rb
|
131
92
|
- strike.gemspec
|
132
|
-
- test/lib/strike/interpreter_test.rb
|
133
|
-
- test/lib/strike/table_test.rb
|
134
|
-
- test/minitest_helper.rb
|
135
93
|
homepage: https://github.com/wuakitv/strike
|
136
94
|
licenses: []
|
137
95
|
post_install_message:
|
@@ -155,8 +113,13 @@ rubyforge_project:
|
|
155
113
|
rubygems_version: 1.8.23
|
156
114
|
signing_key:
|
157
115
|
specification_version: 3
|
158
|
-
summary: Dump a mysql database with sensitive data
|
116
|
+
summary: Dump and obfuscate a mysql database with sensitive data
|
159
117
|
test_files:
|
160
|
-
-
|
161
|
-
-
|
162
|
-
-
|
118
|
+
- spec/assets/dump.sql
|
119
|
+
- spec/assets/dump_profile.rb
|
120
|
+
- spec/bin/strike_spec.rb
|
121
|
+
- spec/lib/strike/dumper_spec.rb
|
122
|
+
- spec/lib/strike/interpreter_spec.rb
|
123
|
+
- spec/lib/strike/obfuscator_spec.rb
|
124
|
+
- spec/lib/strike/table_spec.rb
|
125
|
+
- spec/minitest_helper.rb
|
data/lib/strike/agent.rb
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'tempfile'
|
4
|
-
require 'mysql2'
|
5
|
-
require 'sequel'
|
6
|
-
require 'my_obfuscate'
|
7
|
-
|
8
|
-
class Strike
|
9
|
-
class Agent
|
10
|
-
def initialize(config = {})
|
11
|
-
@db_connector = config[:db_connector]
|
12
|
-
@dumpfile_source = config[:dumpfile_source]
|
13
|
-
@obfuscator_source = config[:obfuscator_source]
|
14
|
-
end
|
15
|
-
|
16
|
-
def connect_to_db(database_url)
|
17
|
-
db_connector.call(database_url.gsub(/^mysql:/, 'mysql2:'))
|
18
|
-
end
|
19
|
-
protected :connect_to_db
|
20
|
-
|
21
|
-
def db_connector
|
22
|
-
@db_connector ||= Sequel.public_method(:connect)
|
23
|
-
end
|
24
|
-
protected :db_connector
|
25
|
-
|
26
|
-
def call(cli, database_url, tables, output = $stdout)
|
27
|
-
@cli = cli
|
28
|
-
@db = connect_to_db(database_url)
|
29
|
-
@tables = tables
|
30
|
-
|
31
|
-
tempfile do |file|
|
32
|
-
dump_data(@db.opts, file)
|
33
|
-
obfuscate_data(file, output)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def tempfile(&block)
|
38
|
-
tmp = dumpfile_source.call(['original_dump', 'sql']) do |file|
|
39
|
-
block.call(file)
|
40
|
-
file
|
41
|
-
end
|
42
|
-
ensure
|
43
|
-
tmp.unlink if tmp
|
44
|
-
end
|
45
|
-
protected :tempfile
|
46
|
-
|
47
|
-
def dumpfile_source
|
48
|
-
@dumpfile_source ||= Tempfile.public_method(:open)
|
49
|
-
end
|
50
|
-
protected :dumpfile_source
|
51
|
-
|
52
|
-
# TODO: support more databases
|
53
|
-
def dump_data(db_config, file)
|
54
|
-
dump_options = %w(-c
|
55
|
-
--add-drop-table
|
56
|
-
--add-locks
|
57
|
-
--single-transaction
|
58
|
-
--set-charset
|
59
|
-
--create-options
|
60
|
-
--disable-keys
|
61
|
-
--quick).join(' ')
|
62
|
-
dump_options << " -u #{db_config[:user]}" if db_config[:user]
|
63
|
-
dump_options << " -h #{db_config[:host]}" if db_config[:host]
|
64
|
-
dump_options << " -P #{db_config[:port]}" if db_config[:port]
|
65
|
-
dump_options << " -p#{db_config[:password]}" if db_config[:password]
|
66
|
-
dump_options << " #{db_config[:database]}"
|
67
|
-
|
68
|
-
run dump_cmd(dump_options, file)
|
69
|
-
end
|
70
|
-
|
71
|
-
def dump_cmd(options, file)
|
72
|
-
"mysqldump #{options} > #{file.path}"
|
73
|
-
end
|
74
|
-
protected :dump_cmd
|
75
|
-
|
76
|
-
def run(cmd)
|
77
|
-
@cli.run cmd, verbose: false, capture: true
|
78
|
-
end
|
79
|
-
protected :run
|
80
|
-
|
81
|
-
def obfuscate_data(input, output)
|
82
|
-
obfuscator = obfuscator_source.call(table_definitions)
|
83
|
-
obfuscator.globally_kept_columns = %w(id created_at updated_at)
|
84
|
-
|
85
|
-
obfuscator.obfuscate(input, output)
|
86
|
-
end
|
87
|
-
|
88
|
-
def obfuscator_source
|
89
|
-
@obfuscator_source ||= MyObfuscate.public_method(:new)
|
90
|
-
end
|
91
|
-
protected :obfuscator_source
|
92
|
-
|
93
|
-
def table_definitions
|
94
|
-
db_tables.reduce({}) do |acc, table|
|
95
|
-
acc[table] = @tables[table].call
|
96
|
-
acc
|
97
|
-
end
|
98
|
-
end
|
99
|
-
protected :table_definitions
|
100
|
-
|
101
|
-
def db_tables
|
102
|
-
@db.tables
|
103
|
-
end
|
104
|
-
protected :db_tables
|
105
|
-
end
|
106
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require_relative '../../minitest_helper'
|
4
|
-
require 'strike/interpreter'
|
5
|
-
|
6
|
-
class Strike::InterpreterTest < MiniTest::Unit::TestCase
|
7
|
-
def setup
|
8
|
-
@interpreter = Strike::Interpreter.new(table_source)
|
9
|
-
@profile = <<-PROFILE
|
10
|
-
table :users do |t|
|
11
|
-
t.name :first_name
|
12
|
-
end
|
13
|
-
|
14
|
-
table :movies
|
15
|
-
PROFILE
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_should_parse_a_profile_into_tables
|
19
|
-
tables = @interpreter.parse(@profile)
|
20
|
-
|
21
|
-
assert_equal 2, tables.count
|
22
|
-
assert tables[:users]
|
23
|
-
assert tables[:movies]
|
24
|
-
|
25
|
-
table_mock.verify
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_should_have_default_tables
|
29
|
-
tables = @interpreter.tables
|
30
|
-
|
31
|
-
assert_equal :keep, tables[:test].call
|
32
|
-
assert_equal :keep, tables[:test2].call
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def table_source
|
38
|
-
->(&block) { block ? block.call(table_mock) : table_mock }
|
39
|
-
end
|
40
|
-
|
41
|
-
def table_mock
|
42
|
-
@table_mock ||= MiniTest::Mock.new.expect(:name, true, [:first_name])
|
43
|
-
end
|
44
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require_relative '../../minitest_helper'
|
4
|
-
require 'strike/table'
|
5
|
-
|
6
|
-
class Strike::TableTest < MiniTest::Unit::TestCase
|
7
|
-
def setup
|
8
|
-
@table = Strike::Table.new
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_should_respond_to_missing_methods
|
12
|
-
assert @table.name(:test)
|
13
|
-
assert @table.test
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_should_save_method_calls_as_hash
|
17
|
-
expected = { name: :test }
|
18
|
-
@table.name(:test)
|
19
|
-
|
20
|
-
assert_equal expected, @table.to_hash
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_should_respond_to_call
|
24
|
-
assert_equal Hash.new, @table.call
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_should_accept_a_block_when_its_initialized
|
28
|
-
table = Strike::Table.new do |t|
|
29
|
-
t.name :test
|
30
|
-
end
|
31
|
-
expected = { name: :test }
|
32
|
-
|
33
|
-
assert_equal expected, table.to_hash
|
34
|
-
end
|
35
|
-
end
|