IAM 故障排查与调试

掌握 IAM 权限问题的诊断技巧和调试工具。

常见错误类型

Access Denied 错误

错误表现:

CLI 输出:
An error occurred (AccessDenied) when calling the <Operation>: 
User: arn:aws:iam::123456789012:user/alice is not authorized 
to perform: <service>:<action> on resource: <resource-arn>

控制台显示:
You are not authorized to perform this operation

可能原因分析:

1. 缺少显式 Allow:

问题:没有任何策略授予所需权限

排查:
├─ 检查用户直接附加的策略
├─ 检查用户所属组的策略
├─ 检查资源策略(如 S3 Bucket Policy)
└─ 验证策略中的 Action 和 Resource

解决:
└─ 添加包含所需操作的策略

2. 存在显式 Deny:

问题:某个策略显式拒绝了操作

排查优先级:
1. SCP(组织策略)- 最高优先级
2. 权限边界(Permissions Boundary)
3. 会话策略(Session Policy)
4. 身份策略或资源策略中的 Deny

解决:
├─ 找到 Deny 语句的来源
├─ 评估是否可以修改或删除
└─ 如果是 SCP,需要组织管理员协助

3. 权限边界限制:

问题:实际权限超出了权限边界

原理:
实际权限 = 身份策略 ∩ 权限边界

排查:
aws iam get-user --user-name alice
# 查看 PermissionsBoundary 字段

aws iam get-policy --policy-arn <boundary-arn>
aws iam get-policy-version \
  --policy-arn <boundary-arn> \
  --version-id <version>

解决:
├─ 扩大权限边界(如果合理)
└─ 或调整身份策略以符合边界

4. SCP 限制:

问题:组织策略拒绝操作

排查:
aws organizations list-policies-for-target \
  --target-id <account-id> \
  --filter SERVICE_CONTROL_POLICY

aws organizations describe-policy \
  --policy-id <policy-id>

解决:
├─ 联系组织管理员
├─ 评估 SCP 的合理性
└─ 可能需要修改 SCP 或更换账户

5. 资源策略冲突:

问题:资源策略拒绝访问

典型场景:
├─ S3 Bucket Policy 拒绝
├─ KMS Key Policy 未授权
├─ Lambda 资源策略限制
└─ SQS Queue Policy 限制

排查:
# S3 示例
aws s3api get-bucket-policy --bucket my-bucket

# KMS 示例
aws kms get-key-policy \
  --key-id <key-id> \
  --policy-name default

解决:
└─ 修改资源策略以允许访问

策略语法错误

JSON 格式错误:

// ❌ 错误:缺少逗号
{
  "Version": "2012-10-17"
  "Statement": [...]
}

// ✅ 正确
{
  "Version": "2012-10-17",
  "Statement": [...]
}

// ❌ 错误:多余的逗号
{
  "Version": "2012-10-17",
  "Statement": [
    {...},
  ]
}

// ✅ 正确
{
  "Version": "2012-10-17",
  "Statement": [
    {...}
  ]
}

Action 名称错误:

// ❌ 错误:拼写错误
{
  "Action": "s3:GetObjet"
}

// ❌ 错误:大小写错误
{
  "Action": "s3:getobject"
}

// ✅ 正确
{
  "Action": "s3:GetObject"
}

// ❌ 错误:通配符位置错误
{
  "Action": "s3*:GetObject"
}

// ✅ 正确
{
  "Action": "s3:Get*"
}

Resource ARN 错误:

// ❌ 错误:缺少分区
{
  "Resource": "arn:s3:::my-bucket/*"
}

// ✅ 正确
{
  "Resource": "arn:aws:s3:::my-bucket/*"
}

// ❌ 错误:S3 ARN 包含区域和账户ID(不需要)
{
  "Resource": "arn:aws:s3:us-east-1:123456789012:my-bucket/*"
}

// ✅ 正确
{
  "Resource": "arn:aws:s3:::my-bucket/*"
}

// ❌ 错误:缺少资源类型
{
  "Resource": "arn:aws:ec2:us-east-1:123456789012:i-1234567890abcdef0"
}

// ✅ 正确
{
  "Resource": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"
}

Condition 错误:

// ❌ 错误:条件运算符拼写错误
{
  "Condition": {
    "StringEqual": {
      "aws:username": "alice"
    }
  }
}

// ✅ 正确
{
  "Condition": {
    "StringEquals": {
      "aws:username": "alice"
    }
  }
}

// ❌ 错误:条件键不存在
{
  "Condition": {
    "StringEquals": {
      "aws:UserName": "alice"
    }
  }
}

// ✅ 正确
{
  "Condition": {
    "StringEquals": {
      "aws:username": "alice"
    }
  }
}

角色承担失败

错误类型:

1. 信任策略配置错误:

