Author: 巴彦
本文基于mysql 8.0.13.
MySQL 5.6.35 开始提供Connnection Control 插件;
如果客户端在连续失败登陆一定次数后,那么此插件可以给客户端后续登陆行为的响应增加一个延迟。该插件可以防止恶意暴力破解MySQL账户。该插件包含以下2个组件:
- CONNECTION_CONTROL:检查mysql的刚建立连接的响应是否需要延迟,并且提供一些系统变量和状态参数;方便用户配置插件和查看此插件基本的状态。
- CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS:提供了一个INFORMATION_SCHEMA类型的表,用户在此表中可以查看更详细关于登陆失败连接的信息。
安装可以通过配置文件静态安装,也可以在MySQL中动态安装。
静态安装
-- 配置文件增加以下配置
[mysqld]
plugin-load-add = connection_control.so
动态安装
-- 插件动态安装启用
mysql> INSTALL PLUGIN CONNECTION_CONTROL SONAME 'connection_control.so';
mysql> INSTALL PLUGIN CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS SONAME 'connection_control.so';
-- 验证是否正常安装
mysql> SHOW PLUGINS;
卸载
-- 插件卸载
UNINSTALL PLUGIN CONNECTION_CONTROL;
UNINSTALL PLUGIN CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS;
更多关于插接件安装/卸载的信息 请点击
从上一小节的基本原理我们知道Connection Control插件主要是通过订阅处理MYSQL_AUDIT_CONNECTION_CONNECT与MYSQL_AUDIT_CONNECTION_CHANGE_USER事件来实现的。
主要处理流程如下:
//创建一个新线程,处理新连接
handle_connection() in connection_handler_per_thread.cc
|
| //准备工作
->thd_prepare_connection() in sql_connect.cc
|
| //进行登陆操作
->login_connection() in sql_connect.cc
|
| //对此连接的有效性进行验证
->check_connection() in sql_connect.cc
|
| //验证登陆
->acl_authenticate() in sql_authentication.cc
|
| //对登陆连接事件进行处理
->mysql_audit_notify() in sql_audit.cc
|
| //对登陆连接事件进行处理,并获得错误码
->mysql_audit_notify() in sql_audit.cc
|
| //获取需要处理登陆事件的插件
->mysql_audit_acquire_plugins() in sql_audit.cc
|
| //将连接事件分发,并按照需求是都获取插件处理的返回值
->event_class_dispatch_error() in sql_audit.cc
|
| //将连接事件分发
->event_class_dispatch() in sql_audit.cc
|
| // 调用插件的相关处理函数处理连接事件
->plugins_dispatch() in sql_audit.cc
|
| //检查当前插件是否需要处理此事件
->check_audit_mask()in sql_audit.cc
|
| //connection_control处理连接事件
->connection_control_notify() in connection_control.cc
|
| //依次遍历订阅了连接事件的订阅者处理此事件
->notify_event() in connection_control_coordinator.cc
|
| //处理连接事件
->notify_event() in connection_delay.cc
下面我们主要看一下最终Connection Control插件是怎么处理连接事件的。
/**
@brief Handle a connection event and if requried,
wait for random amount of time before returning.
We only care about CONNECT and CHANGE_USER sub events.
@param [in] thd THD pointer
@param [in] coordinator Connection_event_coordinator
@param [in] connection_event Connection event to be handled
@param [in] error_handler Error handler object
@returns status of connection event handling
@retval false Successfully handled an event.
@retval true Something went wrong.
error_buffer may contain details.
*/
bool Connection_delay_action::notify_event(
MYSQL_THD thd, Connection_event_coordinator_services *coordinator,
const mysql_event_connection *connection_event,
Error_handler *error_handler) {
...
// 只关注CONNECT与CHANGE_USER事件
if (subclass != MYSQL_AUDIT_CONNECTION_CONNECT &&
subclass != MYSQL_AUDIT_CONNECTION_CHANGE_USER)
DBUG_RETURN(error);
RD_lock rd_lock(m_lock);
int64 threshold = this->get_threshold();
// 拿到当前阈值检查阈值是否有效,DISABLE_THRESHOLD=0
if (threshold <= DISABLE_THRESHOLD) DBUG_RETURN(error);
int64 current_count = 0;
bool user_present = false;
Sql_string userhost;
make_hash_key(thd, userhost);
DBUG_PRINT("info", ("Connection control : Connection event lookup for: %s",
userhost.c_str()));
// 获取到当前失败登陆的次数
user_present = m_userhost_hash.match_entry(userhost, (void *)¤t_count)
? false
: true;
// 如果失败次数超过阈值,无论这次连接成功与否,都需要延迟
// 同时更新统计信息
if (current_count >= threshold || current_count < 0) {
ulonglong wait_time = get_wait_time((current_count + 1) - threshold);
if ((error = coordinator->notify_status_var(
&self, STAT_CONNECTION_DELAY_TRIGGERED, ACTION_INC))) {
error_handler->handle_error(
ER_CONN_CONTROL_STAT_CONN_DELAY_TRIGGERED_UPDATE_FAILED);
}
// 在产生延迟时,需要释放读写锁,以减少锁的粒度
// 防止阻塞对于IS table的数据访问
rd_lock.unlock();
conditional_wait(thd, wait_time);
rd_lock.lock();
}
if (connection_event->status) {
// 如果此次登陆失败,那么更新LF Hash
if (m_userhost_hash.create_or_update_entry(userhost)) {
error_handler->handle_error(
ER_CONN_CONTROL_FAILED_TO_UPDATE_CONN_DELAY_HASH, userhost.c_str());
error = true;
}
} else {
// 如果此次登陆成功并且LF Hash中有数据,那么就删除LF Hash中的数据
if (user_present) {
(void)m_userhost_hash.remove_entry(userhost);
}
}
DBUG_RETURN(error);
}
1,通过分析Connection Control处理流程与具体实现,我们可以知道插件是如何来处理连接事件的。
2,该插件虽然可以防止恶意暴力破解MySQL账户,但是可能会浪费MySQL的资源;
- 比如如果短时间内有大量的恶意攻击,该插件虽然可以防止破解mysql账户,但是会消耗主机资源(每一个连接创建一个线程);
- 如果这里使用了线程池,虽然可以避免消耗主机资源,但是等线程池中的线程被消耗光,再有新连接来就会拒绝服务。