故障分析 | 从一则错误日志到 MySQL 认证机制与 bug 的深入分析

news2025/2/25 17:00:28

作者:李锡超

一个爱笑的江苏苏宁银行 数据库工程师,主要负责数据库日常运维、自动化建设、DMP平台运维。擅长MySQL、Python、Oracle,爱好骑行、研究技术。
本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


研发同学反馈某系统性能测试环境MySQL数据库相关的业务系统运行正常,但存在大量警告日志,需配合分析原因。

一、异常现象

mysql错误日志文件中存在大量如下信息:

2023-01-10T01:07:23.035479Z 13 [Warning] [MY-013360] [Server] Plugin sha256_password reported: ''sha256_password' is deprecated and will be removed in a future release. Please use caching_sha2_password instead'

关键环境信息

二、初步分析

当看到如上警告日志时,根据经验主义,第一反应应该是客户端的版本过低,其授权认证插件是服务端将要废弃的版本,所以产生了以上告警信息。特别是一些常见的客户端的工具,可能会由于更新频率,会很容易触发该问题。

尝试复现

根据初步分析建议,将初步分析建议与研发同学沟通后,通过常见的数据库工具访问数据库,看是否能否复现该错误。但通过数据库里面常见的数据库用户,通过不同的工具访问数据库,均未在访问时刻触发该异常。
由此,第一次尝试复现失败。难道是因为其它原因?

再第一次尝试访问的过程,通过实时观察数据库错误日志。在用客户端尝试访问的过程中,没有复现该错误。但是仍然看到对应的警告日志在持续输出到错误日志文件。且频率较高、间隔时间固定,由此也证明在错误不是数据库工具人工访问的。

应用系统运行正常,又不是客户端导致的!作为DBA的你,应该如何进一步分析呢?

初放小招

由于所处测试环境,针对该错误,可以执行如下操作启用MySQL的一般日志:

-- 开通一般日志:
show variables like 'general_log';
set global general_log=on;
show variables like 'general_log';
-- 查看一般日志路径:
show variables like 'general_log_file';

启用日志后,观察错误日志,发现一般日志中如下记录:

提示:发现异常后,立即关闭一般日志,避免产生过多日志耗尽磁盘空间:

-- 开通一般日志:
show variables like 'general_log';
set global general_log=off;
show variables like 'general_log';

即用户dbuser2 在问题时刻从10.x.y.43 服务器发起了访问数据库的请求。确认异常访问的用户和服务器后,检查数据库mysql.user表、skip-grant-tables等配置,发现数据库并不存在该用户,且没有跳过授权表等配置。使用该用户将无法登录到数据库。

将信息反馈至研发同学,很快就确认了是由于部分应用配置不合理,使用了不存在的数据库用户,并定时连接数据库执行任务。于是研发同学修改配置后,警告日志不再产生。

那么该问题分析到此,可以结束了么?

修改配置后,警告日志不在发生!但既然是不存在的用户,访问时为什么还提示认证插件将废弃呢?

三、源码分析

带着问题,首先想到的是:既然数据库用户为存在于mysql.user表,登录也会产生警告,难道这个用户是mysql的内部用户,被硬编码了么!于是取到对应版本源码,通过如下命令进行确认:

cd mysql-8.0.27/
grep -rwi "dbuser2" *

其访问结果为空,即不存在猜想的“内部用户”。

正常登录认证逻辑

既然没有硬编码,那就只能是内部逻辑导致。于是首先对正常情况下mysql用户登录过程,源码分析结果如下:

