数据库口令从代码搬进凭据托管,轮换从半天变成两分钟
数据库口令从代码搬进凭据托管,轮换从半天变成两分钟
上周给一个测试库换口令。改完发现要发版——那串字符在代码里写死了三处:配置类一份、启动脚本一份、连接池初始化一份。逐处改完,还得挨个重启。
漏的地方第二天早上才暴露:服务起不来,日志里一句 access denied。
这件事本身不新鲜,很多人忍着。但它把一个问题摊平了:口令写在代码里,你连「换掉它」这个动作都做不了。做不了就不会去做;不换,那串字符就一直有效。
这次我把它搬进了凭据管理。下面记的是配置过程和我实际改了什么——过程点到为止,重点在后面那张对比。
一、动手之前,先列清楚要什么
挑工具容易,想清楚要什么难。动手之前我列了四条,按轻重排:
第一,换口令不能发版。这是主诉。做不到这条,后面都是空谈。
第二,要知道谁在用。库里只有一个账号app,五个人、三台机器、两个环境都在用它。审计日志里只有app,问不出具体是谁。
第三,出事能立刻作废。半夜发现异常访问,需要的是「一分钟内让这串字符失效」,不是「明天上午走个变更」。
第四,环境之间别共用一份配置。测试、预发、生产共用一份样例,改一处漏一处——我这次就是漏的。
四条里前三条凭据管理能解,第四条得靠流程,工具替不了。这个边界一开始就该认下来,否则后面容易指望错。
二、配置是怎么走的
真配起来是四步,比预想的少,但第一步是我原来没想到的。
第一步,建根凭据。这是整个机制的地基:把数据库的管理员账号托管进来——数据库类型、主机、端口、库名、管理员账号和口令,系统当场验证连接。验证通过,这条根凭据才能用来派生别的。这一步可以勾「开启轮转」,勾了之后管理员口令按周期自动更新。
第二步,建子凭据。类型选「动态」,然后指定几件事:挂在哪个根凭据下、服务类型要和根凭据一致、有效时间多长、最大有效时间多长、权限是继承根凭据还是自定义增删改查、到期前要不要自动续约。
这里有个容易忽略的约束:有效时间不能超过最大有效时间,而最大有效时间是硬上限——子凭据存在的时间超过它之后自动失效,续约也救不回来。
第三步,关联用户。子凭据建完不归任何人。要把用户挂上去,对方才拿得到。
第四步,应用接入。应用侧不再读配置,改成调接口取。取回来的东西两种类型还不一样:静态凭据解出来直接是账号口令,动态凭据解出来是带有效期的一小段 JSON。
静态凭据这一步短得多:不依赖根凭据,一条独立记录,填名称、值、用途和一个加密方式就完了。
三、静态和动态,差的不是有效期
这是我配之前理解错的地方。
我原来以为两者的区别是有效期长短——静态不限期、动态几分钟。真配下去才发现不是:区别在账号从哪来,以及要不要把管理员账号交给系统。
静态凭据里,账号是你自己的。你在数据库里建好一个账号,把口令填进凭据管理,系统负责加密存着、按权限发给该拿的人。它不需要知道怎么连你的库。
动态凭据里,账号是系统替你建的。所以要先把管理员账号(根凭据)交给它,它才能连上库、按需派生临时账号、到期失效。这就是动态凭据必须挂在根凭据下面、服务类型必须跟根凭据一致的原因。
差别决定了选型:
| 静态凭据 | 动态凭据 | |
|---|---|---|
| 前置条件 | 无,独立存在 | 必须先有根凭据(管理员账号) |
| 账号从哪来 | 你自己在库里建 | 系统按根凭据派生 |
| 系统的权限 | 只存密文,不碰你的库 | 能动你的库(建号、回收) |
| 有效期 | 没有这个概念 | 有效时间 + 最大有效时间(硬上限) |
| 授权粒度 | 用户,分「查看」「使用」两档 | 用户,权限可继承根凭据或自定义 |
所以这不是「先进与落后」的选择,是信任边界的选择:愿不愿意把管理员账号交给一个系统。
产品里的动态凭据也不止数据库一种。同一套「根凭据派生」的模型还用在 SSH(OTP 一次性密码和密钥对两种方案)和主机凭据(Windows/Linux 本地账户按周期轮换)上。这次我只跑了数据库这一条路。
代价分开说,不然就是只讲好话。
动态的代价主要不在代码上:交出管理员账号是一次实质的信任转移,不是配置问题;系统要能连上目标库,网络策略得开。应用侧还有一件必须做的事——凭据是有状态的(有效、已过期、已吊销、申请中),到期前可以续约,过期后得重新申请。写成「取一次用到底」,半夜就会挂。
静态的代价更隐晦:它解决「存」和「发」,不解决「换」。谁来轮换、多久轮一次,还是人的事。放进保险箱和定期换锁,是两件事。
四、配好之后,实际变了什么
这部分是我最想记的。功能列表谁都会写,实际变了多少,得自己用一段时间才知道。
链路的长度其实没变,还是那么五步。变的是口令驻留在哪几个环节:改造前它跟着代码走完整条链,改造后只在取用那一下出现,而且自己会过期。这也是为什么「换口令」从一件要排期的事,变成了一个点一下的动作——它不必再碰应用、代码仓库、部署机器这三样。
再展开看细节:
| 维度 | 之前(写在代码里) | 之后(凭据托管) |
|---|---|---|
| 换一次口令 | 改代码 + 提测 + 发版 + 重启,半天起步 | 在凭据上执行轮转,两分钟 |
| 紧急作废 | 走一遍发版流程 | 禁用或吊销,立即生效 |
| 谁在用 | 只知道账号名 | 授权名单落到具体用户 |
| 环境隔离 | 共用一份配置样例 | 每环境一套根凭据与子凭据,各自授权 |
| 过期 | 没有这个概念 | 静态靠人轮换,动态按有效时间自动失效 |
| 出事排查 | 翻代码找哪几处写死了 | 查这条凭据的取用记录 |
| 人员交接 | 口令跟着人走 | 改授权就行,不必把口令告诉谁 |
表里真正的变化不在「安全」两个字上,在第一行:换口令从「一个项目」变成了「一个动作」。
以前换口令要排期、要等发版窗口、要通知回归。流程这么重,结果就是没人愿意换——一年一次都算勤快。现在两分钟的事,才会真的按季度去做。
安全收益是这件事的副产品。被改掉的是「要不要换」这个判断本身——它不再由成本决定。
还有一条不那么显眼的变化:排查的时候终于有据可查。以前库里出现异常连接,只能对着代码猜是哪台机器、哪个人;现在能顺着授权名单往回追。
五、几条验收判据
真要搬,按这几条过一遍再算完:
| # | 检查项 | 判据 |
|---|---|---|
| 1 | 代码里还有没有硬编码 | 全仓库检索口令片段,命中为 0 |
| 2 | 该用哪种类型 | 账号由自己管 → 静态;要按需建号 → 动态 |
| 3 | 动态的前置 | 根凭据已验证连通,服务类型与根凭据一致 |
| 4 | 两个时间 | 有效时间不超过最大有效时间,且最大有效时间设了上限 |
| 5 | 续约策略 | 到期前自动续约开不开,应用侧处理了续约与重新申请两条分支 |
| 6 | 归属 | 授权名单非空,且能对应到具体用户 |
| 7 | 应用侧缓存 | 缓存时间小于凭据有效期 |
| 8 | 数据库侧回收 | 动态凭据要有建号回收的对应动作 |
第 1 条和第 5 条常挂。前者是因为「就这一次」的临时脚本会沉淀成固定资产;后者是把凭据当静态配置用,取回来就存着不放手——到期了还在用旧口令,表现是夜里开始报连接失败。
收尾
口令该放哪,判断标准就一条:能不能不发版就作废,以及会不会自己过期。写在代码里两条都不满足,托管起来满足第一条,动态凭据两条都满足。
在安当,我们做这一层时有两个取舍,和上面的过程正好对得上。
一是静态凭据的权限分「查看」和「使用」两档。能看不等于能用,看得见也不等于能取到明文。多一档权限就多一次配置,换来的是出了问题能收窄到「谁看过」和「谁用过」两种不同的追责路径。
二是值写进去之后不回读,控制台只给摘要。对拿它当配置中心用的人是限制,但它把「谁能看到明文」从权限配置问题变成了架构问题——不是靠约定没人去看,是结构上看不着。
这两条的代价我们也认:多一档权限要多配一遍,排障时看不到原文得靠取用记录对。我们宁可留着这两处不方便,因为把它们改顺手的代价,是出事那天答不出「谁在用、什么时候开始用的」。
文章作者:安当加密技术负责人