背景介绍:
假设有这么一个情况,你作为某公司的MySQL-DBA,某日公司的数据库一些数据被认为删除了。尽管有数据备份,但是因停止服务而造成了损失。公司希望查出始作俑者是谁。拥有数据库操作权限的人很多,如何排查呢 ?
MySQL本身并没有审计的功能,binlog中虽然可以记录执行sql语句的thread id,通过thread id再结合show processlist可以查找到源头IP,但是却无法知道是哪个用户。而且thread id是短暂的,并不持久,对于以后的反查没有任何帮助。
Init-connect基本原理:
Init-connect在每次连接初始化阶段,会记录下这个连接的用户以及connection_id信息。在后期审计进行行为追踪的时候,根据binglog的记录以及对应的thread id结合连接的记录进行分析就可以得出是哪个用户以及对应的源头IP。
实现步骤
1、创建审计用的库表。
#建库表代码
create database db_monitor ;
use db_monitor ;
CREATE TABLE accesslog (
id int not null auto_increment,
thread_id int(11) DEFAULT NULL, #进程id
log_time datetime default null, #登录时间
localname varchar(50) DEFAULT NULL, #登录名称,带详细ip
matchname varchar(50) DEFAULT NULL, #登录用户
primary key (id),
key idx_log_time(log_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '审计用户登录信息';
2、查询审计配置情况
show global variables like 'log_timestamps';
show global variables like '%general%';
解释:可以看到log时间戳是系统时间;此时log功能没打开
2-1、配置init-connect参数
这个参数是可以动态调整的,也注意要加到配置文件my.cnf中,否则下次重启后就失效了;
mysql> show variables like 'init_connect%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| init_connect | |
+---------------+-------+
1 row in set (0.00 sec)
mysql> set global init_connect='insert into db_monitor.accesslog(thread_id,log_time,localname,matchname) values(connection_id(),now(),user(),current_user());';
3、授予普通用户对accesslog表的insert权限
使用SQL语句获取授权语句:
mysql> select concat("grant insert on db_monitor.accesslog to '",user,"'@'",host,"';") from mysql.user; #拼结授权语句
+--------------------------------------------------------------------------+
| concat("grant insert on db_monitor.accesslog to '",user,"'@'",host,"';") |
+--------------------------------------------------------------------------+
| grant insert on db_monitor.accesslog to 'canal'@'%'; |
| grant insert on db_monitor.accesslog to 'credit_system_r'@'%'; |
| grant insert on db_monitor.accesslog to 'read_test'@'%'; |
| grant insert on db_monitor.accesslog to 'mysql.sys'@'localhost'; |
| grant insert on db_monitor.accesslog to 'root'@'localhost'; |
+--------------------------------------------------------------------------+
5 rows in set (0.00 sec)
-
注:这条语句只是列出了所有语句,需要手动复制再授权一下。
mysql> grant insert on db_monitor.accesslog to 'read_test'@'%' identified by '123456';
Query OK, 0 rows affected (0.00 sec)
grant select,insert on dba_test.* to 'test'@'%' identified by 'test';
注意: 对于所有的普通级别的用户,必须全部都要对日志表具有读写权限, 否则将导致没有权限的用户无法使用数据库。
☆注意:以后每添加一个用户都必须授权此表的插入权限,要不会连接不上。
accesslog表没有insert权限的用户(报错信息):
mysql> use qz_data_share;
No connection. Trying to reconnect...
Connection id: 59
Current database: *** NONE ***
ERROR 1184 (08S01): Aborted connection 59 to db: 'unconnected' user: 'read_test' host: 'localhost' (init_connect command failed)
4、设置配置文件init_connect参数并开启binlog
打开mysql配置文件:
Linux:vi /etc/my.cnf
配置mysql参数(重启时能生效):
[mysqld]
# 设置连接初始SQL
init_connect='insert into db_monitor.accesslog(thread_id,log_time,localname,matchname) values(connection_id(),now(),user(),current_user());'
# 开启binlog日志功能
log-bin=mysql-bin #添加这一行就ok
binlog-format=ROW #选择row模式
重启 MySql…
验证日志是否开启:
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
1 row in set (0.14 sec)
5、验证审计功能
查看审计用户登录信息
mysql> select * from db_monitor.accesslog;
+----+-----------+---------------------+---------------------+-------------+
| id | thread_id | log_time | localname | matchname |
+----+-----------+---------------------+---------------------+-------------+
| 1 | 4 | 2019-09-23 18:26:43 | [email protected] | read_test@% |
| 2 | 6 | 2019-09-23 18:31:50 | [email protected] | test@% |
+----+-----------+---------------------+---------------------+-------------+
注意:
init-connect 是不会在super用户登录时执行, 所以不要使用超级用户,不然你看不到任何信息。
理论上,用户每次连接时往数据库里插入一条记录,不会对数据库产生很大影响,考虑降低连接频率及accesslog插入效率优化。
对于所有的普通级别的用户,必须全部都要对日志表具有读写权限, 否则将导致,没有权限的用户无法使用数据库。
建议把记录表的引擎设置为archive,能极大的缩小空间。
查看binlog信息
获取binlog文件列表
mysql> show binary logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000001 | 169 |
| mysql-bin.000002 | 429 |
| mysql-bin.000003 | 9263 |
| mysql-bin.000004 | 1598 |
+------------------+-----------+
4 rows in set (0.00 sec)
mysql>
查看指定binlog文件的内容
mysql> show binlog events in 'mysql-bin.000004';
+------------------+------+----------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+------+----------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------+
| mysql-bin.000004 | 4 | Format_desc | 1813306 | 123 | Server ver: 5.7.20-log, Binlog ver: 4 |
| mysql-bin.000004 | 123 | Previous_gtids | 1813306 | 190 | 15ff3891-ddb6-11e9-8ded-000c291e45f0:1-33 |
| mysql-bin.000004 | 190 | Gtid | 1813306 | 251 | SET @@SESSION.GTID_NEXT= '15ff3891-ddb6-11e9-8ded-000c291e45f0:34' |
| mysql-bin.000004 | 251 | Query | 1813306 | 323 | BEGIN |
| mysql-bin.000004 | 323 | Table_map | 1813306 | 386 | table_id: 108 (db_monitor.accesslog) |
| mysql-bin.000004 | 386 | Write_rows | 1813306 | 463 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000004 | 463 | Xid | 1813306 | 490 | COMMIT /* xid=9 */ |
| mysql-bin.000004 | 490 | Gtid | 1813306 | 551 | SET @@SESSION.GTID_NEXT= '15ff3891-ddb6-11e9-8ded-000c291e45f0:35' |
| mysql-bin.000004 | 551 | Query | 1813306 | 691 | GRANT INSERT ON `db_monitor`.`accesslog` TO 'root'@'%' |
| mysql-bin.000004 | 691 | Gtid | 1813306 | 752 | SET @@SESSION.GTID_NEXT= '15ff3891-ddb6-11e9-8ded-000c291e45f0:36' |
| mysql-bin.000004 | 752 | Query | 1813306 | 955 | GRANT SELECT, INSERT ON *.* TO 'test'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29' |
| mysql-bin.000004 | 955 | Gtid | 1813306 | 1016 | SET @@SESSION.GTID_NEXT= '15ff3891-ddb6-11e9-8ded-000c291e45f0:37' |
| mysql-bin.000004 | 1016 | Query | 1813306 | 1164 | GRANT SELECT, INSERT ON `db_monitor`.`accesslog` TO 'test'@'%' |
| mysql-bin.000004 | 1164 | Gtid | 1813306 | 1225 | SET @@SESSION.GTID_NEXT= '15ff3891-ddb6-11e9-8ded-000c291e45f0:38' |
| mysql-bin.000004 | 1225 | Query | 1813306 | 1308 | flush privileges |
| mysql-bin.000004 | 1308 | Gtid | 1813306 | 1369 | SET @@SESSION.GTID_NEXT= '15ff3891-ddb6-11e9-8ded-000c291e45f0:39' |
| mysql-bin.000004 | 1369 | Query | 1813306 | 1441 | BEGIN |
| mysql-bin.000004 | 1441 | Table_map | 1813306 | 1504 | table_id: 108 (db_monitor.accesslog) |
| mysql-bin.000004 | 1504 | Write_rows | 1813306 | 1571 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000004 | 1571 | Xid | 1813306 | 1598 | COMMIT /* xid=33 */ |
+------------------+------+----------------+-----------+-------------+----------------------------------------------------------------------------------------------------------------------------------+
20 rows in set (0.00 sec)
mysql>
查看当前正在写入的binlog文件
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000004 | 1598 | | | 15ff3891-ddb6-11e9-8ded-000c291e45f0:1-39 |
+------------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.01 sec)
mysql>
mysqlbinlog工具查看binlog日志
查看全部日志
/usr/local/mysql/bin/mysqlbinlog --no-defaults -d qz_data_share /var/lib/mysql/mysql-bin.000001
基于开始/结束时间查询日志
/usr/local/mysql/bin/mysqlbinlog --no-defaults --start-datetime='2018-05-15 00:00:00' --stop-datetime='2018-05-15 23:01:01' -d qz_data_share /var/lib/mysql/mysql-bin.000001
基于pos值查询日志
/usr/local/mysql/bin/mysqlbinlog --no-defaults --start-position=0 --stop-position=1000 -d qz_data_share /var/lib/mysql/mysql-bin.000001
access-log表如何维护?
由于是一个log系统,推荐使用archive存储引擎,有利于数据厄压缩存放。如果数据库连接数量很大的话,建议一定时间做一次数据导出,然后清表。
为什么开启了已经保存了所有的操作和用户信息还需要再建立一个表保存用户的登陆信息呢?
1、无论sql有无语法错误,只要执行了就会记录,导致记录大量无用信息,后期的筛选有难度。因此加了表后可以根据一个大体的操作时间来进行筛选出这个时间段中执行这个操作的登陆用户的thread_id,然后再在表中查找这个thread_id号是哪个用户登陆的就可以很快筛选出执行具体某个操作的用户了。
2、因为新建的表普通用户没有权限删除记录,而log文件时可以删除记录的,所以若没有审计表的话若普通用户能接触保存log的那台主机,则能手动删除。