MySQL 8.0.x中,我们如果按安全规范配置了账号密码过期策略的话,那么如何查看账号密码还有多长时间就会过期;如何做好账号密码过期监控;以及提前及时处理账号密码过期问题就是DBA必须处理的一些事情。这里简单讨论一下这些事情。个人经验仅供参考,如有不足或错误的地方,敬请指正一二。这篇文章的具体测试环境为MySQL 8.0.35.

设置账号密码过期

一般系统变量default_password_lifetime控制着全局范围的账号密码过期时间。

mysql> show variables like 'default_password_lifetime';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| default_password_lifetime | 180   |
+---------------------------+-------+
1 row in set (0.02 sec)

mysql> 

这个系统系统变量可以在my.cnf中设置。也可以在MySQL运行时设置并持久化。

SET PERSIST default_password_lifetime = 365;

你创建账号时可以指定账号密码过期使用默认的全局策略,也可以不指定,它也会使用全局的密码过期策略,如下所示, 下面两个SQL其实是等价的。

create user test2@'%' identified by random password ;
create user test2@'%' identified by random password password expire default;

另外,你也可以在创建账号时设置账号密码过期时间,如下所示,那么此过期时间会覆盖系统变量default_password_lifetime的值(优先级高于全局变量)。这个值也会记录到系统表mysql.user中password_lifetime中,如果创建用户时没有指定账号密码过期期限,而是使用全局策略,那么此时password_lifetime的值为NULL。

CREATE USER 'test'@'%' IDENTIFIED BY RANDOM PASSWORD PASSWORD EXPIRE INTERVAL 20 DAY;

手动设置账号密码过期

我们也可以手工设置账号密码过期,如下所示:

ALTER USER 'USER_NAME'@'HOSTNAME' PASSWORD EXPIRE;
ALTER USER 'USER_NAME'@'HOSTNAME' PASSWORD EXPIRE INTERVAL 90 DAY;

如果手工将一个账号手工设置为账号密码过期,那么系统表mysql.user中的password_expired会变为Y,而密码全局过期策略下过期的账号的字段password_expired则永远为N。这些细节不知你有没有注意过。

查询账号密码过期期限

根据MySQL密码过期策略,密码过期是自动的,并基于密码期限,对于给定帐户,密码期限是从其最近一次密码更改的日期和时间开始评估的。系统表为每个帐户指示上次更改其密码的时间(mysql.user表的password_last_changed字段值),如果密码的期限大于其允许的生存期,服务器会自动将密码视为在客户端连接时已过期。这适用于没有明确的手动设置密码过期的账户。

查看数据库中账号密码还有多久即将过期,可以使用下面这个SQL:

select user
     , host
     , plugin
     , password_expired
     , password_last_changed
     , if(ifnull(password_lifetime, CAST(@@default_password_lifetime as signed))<1,'NEVER',
       concat(
            cast(   ifnull(password_lifetime, @@default_password_lifetime) as signed)
          + cast(datediff(password_last_changed, now()) as signed), " days")) as 
       days_till_expires
from mysql.user;

或者使用下面优化调整过的SQL

--如果使用ALTER USER 'test'@'%' PASSWORD EXPIRE命令使账号密码过期,但是days_till_expires依然显示账号密码还有N天才过期。
select user
     , host
     , plugin
     , password_expired
     , password_last_changed
     , if(password_expired='Y','password_expired',
          if(ifnull(password_lifetime, CAST(@@default_password_lifetime as signed))<1,'NEVER',
          concat(
               cast(   ifnull(password_lifetime, @@default_password_lifetime) as signed)
             + cast(datediff(password_last_changed, now()) as signed), " days")))  
       as days_till_expires
from mysql.user;

监控账号密码过期

监控账号密码过期,我们可以使用python脚本监控MySQL账号密码过期问题,如果密码小于N(N可以是30,60)天即将过期,那么发出下面告警邮件:

处理即将密码过期账号

对于即将过期的账号密码,分两种情况:

1:password_lifetime为null,default_password_lifetime不为null的情况,重置密码后,它的过期期限变为default_password_lifetime的值。

mysql> alter user zbx_monitor@'localhost' identified by "*******";
Query OK, 0 rows affected (0.04 sec)

2:password_lifetime不为null的情况(手工设置密码过期期限的情况),那么你可以有以下几种方式来处理

mysql> alter user test@'%' identified by "******";
Query OK, 0 rows affected (0.01 sec)

此时password_lifetime会变为之前指定的过期期限,当然,你也可以在修改账号密码时指定过期期限。

#不修改账号密码

mysql> alter user test@'%'  password expire interval 180 day;
Query OK, 0 rows affected (0.01 sec)

#修改账号密码

mysql> alter user test@'%' identified by "******" password expire interval 190 day;
Query OK, 0 rows affected (0.06 sec)

账号密码过期后,依然能连接上MySQL,但是此时做任何操操作都会报ERROR 1820(HY000)错误,如下所示:

mysql> select * from dual;
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
mysql> 

从 MySQL 8.0.14 开始,允许用户帐户拥有双重密码,指定为主密码和辅助密码。双密码功能可以在以下场景中无缝执行凭据更改:

  • 一个系统有大量的MySQL服务器,可能涉及复制。

  • 多个应用程序连接到不同的 MySQL 服务器。

  • 必须对应用程序用来连接服务器的一个或多个帐户定期更改凭据。

DBA如果经历过的,也许都知道有时候修改数据库账号密码是比较麻烦的事情,系统开发人员或系统Support人员会觉得比较麻烦,因为对于复杂系统,一不小心,账号密码配置出了差错或纰漏,可能会导致生产事故,另外,修改账号密码也会影响系统业务的可用性(可能需要短暂的停止应用系统服务...),所以对于定期修改账号密码,很多时候是名存实亡。因此要么不会修改数据库账号密码,要么按规范定期修改账号密码,也是使用相同的账号密码,并不会修改账户密码。

如果使用双密码,就可以更轻松地分阶段修改密码,无需密切合作,也无需停机。确实是一个很了不起的创新。其他数据库暂时还没有这种特性。

#建立新的主密码,保留当前密码作为辅助密码

ALTER USER 'test'@'%' IDENTIFIED BY '****' RETAIN CURRENT PASSWORD;

#等到应用程序切换密码后,丢弃旧密码(辅助密码)

ALTER USER 'test'@'%' DISCARD OLD PASSWORD;