错误信息:
User: arn:aws:iam::123456789012:user/alice is not authorized 
to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/MyRole

原因:
└─ 角色的信任策略未授权该用户

检查信任策略:
aws iam get-role --role-name MyRole \
  --query 'Role.AssumeRolePolicyDocument'

正确的信任策略:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/alice"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

2. External ID 不匹配:

错误信息:
An error occurred (AccessDenied) when calling the AssumeRole operation: 
External ID does not match

原因:
└─ 提供的 External ID 与信任策略要求的不一致

解决:
# 使用正确的 External ID
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/MyRole \
  --role-session-name my-session \
  --external-id "correct-external-id-12345"

3. MFA 要求未满足:

错误信息:
MultiFactorAuthentication failed

原因:
└─ 信任策略要求 MFA,但未提供

信任策略:
{
  "Condition": {
    "Bool": {
      "aws:MultiFactorAuthPresent": "true"
    }
  }
}

解决:
# 先获取 MFA 会话令牌
aws sts get-session-token \
  --serial-number arn:aws:iam::123456789012:mfa/alice \
  --token-code 123456

# 使用临时凭证 assume role

4. 会话持续时间超限:

错误信息:
DurationSeconds exceeds the MaxSessionDuration set for this role

原因:
└─ 请求的会话时长超过角色配置的最大值

检查角色配置:
aws iam get-role --role-name MyRole \
  --query 'Role.MaxSessionDuration'

解决:
# 使用较短的持续时间
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/MyRole \
  --role-session-name my-session \
  --duration-seconds 3600  # 1小时

调试工具

IAM Policy Simulator

功能:

模拟器能做什么:
├─ 测试用户/角色的权限
├─ 模拟 API 调用
├─ 查看策略评估结果
├─ 识别拒绝原因
└─ 无需实际执行操作(安全)

使用方法:

Web 控制台:

1. 访问:https://policysim.aws.amazon.com/
2. 选择用户或角色
3. 选择服务和操作
4. 指定资源(可选)
5. 添加条件(可选)
6. 点击"Run Simulation"
7. 查看结果:
   ├─ Allowed(允许)
   ├─ Denied(拒绝)
   └─ Implicitly Denied(隐式拒绝)

CLI 方式:

# 模拟单个操作
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/alice \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::my-bucket/file.txt

# 输出示例
{
  "EvaluationResults": [
    {
      "EvalActionName": "s3:GetObject",
      "EvalResourceName": "arn:aws:s3:::my-bucket/file.txt",
      "EvalDecision": "allowed",
      "MatchedStatements": [
        {
          "SourcePolicyId": "UserPolicy1",
          "StartPosition": {...},
          "EndPosition": {...}
        }
      ]
    }
  ]
}

