4. IAM 用户与角色的直接策略附加审计

IAM 用户与角色的直接策略附加审计 学习卡片

为什么“直接附加”是审计重点

在 IAM 中,用户或角色获得权限通常有三条路径:通过组继承、通过权限边界约束、以及直接附加策略。直接附加策略(内联策略或托管策略)会绕过组这一层集中管理,一旦人员转岗或角色复用,很容易留下无人认领的授权。本节任务是把所有“直接附加”的授权点找出来,并区分其风险等级。

审计对象与判定口径

  • 直接附加:策略直接绑定在用户或角色实体上,而非绑定在组上。
  • 内联策略:随实体生命周期存在,删除实体即消失,但难以跨实体复用和统一审计。
  • 托管策略:独立存在的策略对象,可被多个实体引用,需关注是否为 AWS 托管(如 AdministratorAccess)还是客户自建。
  • 风险信号:直接附加 + 高危动作(*:*、iam:*、sts:AssumeRole)+ 无权限边界。
判定“直接附加”时,务必区分 AttachedPolicies(托管策略)与 InlinePolicies(内联策略),两者在 API 返回结构中字段不同,漏掉任一类都会造成审计盲区。

审计数据流与判定路径

下图展示从 IAM 实体枚举到风险标记的完整数据流:先分别拉取用户和角色的直接附加策略,再与高危动作清单比对,最后按“是否设置权限边界”分流判定。

IAM 用户枚举 ListUsers IAM 角色枚举 ListRoles 用户直接附加策略 ListAttachedUserPolicies ListUserPolicies 角色直接附加策略 ListAttachedRolePolicies ListRolePolicies 直接附加策略清单 用户 + 角色 合并 高危动作比对 *:* / iam:* / sts:* 权限边界检查 GetUser / GetRole 低风险 有边界约束 高风险 无边界+高危 中风险 有边界但过宽 有边界 → 继续检查边界策略 无边界 → 直接标记高风险

审计脚本实现

以下脚本使用 boto3 遍历所有用户和角色,分别提取托管策略和内联策略,并与高危动作清单比对。脚本输出 CSV 格式,便于后续筛选。

import boto3
import csv
import json

iam = boto3.client('iam')

# 高危动作前缀,可按需扩展
HIGH_RISK_ACTIONS = ['*', 'iam:*', 'sts:*', 'organizations:*']

def is_high_risk(policy_doc):
    """检查策略文档中是否包含高危 Allow 动作"""
    for stmt in policy_doc.get('Statement', []):
        if stmt.get('Effect') != 'Allow':
            continue
        actions = stmt.get('Action', [])
        if isinstance(actions, str):
            actions = [actions]
        for act in actions:
            for risk in HIGH_RISK_ACTIONS:
                if act == risk or act.startswith(risk.replace('*', '')):
                    return True
    return False

def get_inline_policy_doc(entity_type, name, policy_name):
    if entity_type == 'user':
        resp = iam.get_user_policy(UserName=name, PolicyName=policy_name)
    else:
        resp = iam.get_role_policy(RoleName=name, PolicyName=policy_name)
    return resp['PolicyDocument']

def get_managed_policy_doc(arn):
    # 获取默认版本
    policy = iam.get_policy(PolicyArn=arn)['Policy']
    version_id = policy['DefaultVersionId']
    doc = iam.get_policy_version(PolicyArn=arn, VersionId=version_id)
    return doc['PolicyVersion']['Document']

rows = []

# 遍历用户
for user in iam.list_users()['Users']:
    uname = user['UserName']
    # 托管策略
    for ap in iam.list_attached_user_policies(UserName=uname)['AttachedPolicies']:
        doc = get_managed_policy_doc(ap['PolicyArn'])
        rows.append({
            'entity_type': 'user', 'entity_name': uname,
            'policy_type': 'managed', 'policy_name': ap['PolicyName'],
            'policy_arn': ap['PolicyArn'],
            'high_risk': is_high_risk(doc)
        })
    # 内联策略
    for ip in iam.list_user_policies(UserName=uname)['PolicyNames']:
        doc = get_inline_policy_doc('user', uname, ip)
        rows.append({
            'entity_type': 'user', 'entity_name': uname,
            'policy_type': 'inline', 'policy_name': ip,
            'policy_arn': '', 'high_risk': is_high_risk(doc)
        })

# 遍历角色
for role in iam.list_roles()['Roles']:
    rname = role['RoleName']
    for ap in iam.list_attached_role_policies(RoleName=rname)['AttachedPolicies']:
        doc = get_managed_policy_doc(ap['PolicyArn'])
        rows.append({
            'entity_type': 'role', 'entity_name': rname,
            'policy_type': 'managed', 'policy_name': ap['PolicyName'],
            'policy_arn': ap['PolicyArn'],
            'high_risk': is_high_risk(doc)
        })
    for ip in iam.list_role_policies(RoleName=rname)['PolicyNames']:
        doc = get_inline_policy_doc('role', rname, ip)
        rows.append({
            'entity_type': 'role', 'entity_name': rname,
            'policy_type': 'inline', 'policy_name': ip,
            'policy_arn': '', 'high_risk': is_high_risk(doc)
        })

# 输出 CSV
with open('direct_policy_audit.csv', 'w', newline='') as f:
    writer = csv.DictWriter(f, fieldnames=['entity_type','entity_name','policy_type','policy_name','policy_arn','high_risk'])
    writer.writeheader()
    writer.writerows(rows)

print(f"共发现 {len(rows)} 条直接附加策略,其中高危 {sum(1 for r in rows if r['high_risk'])} 条")

结果判读与处置建议

entity_typepolicy_typehigh_risk建议动作
usermanagedtrue立即移除直接附加,改由组继承;若必须保留,设置权限边界
userinlinetrue优先删除内联策略,迁移到托管策略并纳入组管理
rolemanagedtrue检查该角色是否被跨账号信任,评估是否需收窄信任策略
roleinlinefalse记录并纳入定期复查,确认无权限膨胀
usermanagedfalse低优先级,但建议统一迁移到组以简化管理
运行脚本前确保凭证具有 iam:List*、iam:Get* 权限。若账号下实体数量超过 100,需处理分页(list_users 和 list_roles 均支持 Marker 参数)。

快速复查清单

  • 是否同时覆盖了用户和角色两类实体?
  • 是否同时提取了托管策略和内联策略?
  • 高危动作清单是否包含 *、iam:*、sts:*?
  • 是否检查了权限边界(PermissionsBoundary)字段?
  • 输出 CSV 是否包含实体名、策略名、策略类型和高危标记?