mysqlaudit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjRmYmMyZjFiYjNiMGNkYzllNTY3NjZjMWJlZTljNjY0MTUwOTE1Zg==
5
+ data.tar.gz: !binary |-
6
+ N2MwYTg0NWFlZjQ5NzAyMTQzYWRmYzc0YWEwMDk4NGRmY2ViN2ZjNw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ODUzYWIxYWYxY2NhMTI2MTlmZTM0NzhmN2MyYTEyYWY0MjlhMzEwMGUxNWZh
10
+ MjVhMmI3ZDQ1YWVlZTE0YjRjM2E3MTJlZGQxMGFiYjA2YjA4NWQ1NWQyOWIz
11
+ NzQ1MDVmYmM5NWYzNWFkYzEzYWI0YTA1ZjQ5NjhlMDMxYTliNzU=
12
+ data.tar.gz: !binary |-
13
+ MjI4ODI0MWQwODYyNWViZjdiMjFiYWIwZmM0ZmUyOWY2NzVkN2RkYjMyNzYx
14
+ OWZkNTc5NzNiZGFkYWNlYjNkMGJhMjlhMzgwYWFlZTk5NjRjYjI4Nzg5MWU5
15
+ NGY2N2EyYjc0ZTBkNDFlOWEzZWZkZTBkY2M2YzlhMDJiMWY0MzQ=
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nicola Strappazzon C.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # Mysqlaudit
2
+
3
+ MySQL tool build in ruby for audit all tables with triggers, this is a change
4
+ log for SQL transactions executed sentences as INSERT, UPDATE, and DELETE.
5
+
6
+ ## Useful for:
7
+
8
+ 1. Log all changes on table.
9
+ 2. Rollback changes.
10
+ 3. Detecting unnecessary transactions.
11
+
12
+ ## Installation
13
+
14
+ Install this tool executing the following command:
15
+
16
+ ```Shell
17
+ $ gem install mysqlaudit
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ This is a simple tutorial to understand this tool, please, follow next
23
+ instructions:
24
+
25
+ ### Install test enviroment
26
+
27
+ Create test database and table:
28
+
29
+ ```SQL
30
+ CREATE DATABASE audit CHARACTER SET utf8;
31
+
32
+ USE audit;
33
+
34
+ CREATE TABLE foo (
35
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
36
+ email char(255) NOT NULL,
37
+ name VARCHAR(45) NOT NULL,
38
+ birthday TIMESTAMP NOT NULL,
39
+ PRIMARY KEY (id),
40
+ UNIQUE KEY id_UNIQUE (id)
41
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
42
+ ```
43
+
44
+ Install audit table and triggers on foo table:
45
+
46
+ ```Shell
47
+ $ mysqlaudit install --host 127.0.0.1 --user root --pass admin --schema audit --table foo
48
+ ```
49
+
50
+ The following query catch all changes of data with trigger audit:
51
+
52
+ ```SQL
53
+ INSERT INTO foo (email, name, birthday) VALUES ('abc@abc.com','Fulano','1980-04-01');
54
+ INSERT INTO foo (email, name, birthday) VALUES ('def@def.org','Mengano','1979-03-28');
55
+ INSERT INTO foo (email, name, birthday) VALUES ('ghi@ghi.net','Jaimito','1980-06-15');
56
+ UPDATE foo SET name = 'Zutano' WHERE id = 3;
57
+ DELETE FROM foo WHERE id = 2;
58
+ ```
59
+
60
+ To apply rollback with specific transaction, this generate SQL output:
61
+
62
+ ### Get deleted data:
63
+
64
+ ```Shell
65
+ $ mysqlaudit rollback --host 127.0.0.1 \
66
+ --user root \
67
+ --pass admin \
68
+ --schema audit \
69
+ --table foo \
70
+ --statement delete
71
+ ```
72
+
73
+ Output of last executed command:
74
+
75
+ ```
76
+ /* 2013-07-11 07:25:25 */ INSERT INTO foo (id, email, name, birthday) VALUES (2, 'def@def.org', 'Mengano', '1979-03-28 00:00:00');
77
+ ```
78
+
79
+ ### Get updated data:
80
+ ```Shell
81
+ $ mysqlaudit rollback --host 127.0.0.1 \
82
+ --user root \
83
+ --pass admin \
84
+ --schema audit \
85
+ --table foo \
86
+ --statement update
87
+ ```
88
+
89
+ Output of last executed command:
90
+
91
+ ```
92
+ /* 2013-07-11 07:26:26 */ UPDATE users SET birthday = '1980-06-15 00:00:00' WHERE id = 3;
93
+ /* 2013-07-11 07:26:26 */ UPDATE users SET name = 'Jaimito' WHERE id = 3;
94
+ ```
95
+
96
+ ### Get inserted data:
97
+ ```Shell
98
+ $ mysqlaudit rollback --host 127.0.0.1 \
99
+ --user root \
100
+ --pass admin \
101
+ --schema audit \
102
+ --table foo \
103
+ --statement insert
104
+ ```
105
+
106
+ Output of last executed command:
107
+
108
+ ```
109
+ /* 2013-07-11 07:27:27 */ DELETE FROM foo WHERE id = 1;
110
+ /* 2013-07-11 07:27:27 */ DELETE FROM foo WHERE id = 2;
111
+ /* 2013-07-11 07:27:27 */ DELETE FROM foo WHERE id = 3;
112
+ ```
113
+
114
+ ### Uninstall test enviroment:
115
+
116
+ Uninstall all triggers and drop audit table.
117
+
118
+ ```Shell
119
+ $ mysqlaudit uninstall --host 127.0.0.1 \
120
+ --user root \
121
+ --pass admin \
122
+ --schema audit \
123
+ --table foo \
124
+ --drop-audit-table
125
+ ```
126
+
127
+ Drop testing table and database:
128
+
129
+ ```SQL
130
+ DROP TABLE audit;
131
+ DROP DATABASE foo;
132
+ ```
133
+
134
+ For more information:
135
+
136
+ ```Shell
137
+ $ mysqlaudit --help
138
+ ```
139
+
140
+ ## Warning
141
+
142
+ 1. Do not use this tool in production before testing it.
143
+ 2. Please, use when do you need.
144
+ 3. The author is NOT responsible for misuse use of this tool.
145
+
146
+ ## Contributing
147
+
148
+ 1. Fork it
149
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
150
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
151
+ 4. Push to the branch (`git push origin my-new-feature`)
152
+ 5. Create new Pull Request
data/bin/mysqlaudit ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Only for deploy this script.
4
+ $LOAD_PATH << './lib'
5
+
6
+
7
+ require 'rubygems'
8
+ require 'commander'
9
+ require 'commander/import'
10
+ require 'mysqlaudit'
11
+ require 'mysqlaudit/actions'
12
+ require 'mysqlaudit/version'
13
+
14
+ program :name, 'MySQL Audit Tool'
15
+ program :version, Mysqlaudit::VERSION
16
+ program :description, 'MySQL tool for audit all tables with triggers.'
17
+ program :help, 'Author', 'Nicola Strappazzon <nicola51980@gmail.com>'
18
+
19
+ command :install do |c|
20
+ c.description = 'Install audit table'
21
+ c.syntax = 'audits install --host 127.0.0.1 --user root [--pass admin] --schema sakila [--table foo]'
22
+ c.option '--host STRING', String, 'Host'
23
+ c.option '--user STRING', String, 'User'
24
+ c.option '--pass STRING', String, 'Password'
25
+ c.option '--schema STRING', String, 'Schema name'
26
+ c.option '--table STRING', String, 'Table name'
27
+ c.action do |args, options|
28
+ Mysqlaudit::Actions.new(options.host, options.user, options.pass, options.schema, options.table).install
29
+ end
30
+ end
31
+
32
+ command :uninstall do |c|
33
+ c.description = 'Uninstall audit table'
34
+ c.syntax = 'audits uninstall --host 127.0.0.1 --user root [--pass admin] --schema sakila [--table foo] [--[no-]drop-audit-table]'
35
+ c.option '--host STRING', String, 'Host'
36
+ c.option '--user STRING', String, 'User'
37
+ c.option '--pass STRING', String, 'Password'
38
+ c.option '--schema STRING', String, 'Schema name'
39
+ c.option '--table STRING', String, 'Table name'
40
+ c.option '--[no-]drop-audit-table', 'Drop audit table'
41
+ c.action do |args, options|
42
+ Mysqlaudit::Actions.new(options.host, options.user, options.pass, options.schema, options.table, options.drop_audit_table).uninstall
43
+ end
44
+ end
45
+
46
+ command :rollback do |c|
47
+ c.description = 'Rollback transaction cath in audit table'
48
+ c.syntax = 'audits rollback --host 127.0.0.1 --user root [--pass admin] --schema sakila --table foo --statement INSERT'
49
+ c.option '--host STRING', String, 'Host'
50
+ c.option '--user STRING', String, 'User'
51
+ c.option '--pass STRING', String, 'Password'
52
+ c.option '--schema STRING', String, 'Schema name'
53
+ c.option '--table STRING', String, 'Table name'
54
+ c.option '--statement STRING', String, 'Statement operation, INSERT, UPDATE, or DELETE'
55
+ c.action do |args, options|
56
+ Mysqlaudit::Actions.new(options.host, options.user, options.pass, options.schema, options.table, nil, options.statement).rollback
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ require 'mysqlaudit/audit'
2
+
3
+ module Mysqlaudit
4
+ class Actions
5
+ @audit = nil
6
+
7
+ def initialize(host, user, password, schema, table, drop = nil, statement = nil)
8
+ # def initialize(schema, table, options = {})
9
+ # options = {host: 'localhost', user: 'root', password: ''}.merge(options)
10
+
11
+ $host = host
12
+ $user = user
13
+ $password = password
14
+ $schema = schema
15
+ $table = table
16
+ $drop = drop
17
+ $statement = statement
18
+
19
+ @audit = Mysqlaudit::Audit.new($host, $user, $password, $schema)
20
+ end
21
+
22
+ def install()
23
+ @audit.create_table()
24
+
25
+ if !$table
26
+ tables = @audit.get_tables()
27
+ tables.each do | table |
28
+ @audit.create_trigger(table, :insert)
29
+ @audit.create_trigger(table, :update)
30
+ @audit.create_trigger(table, :delete)
31
+ end
32
+ else
33
+ @audit.create_trigger($table, :insert)
34
+ @audit.create_trigger($table, :update)
35
+ @audit.create_trigger($table, :delete)
36
+ end
37
+ end
38
+
39
+ def uninstall()
40
+ if !$table
41
+ tables = @audit.get_tables()
42
+ tables.each do | table |
43
+ @audit.drop_trigger(table, :insert)
44
+ @audit.drop_trigger(table, :update)
45
+ @audit.drop_trigger(table, :delete)
46
+ end
47
+ else
48
+ @audit.drop_trigger($table, :insert)
49
+ @audit.drop_trigger($table, :update)
50
+ @audit.drop_trigger($table, :delete)
51
+ end
52
+
53
+ if $drop
54
+ @audit.drop_table()
55
+ end
56
+ end
57
+
58
+ def rollback()
59
+ @audit.rollback($table, $statement.to_sym)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,168 @@
1
+ require 'rubygems'
2
+ require 'mysql2'
3
+
4
+ # @todo Optimize passing params on methods with maps.
5
+ # @todo Optimize is set params to execute methods.
6
+ # @todo Passing as parameter the tables names to apply triggers.
7
+ # @todo Change name for audit table.
8
+ # @todo If exist trigger then append to existing.
9
+ # @todo Backup if exist triggers.
10
+ # @todo Define expiration time for events.
11
+
12
+ module Mysqlaudit
13
+ class Audit
14
+ def initialize(host, user, password, schema)
15
+ $host = host
16
+ $user = user
17
+ $password = password
18
+ $schema = schema
19
+
20
+ connect
21
+ end
22
+
23
+ def connect()
24
+ begin
25
+ @mysql = Mysql2::Client.new(:host => $host,
26
+ :username => $user,
27
+ :password => $password,
28
+ :database => $schema)
29
+ rescue
30
+ puts "Can't connect to MySQL Server."
31
+ exit 1
32
+ end
33
+ end
34
+
35
+ def create_table()
36
+ if !has_table
37
+ sql = <<SQL
38
+ CREATE TABLE IF NOT EXISTS audits (
39
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
40
+ type enum('I', 'U', 'D') NOT NULL,
41
+ `table` char(64) NOT NULL,
42
+ `column` char(64) NOT NULL,
43
+ `primary_key` int(10) unsigned,
44
+ `old` TEXT DEFAULT NULL,
45
+ `new` TEXT DEFAULT NULL,
46
+ `trigger_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
47
+ PRIMARY KEY (id),
48
+ UNIQUE KEY id_UNIQUE (id)
49
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
50
+ SQL
51
+
52
+ @mysql.query(sql)
53
+ puts "Created table audits in #{$schema} database."
54
+ end
55
+ end
56
+
57
+ def has_table()
58
+ sql = "SELECT true AS has FROM information_schema.tables WHERE table_schema = '#{$schema}' AND table_name = 'audits';"
59
+ @mysql.query(sql).count == 1
60
+ end
61
+
62
+ def drop_table()
63
+ return unless has_table()
64
+ @mysql.query("DROP TABLE IF EXISTS audits;")
65
+ puts "Delete table audits in #{$schema} database."
66
+ end
67
+
68
+ def get_tables()
69
+ sql = "SELECT table_name AS name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '#{$schema}' AND table_name <> 'audits';"
70
+ sql_result = @mysql.query(sql)
71
+ sql_result.map { |table| table['name']}
72
+ end
73
+
74
+ def get_primary_key(table)
75
+ sql = "SELECT column_name AS name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='#{$schema}' AND TABLE_NAME='#{table}' AND column_key = 'PRI';"
76
+ @mysql.query(sql).first['name']
77
+ end
78
+
79
+ def get_columns(table)
80
+ sql = "SELECT column_name AS name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='#{$schema}' AND TABLE_NAME='#{table}' AND column_key <> 'PRI';"
81
+ sql_result = @mysql.query(sql)
82
+ sql_result.map { |table| table['name']}
83
+ end
84
+
85
+ def create_trigger(table, actionName)
86
+ if !has_trigger(table, actionName)
87
+ columns = get_columns(table)
88
+ key = get_primary_key(table)
89
+
90
+ if key
91
+ sql = "CREATE TRIGGER audit_#{table}_#{actionName.downcase} AFTER #{actionName.upcase} ON #{table}\nFOR EACH ROW BEGIN\n"
92
+
93
+ columns.each do | column |
94
+ case actionName.downcase
95
+ when :insert
96
+ sql << "INSERT INTO audits (type, `table`, `column`, primary_key, old, new) VALUES ('#{actionName[0,1].upcase}', '#{table}', '#{column}', NEW.#{key}, NULL, NEW.#{column});\n"
97
+ when :update
98
+ sql << "IF OLD.#{column} <> NEW.#{column} THEN\n"
99
+ sql << " INSERT INTO audits (type, `table`, `column`, primary_key, old, new) VALUES ('#{actionName[0,1].upcase}', '#{table}', '#{column}', NEW.#{key}, OLD.#{column}, NEW.#{column});\n"
100
+ sql << "END IF;\n"
101
+ when :delete
102
+ sql << "INSERT INTO audits (type, `table`, `column`, primary_key, old, new) VALUES ('#{actionName[0,1].upcase}', '#{table}', '#{column}', OLD.#{key}, OLD.#{column}, NULL);\n"
103
+ end
104
+ end
105
+
106
+ sql << "END;"
107
+
108
+ @mysql.query(sql)
109
+ puts "Created trigger in #{table} table after #{actionName}."
110
+ else
111
+ puts "Impossible to create trigger in #{table}, not have primary key."
112
+ end
113
+ else
114
+ puts "Impossible to create trigger in #{table}, has a trigger."
115
+ end
116
+ end
117
+
118
+ def drop_trigger(table, actionName)
119
+ if has_trigger(table, actionName)
120
+ sql = "DROP TRIGGER IF EXISTS audit_#{table}_#{actionName};"
121
+ @mysql.query(sql)
122
+ puts "Deleted trigger in #{table} table after #{actionName}."
123
+ end
124
+ end
125
+
126
+ def has_trigger(table, actionName)
127
+ sql = "SELECT true AS has FROM information_schema.triggers WHERE trigger_schema = '#{$schema}' AND trigger_name = 'audit_#{table}_#{actionName}';"
128
+ @mysql.query(sql).count == 1
129
+ end
130
+
131
+ def build_pivot_query(table, statement)
132
+ columns = get_columns($table)
133
+ key = get_primary_key($table)
134
+
135
+ case statement
136
+ when :insert
137
+ sql = <<SQL
138
+ SELECT CONCAT('/* ', trigger_at, ' */ DELETE FROM #{$table} WHERE id = ', MAX(primary_key),';') AS `row`
139
+ FROM audits
140
+ WHERE `type` = 'I'
141
+ AND `table` = '#{$table}'
142
+ GROUP BY primary_key
143
+ ORDER BY trigger_at, primary_key, `column` ASC;
144
+ SQL
145
+ when :delete
146
+ sql = "SELECT CONCAT('/* ', trigger_at, ' */ INSERT INTO #{$table} (#{key}, #{columns.join(', ')}) VALUES (', primary_key, ', ', "
147
+ columns.each do | column |
148
+ sql << " QUOTE(MAX(IF(`column` = '#{column}', `old`, NULL))), ', ', "
149
+ end
150
+ sql = sql.chomp(" ', ', ")
151
+ sql << "');'"
152
+ sql << ") AS `row` FROM audits WHERE `type` = 'D' AND `table` = '#{$table}' GROUP BY primary_key ORDER BY trigger_at, primary_key, `column` ASC;"
153
+ when :update
154
+ sql = "SELECT CONCAT('/* ', trigger_at, ' */ UPDATE users SET ', `column`, ' = ', QUOTE(old), ' WHERE id = ', primary_key, ';') AS `row`"
155
+ sql << "FROM audits WHERE `type` = 'U' AND `table` = '#{$table}' ORDER BY trigger_at, primary_key, `column` ASC;"
156
+ end
157
+
158
+ sql_result = @mysql.query(sql)
159
+ sql_result.each(:as => :array) do |row|
160
+ puts row
161
+ end
162
+ end
163
+
164
+ def rollback(table, statement)
165
+ build_pivot_query(table, statement)
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,3 @@
1
+ module Mysqlaudit
2
+ VERSION = '0.0.1'
3
+ end
data/lib/mysqlaudit.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'mysqlaudit/actions'
3
+ require 'mysqlaudit/audit'
4
+ require 'mysqlaudit/version'
5
+
6
+ module Mysqlaudit
7
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysqlaudit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nicola Strappazzon C.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: MySQL tool for audit all tables with triggers.
14
+ email: nicola51980@gmail.com
15
+ executables:
16
+ - mysqlaudit
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/mysqlaudit
21
+ - lib/mysqlaudit/actions.rb
22
+ - lib/mysqlaudit/audit.rb
23
+ - lib/mysqlaudit/version.rb
24
+ - lib/mysqlaudit.rb
25
+ - LICENSE.txt
26
+ - README.md
27
+ homepage: https://github.com/nicola51980/mysqlaudit
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.0.5
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: MySQL Audit Tool
51
+ test_files: []