# 模拟多个操作
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/alice \
  --action-names s3:GetObject s3:PutObject s3:DeleteObject \
  --resource-arns arn:aws:s3:::my-bucket/*

# 添加条件上下文
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/alice \
  --action-names ec2:RunInstances \
  --resource-arns "*" \
  --context-entries \
    "ContextKeyName=aws:RequestedRegion,ContextKeyValues=us-east-1,ContextKeyType=string" \
    "ContextKeyName=aws:CurrentTime,ContextKeyValues=2024-01-11T10:00:00Z,ContextKeyType=date"

解读结果:

EvalDecision 值:
├─ allowed
│   └─ 有明确的 Allow,无 Deny
├─ explicitDeny
│   └─ 存在明确的 Deny 语句
└─ implicitDeny
    └─ 没有 Allow(默认拒绝)

MissingContextValues:
└─ 表示需要额外的上下文信息才能完整评估

IAM Access Analyzer

功能:

持续分析和发现:
├─ 对外共享的资源
├─ 公开访问的资源
├─ 跨账户访问
├─ 未使用的访问权限
├─ 过度授权
└─ 策略验证

启用 Access Analyzer:

# 创建 Analyzer
aws accessanalyzer create-analyzer \
  --analyzer-name my-analyzer \
  --type ACCOUNT

# 或组织级别
aws accessanalyzer create-analyzer \
  --analyzer-name org-analyzer \
  --type ORGANIZATION

查看发现:

# 列出所有发现
aws accessanalyzer list-findings \
  --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-analyzer

# 过滤特定类型
aws accessanalyzer list-findings \
  --analyzer-arn <analyzer-arn> \
  --filter 'resourceType={eq:["AWS::S3::Bucket"]}'

# 过滤活跃的发现
aws accessanalyzer list-findings \
  --analyzer-arn <analyzer-arn> \
  --filter 'status={eq:["ACTIVE"]}'

# 查看详细信息
aws accessanalyzer get-finding \
  --analyzer-arn <analyzer-arn> \
  --id <finding-id>

发现类型示例:

类型 1:Public Access(公开访问)
资源:S3 Bucket
问题:Bucket 策略允许所有人访问
建议:移除 Principal: "*" 或添加条件限制

类型 2:Cross-Account Access(跨账户访问)
资源:IAM Role
问题:信任策略允许外部账户承担角色
建议:验证是否合理,添加 External ID

类型 3:Unused Access(未使用的访问)
资源:IAM User
问题:Access Key 180 天未使用
建议:删除或停用未使用的凭证

类型 4:Overly Permissive(过度授权)
资源:IAM Policy
问题:策略使用 Action: "*", Resource: "*"
建议:应用最小权限原则

策略验证:

# 验证策略语法和安全性
aws accessanalyzer validate-policy \
  --policy-document file://policy.json \
  --policy-type IDENTITY_POLICY

# 输出包含:
# - ERRORS: 语法错误
# - SECURITY_WARNINGS: 安全问题
# - SUGGESTIONS: 改进建议
# - WARNINGS: 潜在问题

# 示例输出
{
  "findings": [
    {
      "findingType": "SECURITY_WARNING",
      "issueCode": "PASS_ROLE_WITH_STAR_IN_RESOURCE",
      "learnMoreLink": "https://...",
      "locations": [...],
      "findingDetails": "Using wildcards (*) in the resource can be overly permissive."
    }
  ]
}

CloudTrail 日志分析

查找权限拒绝事件:

# CLI 方式
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=AccessDenied \
  --max-results 50

# 查看特定用户的拒绝事件
aws cloudtrail lookup-events \
  --lookup-attributes \
    AttributeKey=Username,AttributeValue=alice \
  --start-time 2024-01-10T00:00:00Z \
  --end-time 2024-01-11T00:00:00Z \
  | jq '.Events[] | select(.CloudTrailEvent | fromjson | .errorCode == "AccessDenied")'

CloudTrail Insights 查询(Athena):

-- 创建表(首次)
CREATE EXTERNAL TABLE cloudtrail_logs (
  eventversion STRING,
  useridentity STRUCT<
    type:STRING,
    principalid:STRING,
    arn:STRING,
    accountid:STRING,
    invokedby:STRING,
    accesskeyid:STRING,
    userName:STRING,
    sessioncontext:STRUCT<
      attributes:STRUCT<
        mfaauthenticated:STRING,
        creationdate:STRING>,
      sessionissuer:STRUCT<
        type:STRING,
        principalid:STRING,
        arn:STRING,
        accountid:STRING,
        username:STRING>>>,
  eventtime STRING,
  eventsource STRING,
  eventname STRING,
  awsregion STRING,
  sourceipaddress STRING,
  useragent STRING,
  errorcode STRING,
  errormessage STRING,
  requestparameters STRING,
  responseelements STRING,
  requestid STRING,
  eventid STRING,
  resources ARRAY<STRUCT<
    ARN:STRING,
    accountId:STRING,
    type:STRING>>,
  eventtype STRING,
  apiversion STRING,
  readonly BOOLEAN,
  recipientaccountid STRING
)
PARTITIONED BY (region string, year string, month string, day string)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://your-cloudtrail-bucket/AWSLogs/123456789012/CloudTrail/';

-- 查询 Access Denied 事件
SELECT 
  eventtime,
  useridentity.username,
  eventname,
  eventsource,
  errorcode,
  errormessage,
  sourceipaddress,
  requestparameters
FROM cloudtrail_logs
WHERE errorcode = 'AccessDenied'
  AND year = '2024'
  AND month = '01'
  AND day = '11'
ORDER BY eventtime DESC
LIMIT 100;

-- 分析特定用户的拒绝操作
SELECT 
  eventname,
  eventsource,
  COUNT(*) as denial_count,
  array_agg(DISTINCT errormessage) as error_messages
FROM cloudtrail_logs
WHERE errorcode = 'AccessDenied'
  AND useridentity.username = 'alice'
  AND year = '2024'
  AND month = '01'
GROUP BY eventname, eventsource
ORDER BY denial_count DESC;

-- 识别异常 IP 访问
SELECT 
  sourceipaddress,
  useridentity.username,
  COUNT(*) as request_count,
  array_agg(DISTINCT eventname) as operations
FROM cloudtrail_logs
WHERE year = '2024'
  AND month = '01'
  AND day = '11'
  AND sourceipaddress NOT LIKE '203.0.113.%'  -- 公司 IP 段
GROUP BY sourceipaddress, useridentity.username
HAVING request_count > 10
ORDER BY request_count DESC;

Credential Report

生成和下载:

# 生成报告
aws iam generate-credential-report

# 等待生成完成
while true; do
  STATUS=$(aws iam get-credential-report --query 'ReportFormat' 2>&1)
  if [[ $STATUS != *"ReportNotPresent"* ]]; then
    break
  fi
  sleep 5
done

# 下载报告
aws iam get-credential-report \
  --query 'Content' \
  --output text \
  | base64 --decode > credential-report.csv

# 查看报告
cat credential-report.csv | column -t -s ','

报告字段:

user,arn,user_creation_time,password_enabled,password_last_used,
password_last_changed,password_next_rotation,mfa_active,
access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,
access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,
cert_1_active,cert_2_active

分析未使用的凭证:

# 使用 csvkit 分析
pip install csvkit

# 查找 90 天未使用的 Access Key
csvgrep -c access_key_1_last_used_date -r "^(19|20)\d{2}" credential-report.csv \
  | csvgrep -c access_key_1_active -m TRUE \
  | awk -F',' 'NR==1 || (systime() - mktime(gensub(/T/, " ", "g", gensub(/:/, " ", "g", $11))) > 7776000)'

# 查找未启用 MFA 的用户
csvgrep -c mfa_active -m false credential-report.csv \
  | csvcut -c user,arn,mfa_active

排查流程

系统化排查步骤

Step 1:确认错误详情

收集信息:
├─ 完整的错误消息
├─ 发生时间
├─ 尝试的操作
├─ 目标资源
├─ 执行的主体(用户/角色)
└─ 源 IP 地址

Step 2:使用 Policy Simulator

# 模拟相同的操作
aws iam simulate-principal-policy \
  --policy-source-arn <principal-arn> \
  --action-names <service>:<action> \
  --resource-arns <resource-arn>

# 分析结果
# - 如果 allowed:检查其他因素(条件、资源策略)
# - 如果 explicitDeny:找到 Deny 来源
# - 如果 implicitDeny:添加 Allow 策略

Step 3:检查 CloudTrail

# 查找具体的拒绝事件
aws cloudtrail lookup-events \
  --lookup-attributes \
    AttributeKey=EventName,AttributeValue=<operation> \
  --start-time <timestamp>

# 查看详细信息
echo $EVENT_JSON | jq '.errorCode, .errorMessage, .userIdentity, .requestParameters'

Step 4:检查策略层级

检查顺序:
1. SCP(如果在 Organizations 中)
   aws organizations list-policies-for-target --target-id <account-id>
   
2. 权限边界
   aws iam get-user/get-role --user-name/role-name <name>
   
3. 会话策略(如果使用 AssumeRole)
   检查 assume-role 调用时传递的策略
   
4. 身份策略
   aws iam list-attached-user-policies --user-name <name>
   aws iam list-user-policies --user-name <name>
   
5. 资源策略
   aws s3api get-bucket-policy --bucket <name>
   aws kms get-key-policy --key-id <id>

Step 5:使用 Access Analyzer

# 检查资源是否对外暴露
aws accessanalyzer list-findings \
  --analyzer-arn <analyzer-arn> \
  --filter 'resourceArn={eq:["<resource-arn>"]}'

# 检查策略问题
aws accessanalyzer validate-policy \
  --policy-document file://suspect-policy.json \
  --policy-type IDENTITY_POLICY

Step 6:临时提权测试

# 临时授予 AdministratorAccess 测试
aws iam attach-user-policy \
  --user-name alice \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# 测试操作
# 如果成功:说明是权限不足
# 如果失败:说明是其他问题(SCP、资源策略等)

# 移除临时权限
aws iam detach-user-policy \
  --user-name alice \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

常见问题解决方案

问题:无法列出 S3 bucket 中的对象

症状:
aws s3 ls s3://my-bucket/
An error occurred (AccessDenied)

可能原因:
1. 缺少 s3:ListBucket 权限
2. Bucket 策略拒绝
3. S3 Block Public Access 设置

解决:
# 检查权限
aws iam simulate-principal-policy \
  --policy-source-arn <user-arn> \
  --action-names s3:ListBucket \
  --resource-arns arn:aws:s3:::my-bucket

# 检查 Bucket 策略
aws s3api get-bucket-policy --bucket my-bucket

# 检查 Block Public Access
aws s3api get-public-access-block --bucket my-bucket

问题:KMS 加密操作失败

症状:
Error: AccessDeniedException: User is not authorized to perform: 
kms:Decrypt on resource

解决:
1. 检查 KMS Key Policy
   aws kms get-key-policy --key-id <key-id> --policy-name default
   
2. 确保 Key Policy 允许账户访问
   {
     "Sid": "Enable IAM User Permissions",
     "Effect": "Allow",
     "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
     "Action": "kms:*",
     "Resource": "*"
   }
   
3. 检查用户的 IAM 策略
   需要 kms:Decrypt 权限

掌握这些排查技巧和工具,能够快速定位和解决 IAM 权限问题!