多云 IAM 权限收敛的目标,是把散落在长期访问密钥、通配符策略与过度授权角色中的权限,压缩到「每个身份只拥有完成本职工作所需的最小集合」。本文以 AWS IAM 与阿里云 RAM 为例,给出权限清单盘点、策略模拟、权限边界(permissions boundary)与服务控制策略(SCP)的完整配置流程,并附可直接执行的验证命令。
适用场景
- 多云或多账号环境,账号数量增长后无法追踪谁拥有什么权限。
- 等保、ISO 27001 或 SOC 2 审计前的权限整改与最小权限举证。
- 员工离职、外包团队退场后的账号与密钥回收。
- 发生访问密钥泄露或 AK 被提交到代码仓库后的紧急收口。
- 为自动化脚本、CI/CD 流水线创建可控的专用角色,替代全权限子账号。
前置条件
- AWS CLI v2,已配置具备
iam:Get*、iam:List*、access-analyzer:*只读权限的管理员凭证。 - 阿里云
aliyunCLI,以及 RAM 只读权限的 AccessKey。 - 若使用 SCP,需要 AWS Organizations 已开启并拥有组织管理账号权限。
- 一个独立的非生产测试账号,用于在收紧前验证策略不会中断业务。
jq,用于解析授权明细 JSON。
原理说明
云平台的授权判定不是「多个策略取并集」这么简单,而是一个逐层收窄的漏斗模型,理解这一点是权限收敛的前提:
- 有效权限 = 身份策略(Identity Policy) ∩ 权限边界(Permissions Boundary) ∩ 服务控制策略(SCP),且任意一层出现 显式 Deny 都会直接否决,显式 Deny 的优先级永远最高。
- 权限边界只做上限约束、本身不授予任何权限。它适合覆盖「允许自助授权但不得越界」的场景,例如让团队自行创建角色,但不允许角色获得
iam:*之类的提权动作。 - SCP 作用于账号内全部身份,是账号级的合规底线,例如禁止在非白名单区域创建资源。需要注意组织管理账号自身不受 SCP 约束,必须用独立的 root 保护策略处理。
- 资源策略(如 S3 Bucket Policy)属于额外一层,当同一账号内请求同时命中身份策略与资源策略时按并集判定,跨账号请求才要求两者同时允许。
操作步骤
步骤一:导出全量授权明细与凭证报告
aws iam get-account-authorization-details > iam-details.json
aws iam generate-credential-report
sleep 5
aws iam get-credential-report --query Content --output text | base64 -d > credential-report.csv
授权明细包含所有用户、组、角色、内联策略与自定义策略的完整绑定关系,是后续所有分析的输入。凭证报告则用于定位长期未使用的密钥与口令。
步骤二:筛出高危通配符策略
jq -r '.Policies[] | select(.PolicyName | test("FullAccess|Admin|Wildcard")) | [.PolicyName, .Arn] | @tsv' iam-details.json
# 找出所有含 Action:* 且 Resource:* 的策略
jq -r '.Policies[] | .PolicyName as $n
| .PolicyVersionList[-1].Document.Statement[]
| select(.Effect=="Allow")
| select((.Action|tostring|test("\\*")) and (.Resource|tostring|test("^\\*$")))
| [$n, (.Action|tostring), (.Resource|tostring)] | @tsv' iam-details.json
输出中同时命中「Action 含通配符」与「Resource 为全星号」的条目,就是权限收敛的第一批整改对象。只读型策略(如 s3:Get* 且 Resource 精确到具体桶)优先级可以放后。
步骤三:基于真实访问活动生成策略
把「凭经验猜最小权限」换成「按 CloudTrail 实际调用生成」,可显著降低误伤:
aws accessanalyzer start-policy-generation \
--policy-generation-details '{"principalArn":"arn:aws:iam::111122223333:role/AppRole"}' \
--cloud-trail-details '{"trailArn":"arn:aws:cloudtrail:cn-north-1:111122223333:trail/org-trail","accessRole":"arn:aws:iam::111122223333:role/AccessAnalyzerMonitor","startTime":"2026-09-01T00:00:00Z","endTime":"2026-10-01T00:00:00Z"}'
# 等待任务完成后取出生成的策略文档
aws accessanalyzer list-policy-generations --query 'policyGenerations[0].jobId' --output text
aws accessanalyzer get-generated-policy --job-id <job-id> --include-resource-placeholders FINDING_DETAILS
观察窗口建议不少于 30 天,且必须覆盖月度批处理、报表导出等低频任务,否则生成的策略会漏掉这些动作。
步骤四:编写并绑定权限边界
先禁止提权动作,再列出允许集合。以下边界策略可直接复用,重点是第一段显式 Deny:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyPrivilegeEscalation",
"Effect": "Deny",
"Action": [
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion",
"iam:CreateAccessKey",
"iam:AttachUserPolicy",
"iam:PutRolePolicy",
"iam:PassRole",
"organizations:LeaveOrganization"
],
"Resource": "*"
},
{
"Sid": "AllowOnlyBusinessActions",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
aws iam create-policy --policy-name AppBoundary \
--policy-document file://boundary.json
aws iam put-role-permissions-boundary --role-name AppRole \
--permissions-boundary arn:aws:iam::111122223333:policy/AppBoundary
步骤五:用 SCP 锁住账号级底线
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonApprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["cn-north-1", "cn-northwest-1"]
}
}
},
{
"Sid": "DenyCloudTrailTamper",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail"
],
"Resource": "*"
}
]
}
aws organizations create-policy --name DenyNonApprovedRegions \
--type SERVICE_CONTROL_POLICY --content file://scp.json
aws organizations attach-policy --policy-id p-xxxxxxxx --target-id 111122223333
把审计日志的关闭动作纳入 SCP 是权限收敛里最容易被忽略的一环:即便攻击者拿到了高权限身份,也无法抹掉自己的操作痕迹。
步骤六:清理长期访问密钥与硬编码凭证
aws iam list-access-keys --user-name svc-app
# 先禁用观察一周,确认无调用后再删除
aws iam update-access-key --user-name svc-app \
--access-key-id AKIAXXXXXXXXXXXXXXXX --status Inactive
aws iam delete-access-key --user-name svc-app \
--access-key-id AKIAXXXXXXXXXXXXXXXX
长期密钥的正确替代是联合身份:EC2 上用实例角色,容器上用服务账号或 IRSA,CI/CD 上用 OIDC 短期令牌换取 STS 凭证。完成替换后,账号内的长期密钥数量应趋近于零。
步骤七:阿里云 RAM 侧同步收敛
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["oss:GetObject", "oss:ListObjects"],
"Resource": ["acs:oss:*:*:app-data/*"]
},
{
"Effect": "Deny",
"Action": ["ram:*", "sts:AssumeRole"],
"Resource": ["*"]
}
]
}
aliyun ram CreatePolicy --PolicyName AppReadOnly \
--PolicyDocument "$(cat ram-policy.json)" --Description "应用只读最小权限"
aliyun ram AttachPolicyToUser --PolicyName AppReadOnly \
--PolicyType Custom --UserName dev01
配置验证
- 高危动作模拟:应返回
implicitDeny或explicitDeny,而不是allowed。 - 业务动作模拟:应返回
allowed,用于确认收紧没有误伤正常调用。 - 边界已生效:查询角色的边界字段应有值。
- 密钥健康度:凭证报告中 90 天未使用的密钥数应为 0。
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/AppRole \
--action-names s3:GetObject iam:CreateAccessKey \
--resource-arns arn:aws:s3:::app-data/report.csv
aws iam get-role --role-name AppRole --query 'Role.PermissionsBoundary'
awk -F, 'NR>1 && $9=="true" {print $1, $10, $11}' credential-report.csv
常见问题
Q1:权限边界与 SCP 功能重叠,是不是二选一即可?
两者作用层级不同,不能互相替代。权限边界是身份级上限,适用于「允许团队自助授权,但硬性规定不得超越某个集合」的场景,粒度细、可按角色单独设置;SCP 是组织级上限,作用于账号内全部身份,适合承载合规底线(区域限制、日志防篡改)。管理账号不受 SCP 约束,因此高敏感账号必须单独设计 root 保护与 MFA 策略。
Q2:直接按 Access Analyzer 生成的策略上线安全吗?
不建议直接强制上线。生成策略只统计观察窗口内实际发生的调用,天然会遗漏低频操作与灾备路径。稳妥做法分两步:先用「只记录不拒绝」的模式再观察 2 到 4 周,把期间出现的 implicitDeny 告警逐条确认;确认清单清零后,再把策略切换为强制生效。同时保留一份回滚用的宽松策略,并记录切换时间点,便于出现故障时快速恢复。
Q3:策略里出现通配符 Action 就一定不安全吗?
不是绝对的。判定的关键是通配符是否跨越了权限提升边界:s3:Get* 配合精确到具体桶的 Resource,且策略中不含 iam:*、sts:AssumeRole、lambda:UpdateFunctionCode 等提权动作时,风险可控。真正需要立即整改的是 Action:* + Resource:*,以及任何能修改身份策略、创建新凭证或关闭审计的组合。收敛时应以「能否获得新权限」为判据,而不是以是否含星号为判据。
总结
多云 IAM 权限收敛不是一次性动作,而是一条可持续运行的闭环:先用授权明细与凭证报告建立现状基线,再用访问活动分析生成候选最小权限集,然后通过权限边界与 SCP 双层收窄,最后用策略模拟和历史调用对账验证效果。落地节奏建议按「只读审计 → 灰度记录 → 强制拒绝 → 定期复查」四步推进,每一步都保留可回滚的中间态,避免为了合规把线上业务一起收掉。