Secrets 管理四大原则:最小权限、轮换、审计、加密存储

说实话,我早期管 secrets 的时候,踩的坑十个手指头数不过来。后来我慢慢摸出规律——其实 secrets 管理就四个核心原则。你把这四条咬死了,至少 80% 的事故能提前挡住。下面我一条一条掰开讲,你对照自己的项目看看,哪条还没做到位。

原则一:最小权限(Least Privilege)

最小权限,说白了就是“够用就行”。别给一个只读数据库的 配上 delete 权限,更别把生产环境的密钥塞到所有人能拉的 CI 变量里。我自己的习惯是:每个 secret 只绑定一个具体的操作范围,而且不搞永久授权。

我的经验:我在一个微服务项目里遇到过开发人员为了省事,把 AWS 的 IAM 密钥直接嵌在代码里,还给了 AdministratorAccess。后来不小心把代码推到了公开仓库……嗯,那天的告警音我现在还记得。从那以后我强制所有 secret 都走细粒度策略,哪怕是读取配置文件也只能用 ReadOnly 角色。

实现最小权限时,常用的手段有:

  • 角色分离:读写分离、管理 vs 使用分离。比如数据库连接密码只给 SELECT 权限,写操作走独立的服务账号。
  • 临时凭证:用 STS / 短期 ,过期就废。尽量避免长期静态密钥。
  • 动态授权:Vault 的 Dynamic Secrets 或 AWS Secrets Manager 的轮换策略,每次获取都生成一次性的临时凭证。
避坑指南:别迷信“只要我加密了就安全”。密钥的拥有者数量才是真正的攻击面。我建议你每季度拉一次 list,清掉那些没人记得、没人用的幽灵权限。

原则二:轮换(Rotation)

密钥不轮换,就像家里的锁一直不换。你想想看,就算当初分发安全,半年后谁知道 key 的副本流落到哪里了?轮换的核心就一句话:自动化、无感知、频率可控。

我见过一个团队手动改数据库密码,改完后才发现有 6 个服务还在用旧密码,整整断连了 40 分钟。所以轮换不能靠人肉,必须靠系统。

密钥类型 建议轮换周期 轮换方式
数据库密码 30 天 双版本 + 渐变切换
API 令牌 7 天 或 按会话 自动生成 + 旧令牌延迟删除
SSH 密钥对 90 天 托管 CA,自动签新证
JWT 签名密钥 按发行策略(通常 1h–24h) 密钥集 + kid 指向
我曾经踩过的坑:轮换时没做灰度,直接把旧密钥删了。结果有个定时任务用了旧 key 且没有重试机制,直接炸了。后来我改成“双缓冲”模式——新 key 先写进去,旧 key 至少保留一个完整轮换周期,等所有消费者都切过去再删除。

原则三:审计(Audit)

审计不是用来事后甩锅的,而是用来发现“异常访问模式”。我自己的信条是:每一次 secrets 的读取、写入、删除都必须留下不可篡改的日志。

为什么要这样做?因为你根本不知道哪个开发者偷偷 copy 了线上数据库密钥。审计日志就是你的摄像头。我记得有一次排查泄漏事故,全靠 Vault 的 audit log 定位到具体是哪个 CI Job 在哪个 commit 里拉取过敏感 secret。

  • 日志必须集中存储:别散落在各个节点上。我一般用 ELK 或 Splunk,开启 audit_mode=strict。
  • 做告警基线:比如“一个服务每分钟拉取同一个 secret 超过 10 次”就报 alert。这往往意味着被暴力遍历或代码写死了重试。
  • 保留至少 90 天:合规要求有的行业要保留 1 年。注意日志不能回写。
一个小技巧:审计日志里可以加一层“指纹码”,比如给每条记录附加一个 HMAC,防止被内部员工悄悄修改日志库。你想想看,如果攻击者连审计日志都改了,那你就彻底瞎了。

原则四:加密存储(Encryption at Rest & in Transit)

这点最直观,但也最容易忽视细节。加密存储包含两层:传输层和存储层。

传输层——所有 secrets 的读写必须走 TLS 1.2+。我见过有人用 HTTP 从本地直接读取远程 vault,理由是内网安全……内网就没风险吗?内网被横向移动后,明文流量等于裸奔。

存储层——我建议至少使用 AES-256-GCM。而且密钥本身不能明文落盘。要用 KMS 或 HSM 来 wrap 加密密钥,形成“密钥链”。具体做法:数据用 DEK(数据加密密钥)加密,DEK 用 KEK(密钥加密密钥)加密,KEK 存在 KMS 里。应用拿到的永远是密文,想要解密必须过 KMS 的权限检查。

我的个人习惯:在代码里从不写任何静态加密算法的硬编码 IV。每次加密都随机生成 IV 并和密文拼在一起存储。这样即使两个完全相同的 secret,密文也不会一样,防止侧信道分析。
import os, struct
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def encrypt(data: bytes, key: bytes) -> bytes:
    iv = os.urandom(12)  # 随机 IV
    cipher = Cipher(algorithms.AES(key), modes.GCM(iv))
    encryptor = cipher.encryptor()
    ct = encryptor.update(data) + encryptor.finalize()
    # 返回 iv + ciphertext + tag
    return iv + ct + encryptor.tag

def decrypt(payload: bytes, key: bytes) -> bytes:
    iv, ct_tag = payload[:12], payload[12:]
    ct, tag = ct_tag[:-16], ct_tag[-16:]
    cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag))
    decryptor = cipher.decryptor()
    return decryptor.update(ct) + decryptor.finalize()

你注意看上面代码里,我没有把密钥管理写在里面。因为真正生产环境下,key 是从 KMS 或 Vault 的 transit 引擎获取的,永远不直接暴露给应用进程。

注意:加密存储 ≠ 安全存储。哪怕你用了 AES-256,如果密钥管理不当,比如把 KEK 放在配置文件里,那和没加密一样。我曾经审计过一个项目,密文存得好好的,但 decrypt 的密钥就写在同目录的 config.ini 里……这种“锁挂在钥匙边上”的事,大家千万别犯。

四条原则的关系

这四个原则其实是环环相扣的:最小权限减少暴露面,轮换缩短被攻击窗口,审计保证可见性,加密存储守住最后一道防线。缺一条,其他三条的效果就打折扣。

总结一下我内心的优先级:加密存储是地基,必须最先落地;最小权限是日常管控,能防住大部分人祸;轮换是动态免疫力;审计则是你的眼睛。别等出了事再回头补审计日志,那时候已经晚了。
Secrets 管理四大原则 Secrets 体系 最小权限 角色分离 临时凭证 轮换 自动化 无感知切换 审计 不可篡改日志 异常告警基线 加密存储 TLS 1.2+ AES-256-GCM 四大原则相互支撑:加密是地基,最小权限是围栏,轮换是换锁,审计是监控

这四个原则你只要真理解了,写出来的 secrets 管理方案就不会飘。我自己的代码库里一直挂着这四条,每次架构评审先拿原则过一遍:有没有不必要的权限?密钥多久轮一次?审计日志能回溯到哪个粒度?加密算法和密钥链路对不对?全过了,才能上线。