2. IAM 角色清单与权限边界梳理
为什么角色清单不能只看“角色名”
角色清单治理的核心不是把角色名抄一遍,而是把角色 → 信任策略 → 附加策略 → 权限动作 → 资源范围这条链拉直。一个角色叫 ReadOnlyRole,不代表它真的只读;真正决定权限边界的是附加策略里的 Action 与 Resource。本案例用 AWS IAM 作为对象,导出全部角色及其附加策略,并标记两类高风险:包含通配符动作(如 s3:*、*)和包含高危操作(如 iam:CreateUser、iam:AttachRolePolicy、sts:AssumeRole 到宽泛主体)。
导出角色与附加策略
使用 AWS CLI 先拉取全量角色,再逐个查询附加的托管策略和内联策略。下面命令输出 JSON 行,便于后续用 jq 或 Python 处理。注意:list-attached-role-policies 只返回托管策略,内联策略需要额外调用 list-role-policies。
# 1. 导出全部角色基础信息
aws iam list-roles --query 'Roles[*].[RoleName,Arn,CreateDate,Path]' --output text > roles.tsv
# 2. 逐个角色导出附加的托管策略
while read -r role; do
echo "=== $role ==="
aws iam list-attached-role-policies --role-name "$role" \
--query 'AttachedPolicies[*].[PolicyName,PolicyArn]' --output text
done < <(cut -f1 roles.tsv)
# 3. 逐个角色导出内联策略名
while read -r role; do
echo "=== $role inline ==="
aws iam list-role-policies --role-name "$role" \
--query 'PolicyNames[*]' --output text
done < <(cut -f1 roles.tsv)
解析策略文档,提取权限动作
拿到策略 ARN 后,需要取默认版本(DefaultVersionId)并解码 Document。下面 Python 脚本把每个角色的所有策略动作汇总,并标记通配符与高危操作。脚本假设已安装 boto3 并配置了只读凭证。
import boto3, json, re
iam = boto3.client('iam')
WILDCARD = re.compile(r'(^|\s)\*$|:\*$')
HIGH_RISK = {
'iam:CreateUser', 'iam:CreateRole', 'iam:AttachRolePolicy',
'iam:PutRolePolicy', 'iam:CreatePolicyVersion', 'sts:AssumeRole',
'kms:Decrypt', 's3:DeleteBucket', 'ec2:TerminateInstances'
}
def get_policy_document(policy_arn):
policy = iam.get_policy(PolicyArn=policy_arn)['Policy']
version_id = policy['DefaultVersionId']
doc = iam.get_policy_version(
PolicyArn=policy_arn, VersionId=version_id
)['PolicyVersion']['Document']
return doc
def extract_actions(doc):
actions, resources = [], []
for stmt in doc.get('Statement', []):
if stmt.get('Effect') != 'Allow':
continue
act = stmt.get('Action', [])
res = stmt.get('Resource', [])
actions += act if isinstance(act, list) else [act]
resources += res if isinstance(res, list) else [res]
return actions, resources
def audit_role(role_name):
attached = iam.list_attached_role_policies(
RoleName=role_name)['AttachedPolicies']
inline = iam.list_role_policies(RoleName=role_name)['PolicyNames']
all_actions, all_resources = [], []
for p in attached:
doc = get_policy_document(p['PolicyArn'])
a, r = extract_actions(doc)
all_actions += a; all_resources += r
for name in inline:
doc = iam.get_role_policy(
RoleName=role_name, PolicyName=name)['PolicyDocument']
a, r = extract_actions(doc)
all_actions += a; all_resources += r
wildcard = [a for a in all_actions if WILDCARD.search(a)]
high = [a for a in all_actions if a in HIGH_RISK]
wide_res = [r for r in all_resources if r == '*']
return {
'role': role_name,
'actions': sorted(set(all_actions)),
'wildcard': sorted(set(wildcard)),
'high_risk': sorted(set(high)),
'wide_resource': sorted(set(wide_res))
}
# 遍历全部角色
for role in iam.list_roles()['Roles']:
result = audit_role(role['RoleName'])
if result['wildcard'] or result['high_risk'] or result['wide_resource']:
print(json.dumps(result, ensure_ascii=False))
结果判读与处置优先级
| 标记类型 | 典型特征 | 处置优先级 |
|---|---|---|
| 通配符动作 | Action: "*" 或 s3:* | 高:立即收敛到具体动作 |
| 高危操作 | iam:AttachRolePolicy、sts:AssumeRole | 高:确认是否必要,加 Condition 限制 |
| 资源范围过宽 | Resource: "*" | 中高:限定到具体 ARN 或加标签条件 |
| 仅只读动作 | Describe*、List*、Get* | 低:保留,定期复核 |
导出时建议同时记录角色的
LastUsedDate(通过 get-role 或 CloudTrail 补充),长期未使用的角色可优先考虑删除或禁用,而不是只做权限收敛。不要直接删除仍在使用的角色或策略。先标记、再确认负责人、最后收敛;对包含
sts:AssumeRole 的角色,还要检查信任策略中的 Principal 是否被限定到具体账号或角色,避免跨账号过度信任。