|—> handle_connection
  |—> thd_prepare_connection
    |—> login_connection
      |—> check_connection
        // 判断客户端的主机名是否可以登录(mysql.user.host),如果 mysql.user.host 有 '%' 那么将 allow_all_hosts,允许所有主机。
        |—> acl_check_host   
        |—> acl_authenticate
          |—> server_mpvio_initialize // 初始化mpvio对象,包括赋值 mpvio->ip / mpvio->host
          |—> auth_plugin_name="caching_sha2_password"
          |—> do_auth_once
            |—> caching_sha2_password_authenticate // auth->authenticate_user
              |—> server_mpvio_read_packet // vio->read_packet(vio, &pkt) // pkt=passwd
                |—> parse_client_handshake_packet
                  |—> char *user = get_string(&end, &bytes_remaining_in_packet, &user_len);
                  |—> passwd = get_length_encoded_string(&end, &bytes_remaining_in_packet, &passwd_len);
                  |—> mpvio->auth_info.user_name = my_strndup(key_memory_MPVIO_EXT_auth_info, user, user_len, MYF(MY_WME))
                  // 根据 user 搜索 mysql.user.host ,并与客户端的 hostname/ip 进行比较:匹配记录后,赋值 mpvio->acl_user
                  |—> find_mpvio_user(thd, mpvio) 
                    |—> list = cached_acl_users_for_name(mpvio->auth_info.user_name); // 根据 user 搜索 mysql.user.host 
                    |—> acl_user_tmp->host.compare_hostname(mpvio->host, mpvio->ip) //  与客户端的 hostname/ip 进行比较
                    |—> mpvio->acl_user_plugin = mpvio->acl_user->plugin; // 赋值 acl_user_plugin 属性为用户的plugin名
                    |—> mpvio->auth_info.multi_factor_auth_info[0].auth_string = mpvio->acl_user->credentials[PRIMARY_CRED].m_auth_string.str; 
                    |—> mpvio->auth_info.auth_string = mpvio->auth_info.multi_factor_auth_info[0].auth_string; 
                  |—> if (my_strcasecmp(system_charset_info, mpvio->acl_user_plugin.str,plugin_name(mpvio->plugin)->str) != 0)
                  |—> my_strcasecmp(system_charset_info, client_plugin,user_client_plugin_name) //检查客户端的认证插件与用户插件是否相同
              |—> make_hash_key(info->authenticated_as, hostname ? hostname : nullptr, authorization_id);  // 生成 authorization_id = user1\000% 
              |—> g_caching_sha2_password->fast_authenticate(authorization_id,*scramble,20,pkt,false) // 进行快速授权操作
                |—> m_cache.search(authorization_id, digest) // 根据 user、host 搜索密码,赋值到digest
                |—> Validate_scramble validate_scramble_first(scramble, digest.digest_buffer[0], random, random_length);
                |—> validate_scramble_first.validate(); // 校验 scramble
              // 如验证成功
              |—> vio->write_packet(vio, (uchar *)&fast_auth_success, 1)
              |—> return CR_OK;
              // 否则进行进行慢授权操作
              |—> g_caching_sha2_password->authenticate( authorization_id, serialized_string, plaintext_password);
          |—> server_mpvio_update_thd(thd, &mpvio);
          |—> check_and_update_password_lock_state(mpvio, thd, res);
          // 继续其它授权操作

即核心的认证操作在函数 caching_sha2_password_authenticate() 中执行,先调用函数find_mpvio_user(),通过user、hostname找到已经配置的用户,然后调用函数fast_authenticate()对密码进行快速验证。

使用不存在用户认证逻辑

当用户不存在时,mysql用户登录过程,源码分析结果如下:

