4. 密钥策略详解:如何编写和管理CMK的访问策略

咱们搞 KMS,说白了,密钥策略就是 CMK 的“门禁系统”。你策略写错了,哪怕 IAM 里配得再花哨,照样调用失败。我见过不少团队,一上来就卡在这个地方——明明权限都给了,KMS 就是拒你,查半天发现策略里少写了一个 Action。

这一节,我把密钥策略的要点掰开来讲。你跟着我的思路走,基本不会再踩坑。

4.1 为什么需要密钥策略?跟 IAM 有啥区别?

先理清两个概念:IAM 策略是“全局通行证”,密钥策略是“本地门禁”。你对一个 CMK 发起请求时,KMS 的鉴权逻辑是这样走的:

  1. 检查请求者是否有 IAM 权限(用户/角色策略)
  2. 检查该 CMK 的密钥策略是否明确允许该操作
  3. 如果密钥策略有明确的 Deny,直接拒绝,IAM 允许也没用

我在项目中遇到过一例:某团队给开发人员配了 IAM 的 kms:Encrypt,结果开发还是报权限错。最后发现密钥策略里只写了“允许管理员用户使用”,没写开发角色——密钥策略没放行,上层 IAM 再允许也没用。

核心原则:对 CMK 的访问,必须同时被 IAM 和密钥策略允许。只要有一方 Deny,请求就被拒绝。

4.2 密钥策略的结构长什么样?

密钥策略本质上是一个 JSON 文档,遵循标准的 IAM 策略语法。我习惯把它拆成五个要素来看:

要素 说明 常见写法
Version 策略语法版本,目前固定 2012-10-17 "Version": "2012-10-17"
Statement 声明列表,每个声明是一条规则 数组形式,至少一条
Effect 允许 Allow 或拒绝 Deny 二选一,不能既允许又拒绝
Principal 谁可以执行操作(IAM 用户/角色/根用户) 可以使用 ARN 或通配符 *
Action / Resource / Condition 操作、资源 ARN、条件限制 类似 IAM 策略写法

举个例子,一个最基本的、允许根用户完全控制的策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    }
  ]
}

嗯,这里要注意:Principal 是密钥策略特有的字段,IAM 策略里没有它。你写密钥策略时,必须明确指定“谁”能操作。

4.3 实战编写:一个典型的生产环境策略

假设我们要创建一个 CMK,让应用服务(IAM 角色 app-role)能加密解密数据,同时让安全工程师(IAM 用户 sec-admin)能管理密钥的别名和标签。完整策略如下:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Enable IAM User Permissions",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "Allow app-role to encrypt/decrypt",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/app-role"
      },
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:GenerateDataKey"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Deny non-HTTPS requests",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "kms:*",
      "Resource": "*",
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}

这段策略里,我做了三件事:

  • 第 1 条:保留根账户的完全控制,这是 KMS 的强制要求,删了会报错。
  • 第 2 条:只给 app-role 加密、解密、生成数据密钥的权限,管理员能加别名,但应用角色不需要。
  • 第 3 条:拒绝所有非 HTTPS 的请求——我曾经遇到过明文调用的情况,虽然概率低,但加上这个条件更安全。
小技巧:写策略时多用 Sid 字段给每条声明起个名字,方便后续审计和排除。我习惯用“动作-主体”的命名方式,比如 AllowEncrypt-app-role。

4.4 如何更新密钥策略?三种方式

你可以在 CMK 创建时一次性配好策略,也可以事后修改。我个人最常用的是前两种:

  1. AWS 管理控制台:进入 KMS → 选择 CMK → ​“密钥策略” → 直接编辑 JSON,保存即可。适合快速验证。
  2. AWS CLI:用 put-key-policy 命令替换整份策略,或者用 create-key-policy 添加语句(需逐个添加)。
  3. CloudFormation / Terraform:基础设施即代码方式,推荐生产环境使用。

注意,每次更新策略,都会完全覆盖旧策略。我建议你在修改前先 get-key-policy 保存一份备份。别问为什么——我曾经手滑删了根用户权限,导致整把 CMK 无人能管,最后只好联系客服。

4.5 常见陷阱与避坑指南

陷阱 1:Principal 写错 ARN

写 Principal 时,IAM 用户要用 arn:aws:iam::account-id:user/username,IAM 角色用 arn:aws:iam::account-id:role/rolename。我见过有人写成 arn:aws:iam::123456789012:service-role/app-role,结果角色是 app-role 但路径不同,死活不生效。

陷阱 2:同时给多个主体写允许,却忘了 Deny 的优先级

密钥策略里,Deny 永远优先于 Allow。即使你写了多个 Allow,只要有一条 Deny 匹配,请求就被拒绝。我建议:尽量用 Allow 来精确放行,少用 Deny。如果非得用 Deny,务必配上 Condition 缩小范围。

陷阱 3:跨账户访问时,两边都要配

想让自己账户的 CMK 被其他账户的 IAM 角色使用,你的密钥策略必须把那个角色的 ARN 写在 Principal 里,同时,对方账户的 IAM 策略也要加上相应权限。缺一不可。我记得有个客户折腾了三天,最后发现对方账户的 IAM 策略写的是 kms:Decrypt,但 CMK 策略里只给了 kms:Encrypt,双向不对齐。

4.6 密钥策略决策流程(一图胜千言)

下面这张图总结了 KMS 鉴权的完整路径。你顺着箭头走一遍,就能明白策略怎么生效。

CMK 密钥策略鉴权流程图 CMK 访问鉴权流程 发起 KMS 请求 IAM 策略检查 IAM 允许? 拒绝访问 密钥策略检查 策略允许? 拒绝访问 允许访问 ⚠ 两者都允许 → 最终允许

这个流程你看懂了吧?IAM 和密钥策略是“与”的关系,缺一个都不行。我建议你把这张图存下来,每次写策略时对照着过一遍,能省不少排查时间。

4.7 管理策略的最佳实践

  • 最小权限原则:只给需要的 Action,不要给 kms:*。比如加密密钥不需要 ScheduleKeyDeletion 权限。
  • 使用条件键:通过 kms:ViaService 限制只能从特定 AWS 服务调用,比如只允许从 S3 加密对象。
  • 定期审计:用 get-key-policy 配合脚本检查策略变更,避免意外的 Deny 生效。
  • 策略版本控制:KMS 本身不保存历史版本,我习惯每次变更前将当前策略另存为 JSON 文件,丢到 Git 里管理。

核心总结:密钥策略不是你想象的 IAM 的“子集”。它是 CMK 自身的独立门禁。写策略时,先想清三个问题:谁(Principal)?做什么(Action)?什么条件下做(Condition)?想清楚再动笔,一般不会出错。