3. IAM 策略文档解析与权限动作提取

IAM 策略文档解析与权限动作提取 学习卡片

为什么策略解析是权限审计的起点

IAM 策略文档是 JSON 结构,一条策略通常包含多个 Statement,每个 Statement 由 Effect、Action、Resource、Condition 四个核心字段构成。审计的目标不是“读一遍策略”,而是把嵌套结构展开成一张扁平的权限动作清单,让每一条 Allow/Deny 都能被逐行核对。下面这张图展示了解析过程中 JSON 结构到审计清单的映射关系:

IAM 策略 JSON 文档 Statement[0] Effect: "Allow" Action: ["s3:GetObject", "s3:ListBucket"] Resource: "arn:aws:s3:::data-*" Condition: {"IpAddress": ...} Statement[1] Effect: "Deny" Action: ["iam:DeleteUser", "iam:CreateAccessKey"] Resource: "*" Condition: 无 Statement[2] Effect: "Allow" Action: "ec2:*" Resource: "*" Condition: {"MFA": "true"} 解析 展开 可审计权限动作清单 Effect Action Resource Condition Allow s3:GetObject arn:aws:s3:::data-* IpAddress Allow s3:ListBucket arn:aws:s3:::data-* IpAddress Deny iam:DeleteUser * — Deny iam:CreateAccessKey * — Allow ec2:* * MFA=true ⚠ 审计关注点 ec2:* 通配符 + Resource:* → 权限过大,需确认是否必要 解析要点 1. 每个 Statement 的 Action 数组需逐项展开为独立行 2. Resource 中的通配符 * 需标记为高风险 3. Condition 为空时表示无额外约束,需重点审查 4. Deny 优先级高于 Allow,冲突时以 Deny 为准 5. 同一 Action 同时出现在 Allow 和 Deny 中需标记冲突 6. 最终输出为 CSV/JSON 格式,供后续审计工具消费

解析实现:从 JSON 到权限清单

下面用 Python 实现一个策略解析器,核心逻辑是遍历 PolicyDocument 中的 Statement 数组,将每个 Statement 的 Action 展开为独立记录,并提取 Effect、Resource、Condition 字段。代码不依赖外部库,可直接运行。

import json
import csv
from io import StringIO

def parse_policy(policy_json: dict, policy_name: str = "unknown") -> list[dict]:
    """
    解析单条 IAM 策略文档,返回扁平化的权限动作清单。
    每条记录包含:policy_name, statement_index, effect, action, resource, condition
    """
    records = []
    statements = policy_json.get("Statement", [])
    if isinstance(statements, dict):
        statements = [statements]

    for idx, stmt in enumerate(statements):
        effect = stmt.get("Effect", "Unknown")
        actions = stmt.get("Action", [])
        if isinstance(actions, str):
            actions = [actions]
        resources = stmt.get("Resource", [])
        if isinstance(resources, str):
            resources = [resources]
        condition = stmt.get("Condition", None)

        for action in actions:
            for resource in resources:
                records.append({
                    "policy_name": policy_name,
                    "statement_index": idx,
                    "effect": effect,
                    "action": action,
                    "resource": resource,
                    "condition": json.dumps(condition, ensure_ascii=False) if condition else ""
                })
    return records

def flag_risks(records: list[dict]) -> list[dict]:
    """标记高风险项:通配符 Action、通配符 Resource、无 Condition 的 Allow"""
    for r in records:
        risks = []
        if r["action"].endswith(":*") or r["action"] == "*":
            risks.append("通配符Action")
        if r["resource"] == "*":
            risks.append("通配符Resource")
        if r["effect"] == "Allow" and not r["condition"]:
            risks.append("无Condition约束")
        r["risk_flags"] = "|".join(risks) if risks else ""
    return records

def export_csv(records: list[dict]) -> str:
    """导出为 CSV 字符串"""
    if not records:
        return ""
    buf = StringIO()
    writer = csv.DictWriter(buf, fieldnames=records[0].keys())
    writer.writeheader()
    writer.writerows(records)
    return buf.getvalue()

# ===== 教学示例:模拟一条 IAM 策略 =====
sample_policy = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": ["s3:GetObject", "s3:ListBucket"],
            "Resource": "arn:aws:s3:::data-*",
            "Condition": {"IpAddress": {"aws:SourceIp": "10.0.0.0/8"}}
        },
        {
            "Effect": "Deny",
            "Action": ["iam:DeleteUser", "iam:CreateAccessKey"],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "ec2:*",
            "Resource": "*",
            "Condition": {"Bool": {"aws:MultiFactorAuthPresent": "true"}}
        }
    ]
}

# 执行解析
records = parse_policy(sample_policy, policy_name="sample-policy")
records = flag_risks(records)

# 输出结果
print(f"共解析出 {len(records)} 条权限动作记录\n")
for r in records:
    flag = f"  ⚠ {r['risk_flags']}" if r["risk_flags"] else ""
    print(f"[{r['effect']:5s}] {r['action']:25s} -> {r['resource']:30s}{flag}")

print("\n--- CSV 输出 ---")
print(export_csv(records))

运行上述代码,预期输出如下(教学示例结果,非真实环境实测):

共解析出 5 条权限动作记录

[Allow] s3:GetObject              -> arn:aws:s3:::data-*              
[Allow] s3:ListBucket             -> arn:aws:s3:::data-*              
[Deny ] iam:DeleteUser            -> *                                
[Deny ] iam:CreateAccessKey       -> *                                
[Allow] ec2:*                     -> *                                ⚠ 通配符Action|通配符Resource

--- CSV 输出 ---
policy_name,statement_index,effect,action,resource,condition,risk_flags
sample-policy,0,Allow,s3:GetObject,arn:aws:s3:::data-*,{"IpAddress": {"aws:SourceIp": "10.0.0.0/8"}},
sample-policy,0,Allow,s3:ListBucket,arn:aws:s3:::data-*,{"IpAddress": {"aws:SourceIp": "10.0.0.0/8"}},
sample-policy,1,Deny,iam:DeleteUser,*,, 
sample-policy,1,Deny,iam:CreateAccessKey,*,, 
sample-policy,2,Allow,ec2:*,*,{"Bool": {"aws:MultiFactorAuthPresent": "true"}},通配符Action|通配符Resource

结果判读与审计动作

解析完成后,需要根据清单中的风险标记决定后续审计动作。以下判断逻辑可直接用于自动化审计脚本:

风险标记 含义 建议审计动作
通配符Action Action 以 :* 或 * 结尾,覆盖该服务的全部操作 确认是否必须使用通配符;若否,替换为具体 Action 列表
通配符Resource Resource 为 *,作用于所有资源 检查是否可缩小到具体 ARN 前缀或资源标签
无Condition约束 Allow 语句没有任何 Condition,权限无额外限制 评估是否可增加 MFA、IP 限制、时间窗口等条件
Allow/Deny 冲突 同一 Action 同时出现在 Allow 和 Deny 中 确认 Deny 是否为显式拒绝;显式 Deny 优先,需检查是否误配置
批量解析建议:实际审计中通常需要遍历多个策略文档。可将 parse_policy 封装为循环,对每个策略文件调用一次,最后合并所有 records 并统一导出 CSV。若策略来自 AWS API,可直接使用 boto3 的 list_policies 和 get_policy_version 获取 JSON 文档后传入解析函数。
注意:上述代码中的 sample_policy 为教学示例数据,输出结果由代码逻辑直接推导,并非真实 IAM 环境运行结果。实际策略中可能包含 NotAction、NotResource、Principal 等字段,解析器需根据审计范围决定是否扩展支持。