|—> handle_connection
  |—> thd_prepare_connection
    |—> login_connection
      |—> check_connection
        // 判断客户端的主机名是否可以登录(mysql.user.host),如果 mysql.user.host 有 '%' 那么将 allow_all_hosts,允许所有主机。
        |—> acl_check_host   
        |—> acl_authenticate
          |—> server_mpvio_initialize // 初始化mpvio对象,包括赋值 mpvio->ip / mpvio->host
          |—> auth_plugin_name="caching_sha2_password"
          |—> do_auth_once
            |—> caching_sha2_password_authenticate // auth->authenticate_user
              |—> server_mpvio_read_packet // vio->read_packet(vio, &pkt) // pkt=passwd
                |—> parse_client_handshake_packet
                  |—> char *user = get_string(&end, &bytes_remaining_in_packet, &user_len);
                  |—> passwd = get_length_encoded_string(&end, &bytes_remaining_in_packet, &passwd_len);
                  |—> mpvio->auth_info.user_name = my_strndup(key_memory_MPVIO_EXT_auth_info, user, user_len, MYF(MY_WME))
                  |—> find_mpvio_user(thd, mpvio) 
                    |—> list = cached_acl_users_for_name(mpvio->auth_info.user_name); // 根据 user 搜索 mysql.user.host, 由于用户不存在,搜索不到记录
                    |—> mpvio->acl_user = decoy_user(usr, hst, mpvio->mem_root, mpvio->rand, initialized); // 
                      |—> Auth_id key(user);
                      // 判断是否用户存在于 unknown_accounts
                      |—> unknown_accounts->find(key, value)
                      // 如存在:
                      |—> user->plugin = Cached_authentication_plugins::cached_plugins_names[value];
                      // 如不存在:
                      |—> const int DECIMAL_SHIFT = 1000;
                      |—> const int random_number = static_cast<int>(my_rnd(rand) * DECIMAL_SHIFT);
                      |—> uint plugin_num = (uint)(random_number % ((uint)PLUGIN_LAST));
                      |—> user->plugin = Cached_authentication_plugins::cached_plugins_names[plugin_num];
                      |—> unknown_accounts->insert(key, plugin_num)
                    |—> mpvio->acl_user_plugin = mpvio->acl_user->plugin; // 赋值 acl_user_plugin 属性为用户的plugin名
                    |—> mpvio->auth_info.multi_factor_auth_info[0].auth_string = mpvio->acl_user->credentials[PRIMARY_CRED].m_auth_string.str; // ""
                    |—> mpvio->auth_info.auth_string = mpvio->auth_info.multi_factor_auth_info[0].auth_string; // ""
                    |—> mpvio->auth_info.additional_auth_string_length = 0; // 0
                    |—> mpvio->auth_info.auth_string_length = mpvio->auth_info.multi_factor_auth_info[0].auth_string_length; // 0
                  |—> if (my_strcasecmp(system_charset_info, mpvio->acl_user_plugin.str,plugin_name(mpvio->plugin)->str) != 0)
                  |—> return packet_error;
                |—> if (pkt_len == packet_error) goto err;
                |—> return -1;
          |—> auth_plugin_name = mpvio.acl_user->plugin;
          |—> res = do_auth_once(thd, auth_plugin_name, &mpvio);
            |—> sha256_password_authenticate() //auth->authenticate_user(mpvio, &mpvio->auth_info);
              |—> LogPluginErr // Deprecate message for SHA-256 authentication plugin.
              // 打印: 2023-01-10T01:07:23.035479Z 13 [Warning] [MY-013360] [Server] Plugin sha256_password reported: ''sha256_password' is deprecated and will be removed in a future release. Please use caching_sha2_password instead'
              |—> server_mpvio_read_packet() // vio->read_packet(vio, &pkt)
              |—> if (info->auth_string_length == 0 && info->additional_auth_string_length == 0) // info -> auth_info
              |—>   return CR_ERROR;
            |—> return res; // 0
          |—> server_mpvio_update_thd(thd, &mpvio);
          |—> check_and_update_password_lock_state(mpvio, thd, res); // 直接返回
          ...
          |—> login_failed_error // 打印登录报错信息
          // 2023-01-10T02:02:44.659796Z 19 [Note] [MY-010926] [Server] Access denied for user 'user2'@'localhost' (using password: YES)
      |—> thd->send_statement_status();  // 客户端终止

即当使用不存在的用户登录数据库时,通过函数 decoy_user() 创建的 acl_user 对象。在创建这个对象时,其 plugin 属性采用随机方式从 cached_plugins_enum 选择。从而有可能选择到 PLUGIN_SHA256_PASSWORD 插件。但在函数sha256_password_authenticate() 的入口,就会生成Warning级别的提示,以提示该验证PLUGIN_SHA256_PASSWORD 将被废弃。随后,由于在decoy_user() 创建的 acl_user 对象auth_string_length 长度未0,在后续的认证逻辑中会直接返回CR_ERROR,即认证失败。

根因总结

根据以上认证过的分析,导致错误日志存在 PLUGIN_SHA256_PASSWORD 将被废弃的根本原因为:在当前版本,当使用不存在的用户登录数据库时,mysql会随机选择用户的密码认证插件,在当前的版本版本中,有1/3的概率会选择到 PLUGIN_SHA256_PASSWORD 插件。选择该插件后,在后续的认证逻辑将会触发警告日志生成。

四、问题解决

综合以上分析过程,导致该问题的直接原因是应用配置了不存在的数据库用户,根本原因为数据库登录认证逻辑存在一定缺陷。那么解决该问题可参考如下几种方案:

1.参考初步分析中的方案,将应用的连接配置修改为正确的用户信息;

2.可以在mysql数据库中通过参数将该告警过滤,避免该告警信息输入到错误日志文件。相关配置如下:

show variables like 'log_error_suppression_list';
set global log_error_suppression_list='MY-013360;
show variables like 'log_error_suppression_list';

注意,使用该方案也会导致某个存在且使用SHA256_PASSWORD认证插件产生的告警。可以作为临时方案;

3.修改mysql代码,避免在使用不存在用户登录数据库时,选择 SHA256_PASSWORD认证插件。目前针对该方案已提交Bug #109635。

附:关键函数位置

