最小权限原则:给密钥策略“够用就好”的权限

做KMS密钥策略时,我最常听到的一句话是“先放开,后面再收紧”。但说实话,这在合规审计里是个坑。一旦权限放出去,想收回来往往要动一堆依赖方,而且审计日志里那堆“未被使用的操作”会被合规官追着问。

我自己的习惯是——从一开始就只给那个角色/用户当前确实要用到的操作,不留任何多余的“万一”。这就是最小权限原则在密钥策略里的落地方式。

为什么最小权限这么重要?

你想想看,一个密钥策略如果写得太宽,意味着什么?

  • 攻击者拿到一个低风险凭证,就能直接调用kms:Decrypt解密敏感数据
  • 内部员工无意中执行了kms:ScheduleKeyDeletion,导致密钥被计划删除,所有加密数据瞬间无法访问
  • 合规扫描工具(比如AWS Config)会持续报警“过度授权”,审计过不了

我在一个金融项目的客户那边遇到过真实案例——他们给一个只做“加密上传”的服务授权了kms:Decrypt,结果一次配置错误,日志里出现了上百万次解密请求。虽然没出安全事故,但合规部门要求立即整改,花了一整周重新梳理所有策略。

最小权限的两个层次

说白了,最小权限不是一句口号,它分两个层次:

层次 说明 示例
操作级 只允许必要的KMS API操作 只给kms:Encrypt,不给kms:Decrypt
资源级 只允许访问特定密钥(甚至特定密钥版本) 只限定arn:aws:kms:...:key/xxx,不写*

嗯,这里要注意:操作级和资源级要同时约束。光限制操作但不限制资源,等于说这个角色能用所有密钥干那件事,风险依然很大。

实战:从“全开”到“最小”三步走

我在团队里做策略审查时,通常用这三步来收窄权限:

  1. 列出现有策略里的所有操作 —— 把Action列拿出来,一条条看
  2. 标记“必要”和“可选” —— 问使用方“你真的需要kms:CreateGrant吗?”
  3. 移除所有“可选”操作 —— 只保留当前功能必须的操作

举个例子:一个后端微服务需要“加密用户身份证号并存储”。常见的问题是,运维人员直接复制一个“全功能”策略,里面包含了Decrypt、GenerateDataKey、CreateGrant、ListKeys。实际最小策略只需要:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "kms:Encrypt",
        "kms:GenerateDataKey"
      ],
      "Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/abcd1234-a123-4567-a123-a123b456c789"
    }
  ]
}

注意这里我没有写kms:Decrypt——因为微服务只做加密上传,解密由另一个独立服务负责。这样即使微服务被攻破,攻击者也拿不到明文数据。

常见过度授权“雷区”

  • 写"Action": "kms:*" —— 我最常看到的“偷懒写法”,千万别用
  • 写"Resource": "*" —— 让这个角色能操作所有密钥,等于没限制
  • 给了kms:ListKeys —— 虽然不是敏感操作,但会给攻击者提供密钥清单,辅助后续攻击
  • 忘了加上Condition约束 —— 比如来源IP、VPC、MFA等,进一步缩窄使用条件
我曾经在代码里写过一次大意:给一个批量处理任务授权了kms:Decrypt和kms:ReEncrypt,结果任务脚本写错逻辑,循环解密了10万条记录后触发KMS限速,导致整个生产环境加密链路堵死。事后排查,发现那个任务其实只需要kms:ReEncrypt就够了。教训是:多一个操作就多一分风险。

用“权限矩阵”理清最小权限

我习惯在项目初期画一张权限矩阵表,把每个角色需要的操作和资源列清楚:

角色 需要操作 限制资源 备注
上传服务 Encrypt, GenerateDataKey Key-A (生产) 不可解密
解密服务 Decrypt Key-A (生产) 来自内部VPC
审计员 DescribeKey, GetKeyPolicy 所有密钥(仅查看) 只读,无写操作
管理员 所有操作 所有密钥 需MFA

这张表定下来之后,直接转换成KMS策略JSON就行。每次有新角色加入,先问“最低要做什么”,再写策略。

我推荐的最小权限检查清单

写策略前,对着这个清单自检一遍:

  1. 这个角色是否真的需要kms:Decrypt?能否用kms:Encrypt + kms:ReEncrypt替代?
  2. 是否能用kms:GenerateDataKey代替直接调用kms:Encrypt(性能更好)?
  3. 资源ARN是否写死了具体密钥ID?还是写了*?
  4. 有没有加上Condition限制来源IP、VPC、MFA?
  5. 是否真的需要kms:CreateGrant?这个操作可以扩展权限,通常用不上
  6. 是否真的需要kms:ListKeys?大部分服务不需要枚举密钥
小技巧:在开发环境先用宽松策略快速联调,但上线前务必做一次“权限收紧审查”,用kms:SimulatePrincipalPolicy API模拟测试确认哪些操作真的被调用过,删掉那些从未出现的操作。我在项目里把这个写进了CI/CD流水线,每次部署前自动校验。

一张图看懂最小权限流程

最小权限策略生成流程 识别角色实际需求 列出最小必要API操作 限定具体密钥ARN 添加可选Condition约束 生成最小权限策略JSON 关键问题 - 这个角色具体做什么? - 需要加密还是解密? - 是CLI/API/SDK调用? 常见过度授权陷阱 - 用 * 代替具体操作 - 给所有密钥写 * - 忘了限制SourceIP - 给了不必要的ListKeys

避坑指南:我曾经踩过的三个坑

坑一:用kms:Decrypt代替kms:GenerateDataKey做数据密钥派生。还记得有一次,一个同事为了少改代码,直接调Decrypt来解密数据密钥,结果一次误操作把加密数据也给解密了。其实应该用GenerateDataKey返回加密+明文数据密钥,解密工作交给单独服务。
坑二:策略里写了"Condition": {"IpAddress": {...}}但忘记写"SourceVpc",导致密钥其实能被公网IP访问(如果网络层面没限制)。我后来学乖了:网络限制和访问控制要双保险。
坑三:给Lambda函数的执行角色授权了kms:Decrypt,但Lambda函数里从没用过这个操作。审计工具告警后我查了半天,发现是开发者从旧例子直接复制了策略。后来我在团队里推行“策略即代码”+自动化审查,避免人肉复制。

总结一下,最小权限不是一锤子买卖。随着业务迭代,角色需要的操作会变。我的建议是:每季度做一次密钥策略审计,跟使用方对一次“你现在真的还需要这个操作吗?”。保持策略瘦身,审计才轻松。