Service Control Policies 是组织级别的权限上限,即使拥有 AdministratorAccess 也会被 explicit deny 阻断,是防止 Agent 误操作的最后兜底。
第三层——强制执行:SCP 作为硬兜底
无论你在身份和操作级别的 IAM 上多么小心,只要这种模式扩散到足够多的工程师,就会有人复用他们的 admin 角色、带 iam:* 的角色,或者生产环境的 break-glass 角色。策略审查文化只能帮你到这里。
这就是 Service Control Policies(SCP)发挥作用的地方。这是当开发者不小心给 agent 过多 IAM 权限时,仍然能守住的那一层——不过在依赖它之前,有些限制你必须先了解清楚。
SCP 是成员账户中委托人的权限上限。你无法用 IAM 的 allow 来覆盖 SCP 的显式 deny。即使是 AdministratorAccess 会话,也无法执行适用 SCP 所拒绝的操作——我给一个临时成员账户附加了一条一行式 deny,结果该账户的 admin 会话调用失败,错误就是"Service Control Policy 中的显式拒绝"。
有三个例外需要关注。SCP 不影响管理账户,不限制服务关联角色,而且约束的是组织内的委托人——不包括资源策略引入的外部委托人。把 agent 放在成员账户里。如果需要跨账户访问,把 SCP 和资源控制策略(RCP)配对使用,RCP 即使在调用方是外部主体时也会约束对成员账户中受支持资源的访问。RCP 支持是服务特定的,且会随时间变化,因此在依赖它之前请查看 AWS 当前的支持服务列表。
SCP 永远不会授予权限。agent 角色的身份策略必须授予它所需的读取和有限写入操作。权限边界(如 SCP)只是对身份策略所能产生的权限设置上限——它本身从不授予任何东西。SCP 作为粗粒度的护栏,位于两者之上,用于那些应该在专用沙箱 OU 或账户层面统一生效的控制措施。
如果仍然附加着默认的 FullAWSAccess SCP,那么以下内容就是一个拒绝列表:任何未被拒绝的操作仍然可以通过 IAM 来授予。如果你用允许列表 SCP 替换了 FullAWSAccess,则必须从组织根到账户的每个层级都存在显式允许——而且 IAM 仍然需要授予该操作。在不理解两者交集的情况下混用这两种模式,是导致整个 OU 被锁定的常见原因。
以下是一个专用 agent 沙箱的拒绝列表起点。它阻止了常见的 IAM 权限提升路径、账户逃生通道,以及一组高影响的删除操作。它有意不被宣传为完整的 CUD 覆盖范围。部署前请替换示例账户 ID 和角色名称;这些例外本身不授予权限,因此相关角色仍然需要严格限定范围的 IAM 策略和信任策略。
策略结构:直接拒绝权限提升和破坏这两大类操作,然后为沙箱真正需要的两个例外场景 carving back。下面是摘录:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BlockPrivilegeEscalation",
"Effect": "Deny",
"Action": [
"iam:AttachRolePolicy",
"iam:CreateAccessKey",
"iam:CreateRole",
"iam:CreateUser",
"iam:UpdateAssumeRolePolicy"
],
"Resource": "*"
},
{
"Sid": "ReserveInlineRolePolicyMutationForIR",
"Effect": "Deny",
"Action": [
"iam:DeleteRolePolicy",
"iam:PutRolePolicy"
],
"Resource": "*",
"Condition": {
"ArnNotEquals": {
"aws:PrincipalArn":
"arn:aws:iam::111122223333:role/AgentBreakGlassIncidentResponse"
}
}
}
]
}
(此摘录是有效的 JSON 但不完整——完整策略有七条语句,仅权限提升类操作就有 31 个 action。完整文件已通过 IAM Access Analyzer 验证,零发现问题:agent-sandbox-scp.json。)
运行此策略的几个实践注意事项:
把 agent 放在专用的成员账户或 OU 中——理想情况下是一个没有生产数据的账户。SCP 作用于账户层面,而不仅仅是携带 UA 标签的请求,把它附加到共享开发账户也可能破坏人工和 CI 工作流。当沙箱里没有任何重要内容时,你可以保持拒绝列表的粗粒度,而不用没完没了地 carving 例外。
从窄到宽,逐步扩展。先阻止爆炸半径最大的操作,观察哪些合法工作流会受到影响,然后再谨慎地扩展。示例是一个起点,不是每个会造成损害的 AWS 操作的完整列表。
拒绝的是结果,而不仅仅是显而易见的 API。阻止实例终止并不能阻止 Auto Scaling 通过服务关联角色缩减集群规模。阻止对象删除并不能阻止生命周期规则使对象过期,也不能阻止版本控制变更削弱恢复能力。删除堆栈不是移除其资源的唯一方式——更新同样可以做到。为你实际允许的服务建模侧门;通用的拒绝列表无法替你做到这一点。
不要拒绝你自己的恢复路径。我特意在发布的拒绝列表中省略了 bucket versioning:账户范围内阻止它也会移除你在事件期间开启 versioning 的能力,而一个拖累事件响应的控制措施迟早会在压力下被移除。针对每个账户单独建模这种权衡,而不是默认使用全面 deny。
保护证据,而不仅仅是日志开关。如果委托人能够擦除 CloudWatch Logs 流、移除订阅过滤器或删除下游转发目标,仅仅拒绝 cloudtrail:StopLogging 是不够的。当审计持久性很重要时,保留一份不可变的或单独管理的 S3 副本。
预期会有运营影响。示例为一条部署角色预留了 iam:PassRole,为一条事件响应角色预留了内联角色策略变更。这些是针对特定拒绝语句的例外,而非账户管理员:IAM 仍然需要限定他们可以传递或修改的范围,并且他们的信任策略必须严格控制谁可以 assume 他们。最尖锐的边缘案例是 KMS grants:拒绝 grant 创建导致使用客户管理密钥加密的 EFS 和 RDS 无法创建资源,并使 DynamoDB 因 kms:CreateGrant 上的字面显式拒绝而失败——而 EBS 卷和 Secrets Manager 则正常通过。日志控制同样可能破坏普通的日志供应和转发。在推广到正式环境之前,在一次性 OU 中测试所有这些。
约束支出,而不仅仅是权限。这整个堆栈无法阻止 agent 在允许的区域启动最大实例类型。在有意义的地方添加区域限制,并配置 Budgets 或成本异常告警——这是这里成本最低的爆炸半径控制措施,也是人们最容易忘记的一个。
通过 CloudFormation / IaC 部署,这样 SCP 本身也是版本化的、可审查的、可回滚的。不要从控制台手动编辑控制平面。一个顺序注意事项:全面的 IAM deny 也会作用于账户内的 IaC,因此在附加此策略之前先建立沙箱基线——或者 carving 一个 deployment-role 例外,并把该角色本身视为权限提升目标,因为它能做的任何事情也会被攻陷的 agent 继承。
下一篇:最后一层——把所有这些变成你实际可以查询和告警的东西。
AWS Organizations — Service control policies (SCPs)(SCP 不限制的内容:管理账户、服务关联角色)
AWS Organizations — Resource control policies (RCPs)
AWS Identity and Access Management — Permissions boundaries