find_mpvio_user() (./sql/auth/sql_authentication.cc:2084)
parse_client_handshake_packet() (./sql/auth/sql_authentication.cc:2990)
server_mpvio_read_packet() (./sql/auth/sql_authentication.cc:3282)
caching_sha2_password_authenticate() (./sql/auth/sha2_password.cc:955)
do_auth_once() (./sql/auth/sql_authentication.cc:3327)
acl_authenticate() (./sql/auth/sql_authentication.cc:3799)
check_connection() (./sql/sql_connect.cc:651)
login_connection() (./sql/sql_connect.cc:716)
thd_prepare_connection() (./sql/sql_connect.cc:889)
handle_connection() (./sql/conn_handler/connection_handler_per_thread.cc:298)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/427595.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis的MoreKey和BigKey问题

文章目录1、MoreKey案例2、BigKey案例1、MoreKey案例 大批量往Redis里面插入200w测试数据key 在Linux Bash下面执行&#xff0c;插入200w数据 for((i1;i<200*10000;i)); do echo "set k$i v$i">>/root/redisTest.txt;done;使用命令 tail -10 redisTest.t…

Android四大组件之 Activity的启动过程源码解析

前言 Activity是Android中一个很重要的概念&#xff0c;堪称四大组件之首&#xff0c;关于Activity有很多内容&#xff0c;比如生命周期和启动Flags&#xff0c;这二者想要说清楚&#xff0c;恐怕又要写两篇长文&#xff0c;更何况分析它们的源码呢。不过本文的侧重点不是它们…

小黑向携程进攻啦1:跟紧沛奇老师的携程步伐

为什么要学 异步非阻塞、asynciotornado、fastapi、django 3.x asgi、aiohttp都在异步->提升功能 如何讲解&#xff1f; 协程asyncio模块进行异步编程实战案例 1.协程 协程不是计算机提供&#xff0c;程序员人为创造出来的。协程&#xff0c;又称微线程&#xff0c;是一…

共聚焦显微镜——光伏产业制造智能化测量新技术

我国智能光伏应用持续升级&#xff0c;产业各环节产量又创新高。根据光伏行业规范公告企业信息和行业协会预测的数据来看&#xff0c;全国多晶硅、组件产量同比增长均超过60%&#xff0c;晶硅电池产品出口同比增长超过156%。 以共聚焦显微测量技术为原理、应用于材料生产领域的…

STM32的SRAM

文章目录SRAM的概念XM8A51216介绍特点连接线原理框图硬件连接图SRAM的配置使能 FSMC 时钟&#xff0c;并配置 FSMC 相关的 IO 及其时钟使能初始化FSMC&#xff0c;设置FSMC BANK1区域3使能 BANK1 区域 3FSMC读写代码SRAM的概念 SRAM的介绍 STM32F407ZGT6自带了 192K字节的 SRA…

[助人为乐]层次分析法

朋友论文需要用到层次分析法。于是回顾了一下。 相关资料推荐 层次分析法(AHP) 层次分析法&#xff08;AHP&#xff09;详细版本 用人话讲明白AHP层次分析法&#xff08;非常详细原理简单工具实现&#xff09; 层次分析法的流程图 构建层次结构模型(目标-准则-方案层) 深入…

认证服务---OAuth2.0基本介绍,微博登录整合到实际项目中【下篇】

前言 上一篇简单介绍了它的基本使用&#xff0c;这一篇就粗略说明一下如何在项目中实际应用 1、核心代码 1.1 认证微服务 当你进行了授权之后&#xff0c;跳转到一个新的地址。这个地址应该是你访问接口的地址。在这个接口中完成相应的access_token获取&#xff0c;以及调用…

docker-compose容器编排使用详解+示例

文章目录一、docker-compose概述1、产生的背景2、核心概念3、使用的三个步骤4、常用命令二、下载安装1、官方文档2、下载3、卸载三、使用compose1、前置知识&#xff0c;将一个springboot项目打包为镜像2、编写docker-compose.yml文件3、启动docker-compose4、停止一、docker-c…

[Linux]进程控制精讲,简单实现一个shell

目录 前言 进程创建 fork函数初识 写时拷贝 fork常见用法 fork调用失败的原因 进程终止 进程退出场景 进程退出码 查看进程退出码 退出码的含义 进程常见退出方法 exit VS _exit exit函数 _exit函数 二者的区别 return退出 进程等待 进程等待必要性 进程等待…

【Java文件操作】手把手教你拿捏IO 流

哈喽&#xff0c;大家好~我是保护小周ღ&#xff0c;本期为大家带来的是 Java 文件操作&#xff0c;理解文件的概念以及&#xff0c;常用的操作文件的类和方法&#xff0c;FileInputStream 类 和 FileOutputStream , PrintWriter and Scnner, Reader and Wirter 确定不来看看…

