hashicorp vault通过database secrets engine实现数据库密码动态管理:按需生成带租约的临时账号(如vault-hr-app-123456),1小时后自动删除,结合acl/rbac策略与审计日志,达成“用完即焚”式安全轮换。

生产环境不能靠手动改密码再重启应用来轮换账号密码——这会导致连接中断、权限不一致、密钥残留,必须用原子化、可验证、无感切换的方式完成。
ALTER USER PASSWORD EXPIRE 仅设过期,不等于自动轮换
MySQL 没有内置的“定时改密码”机制。ALTER USER 'app_user'@'10.20.30.%' PASSWORD EXPIRE; 只是把密码标记为过期,下次用户连接时会强制要求重置(ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement),但应用层通常无法响应这种交互式提示,直接报错断连。
- 该语句适合人工运维场景,比如 DBA 登录后手动执行
ALTER USER ... IDENTIFIED BY,不适合应用账号 - 过期时间无法批量设置或按策略触发,
PASSWORD EXPIRE INTERVAL 90 DAY是创建时设定的静态值,不支持运行中动态调整 - 一旦密码过期而应用未适配,所有连接池里的空闲连接在复用时都会失败,错误日志里大量出现
ERROR 1820
真正可行的轮换路径:密钥服务 + 应用热加载
密码轮换的本质不是“让 MySQL 换密码”,而是“让应用在不重启的前提下切换到新凭据”。MySQL 侧只需接受新密码,其余逻辑由外部控制。
- 用 HashiCorp Vault / AWS Secrets Manager / 阿里云 KMS 等服务托管
app_user的密码,设置 TTL 和轮换策略 - 应用启动时从密钥服务拉取当前密码,并缓存;同时注册回调,在密钥变更时触发凭据刷新
- 数据库侧同步执行:
ALTER USER 'app_user'@'10.20.30.%' IDENTIFIED BY 'new_strong_password';(注意:必须用完整 host 匹配) - 连接池(如 HikariCP、mysql-connector-python 的 pool_reset_session)需支持凭据热更新,否则旧连接仍用老密码,直到自然淘汰
绕不开的兼容性坑:caching_sha2_password 插件与客户端版本
MySQL 8.0 默认用 caching_sha2_password 认证插件,但很多旧客户端(Navicat ≤15、MySQL Connector/J ERROR 1045 (28000): Access denied,而非密码错误。
- 确认当前用户插件:
SELECT User, Host, plugin FROM mysql.user WHERE User = 'app_user'; - 若需兼容旧客户端,建户或改密时显式指定插件:
ALTER USER 'app_user'@'10.20.30.%' IDENTIFIED WITH mysql_native_password BY 'new_pass'; - 但注意:
mysql_native_password不支持密码缓存优化,高并发下握手开销略高
验证是否真生效:别只查 mysql.user 表
SELECT authentication_string FROM mysql.user 显示的是哈希值,看不出密码是否已更新。真正要验证的是连接行为和权限边界。
- 用新密码直连测试:
mysql -u app_user -p -h db-prod.internal -D myapp_db -e "SELECT 1;",确认能通且无权限错误 - 用旧密码尝试连接,应明确拒绝(不是超时或网络错误),证明旧凭据已失效
- 执行
SHOW GRANTS FOR 'app_user'@'10.20.30.%';,确保权限没被意外覆盖或降级 - 检查应用日志里是否有
Access denied或连接池初始化失败记录,尤其关注滚动发布期间第一批 Pod 的启动日志
最常被忽略的一点:轮换后必须清理所有残留的旧密码引用——CI/CD 流水线变量、K8s Secret 的历史版本、本地 .env 文件备份、甚至监控脚本里的硬编码连接串。一次轮换不干净,等于没轮换。