Python机器学习:集成学习

前两天看了SVM、逻辑回归、KNN、决策树、贝叶斯分类这几个很成熟的机器学习方法&#xff0c;但是&#xff0c;今天不看方法了&#xff0c;来看一种思想&#xff1a;集成学习&#xff1a; 先来看一下集成学习的基本原理&#xff1a;通过融合多个模型&#xff0c;从不同的角度降…

3.4 随机变量的相互独立性

学习目标&#xff1a; 要学习二维随机变量的相互独立性&#xff0c;我会按照以下步骤进行&#xff1a; 学习独立性的概念&#xff1a;在概率论中&#xff0c;两个事件A和B是相互独立的&#xff0c;当且仅当它们的概率乘积等于它们的联合概率&#xff0c;即P(A∩B)P(A)P(B)。将…

【Java EE】-网络编程(二) Socket(套接字) + Udp版本客户端服务器 +Tcp版本客户端服务器

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【JavaEE】 主要内容&#xff1a;传输层协议对应Socket编程&#xff0c;DatagramSocket&#xff0c;DatagramPacket&#xff0c;Udp版本的客户端和服务器&#xff0c;UdpEchoSeve…

大力出奇迹——GPT系列论文学习(GPT,GPT2,GPT3,InstructGPT)

目录说在前面1.GPT1.1 引言1.2 训练范式1.2.1 无监督预训练1.2.2 有监督微调1.3 实验2. GPT22.1 引言2.2 模型结构2.3 训练范式2.4 实验3.GPT33.1引言3.2 模型结构3.3 训练范式3.4 实验3.4.1数据集3.5 局限性4. InstructGPT4.1 引言4.2 方法4.2.1 数据收集4.2.2 各部分模型4.3 …

【轻NAS】Windows搭建可道云私有云盘,并内网穿透公网访问

文章目录1.前言2. Kodcloud网站搭建2.1. Kodcloud下载和安装2.2 Kodcloud网页测试3. cpolar内网穿透的安装和注册4. 本地网页发布4.1 Cpolar云端设置4.2 Cpolar本地设置5. 公网访问测试6.结语1.前言 云存储作为近些年兴起的概念&#xff0c;成功吸引了各大互联网厂商下场&…

thingsboard ARM网关

G5501边缘计算网关 G5501是采用中高端的通用型 SOC&#xff0c;一款4 核 arm 架构 A55 处理器的 网关设备。标配处理器为 Cortex-A55 四核&#xff0c;最高主频 2GHz 的处理器&#xff0c; 内置 4GB DDR4 内存&#xff0c;32GB eMMC 存储。 集成Mali G52 2EE 图形处理器GPU&am…

matplotlib设置中文字体为微软雅黑

matplotlib无法设置任何中文字体怎么办&#xff1f; 如何在linux系统下让matplotlib显示中文&#xff1f; 下载微软雅黑字体&#xff0c;把它放在某个目录下。 链接&#xff1a; https://pan.baidu.com/s/1SCLYpH_MzY7vn0HA0wxxAw?pwdft2j 提取码&#xff1a;ft2j 在代码中加…

Learning C++ No.18【STL No.8】

引言&#xff1a; 北京时间&#xff1a;2023/3/18/21:47&#xff0c;周末&#xff0c;不摆烂&#xff0c;但是欠钱终于还是遭报应了&#xff0c;导致坐牢7小时&#xff08;上午3.5&#xff0c;下午3.5&#xff09;&#xff0c;难受&#xff0c;充分意识到行哥是那么的和蔼可亲…

DLRover: 云上自动扩缩容 DeepRec 分布式训练作业

背景 如今&#xff0c;深度学习已广泛应用在搜索、广告、推荐等业务中&#xff0c;这类业务场景普遍有两个特点&#xff1a; 1&#xff09;训练样本量大&#xff0c;需要分布式训练提升训练速度&#xff1b; 2&#xff09;模型稀疏&#xff0c;即模型结构中离散特征计算逻辑占…

强训之【走方格的方案数和另类加法】

目录1.走方格的方案数1.1题目1.2思路讲解1.3代码展示2.另类加法2.1题目2.2思路讲解2.3代码展示3.选择题1.走方格的方案数 1.1题目 链接: link 描述 请计算n*m的棋盘格子&#xff08;n为横向的格子数&#xff0c;m为竖向的格子数&#xff09;从棋盘左上角出发沿着边缘线从左上…