IAM 策略深入

全面理解 IAM 策略的类型、语法、评估逻辑和高级用法。

策略类型

基于身份的策略(Identity-based Policies)

托管策略(Managed Policies):

AWS 托管策略:

特性:
├─ AWS 预先创建和维护
├─ 涵盖常见使用场景
├─ 自动更新(添加新服务/功能)
├─ 无法修改内容
├─ 橙色图标标识
└─ 可附加到多个实体

常用 AWS 托管策略:
├─ AdministratorAccess
│   └─ 完全访问权限(*:*)
├─ PowerUserAccess
│   └─ 完全访问(除 IAM 和 Organizations)
├─ ReadOnlyAccess
│   └─ 所有服务的只读权限
├─ SecurityAudit
│   └─ 安全审计所需权限
├─ Billing
│   └─ 查看账单信息
└─ 服务特定策略
    ├─ AmazonS3FullAccess
    ├─ AmazonEC2ReadOnlyAccess
    └─ AWSLambdaExecute

命名规范:
├─ [Service]FullAccess - 完全访问
├─ [Service]ReadOnlyAccess - 只读访问
├─ [Service]PowerUser - 高级用户
└─ AWS[Service]Role - 服务角色策略

客户托管策略:

特性:
├─ 你创建和管理
├─ 可自定义内容
├─ 支持版本控制(最多 5 个版本)
├─ 可复用到多个实体
├─ 蓝色图标标识
└─ 推荐用于组织标准策略

使用场景:
├─ AWS 托管策略过于宽泛
├─ 需要精确的权限控制
├─ 组织特定的权限模板
├─ 合规要求的自定义策略
└─ 跨多个用户/角色共享

版本管理:
├─ 每次修改创建新版本
├─ 可以回滚到旧版本
├─ 设置默认版本
├─ 最多保留 5 个版本
└─ 删除旧版本释放空间

内联策略(Inline Policies):

特性:
├─ 直接嵌入到实体(用户/组/角色)
├─ 一对一严格关系
├─ 删除实体时自动删除
├─ 无法复用
└─ 无版本控制

使用场景:
├─ 严格的一对一策略关系
├─ 确保策略与实体同生死
├─ 临时、特殊的权限
├─ 避免策略被误附加到其他实体
└─ 例外情况的权限授予

何时使用:
✅ 用于特定实体的独特权限
✅ 测试和开发环境的临时权限
❌ 不要用于标准、可复用的权限
❌ 不要用于需要审计的生产权限

基于资源的策略(Resource-based Policies)

支持资源策略的服务:

存储:
├─ S3 Bucket(Bucket Policy)
├─ S3 Glacier Vault(Vault Policy)
├─ EFS File System
└─ AWS Backup Vault

计算:
├─ Lambda Function(Resource Policy)
└─ ECR Repository(Repository Policy)

消息和队列:
├─ SQS Queue(Queue Policy)
├─ SNS Topic(Topic Policy)
└─ EventBridge Event Bus

密钥和密码:
├─ KMS Key(Key Policy)
├─ Secrets Manager Secret
└─ ACM Private CA

API 和网络:
├─ API Gateway REST API
├─ VPC Endpoint
└─ CloudWatch Logs

其他:
├─ AWS Organizations SCP
├─ CodeArtifact Repository
└─ IoT Policy

资源策略 vs 身份策略对比:

维度 身份策略 资源策略
附加位置 IAM 实体 资源本身
Principal 不需要 必须指定
跨账户 需要两步(角色信任) 直接支持
可见性 IAM 控制台 资源服务控制台
使用场景 定义"谁能做什么" 定义"谁能访问我"
评估 在主体侧 在资源侧

资源策略示例:

S3 Bucket 策略(跨账户访问):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCrossAccountRead",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}

Lambda 资源策略(API Gateway 调用):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAPIGatewayInvoke",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:my-function",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:execute-api:us-east-1:123456789012:api-id/*"
        }
      }
    }
  ]
}

策略语法详解

JSON 结构

完整策略结构:

{
  "Version": "2012-10-17",
  "Id": "PolicyID",
  "Statement": [
    {
      "Sid": "StatementID",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/alice"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-server-side-encryption": "AES256"
        }
      }
    }
  ]
}

元素说明:

Version(必需):

作用:策略语言版本
值:"2012-10-17"(当前版本,也是唯一推荐值)
历史:"2008-10-17"(旧版本,功能受限)

注意:
├─ 必须是策略的第一个元素
├─ 使用新版本获得完整功能
└─ 不是策略的修订版本

Statement(必需):

作用:策略的主体,包含一个或多个语句
类型:对象数组
每个语句是一个完整的权限声明

多语句示例:
{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::bucket1/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::bucket2/*"
    }
  ]
}

Sid(可选):

作用:Statement ID,语句的唯一标识符
类型:字符串
用途:
├─ 便于识别和引用
├─ 日志和错误消息中显示
├─ 策略编辑器中的标签
└─ 文档说明

命名建议:
├─ 使用描述性名称
├─ CamelCase 或 kebab-case
├─ 说明语句的目的
└─ 例如:"AllowS3ReadAccess"

Effect(必需):

作用:允许或拒绝访问
值:
├─ "Allow" - 允许
└─ "Deny" - 明确拒绝

优先级:
Deny > Allow > 默认 Deny

注意:
├─ 显式 Deny 始终优先
├─ 默认情况下所有请求都被拒绝
└─ 至少需要一个 Allow 才能访问

Principal(条件必需):

作用:指定被允许或拒绝的主体
使用场景:
├─ 资源策略(必需)
├─ 信任策略(必需)
└─ 身份策略(不使用)

Principal 类型:
├─ AWS 账户和 IAM 实体
├─ AWS 服务
├─ 联合用户
├─ Canonical User ID(S3)
└─ 通配符(*)表示所有人

Principal 示例:

{
  "Principal": {
    "AWS": [
      "arn:aws:iam::123456789012:user/alice",
      "arn:aws:iam::123456789012:role/MyRole",
      "123456789012"
    ],
    "Service": [
      "ec2.amazonaws.com",
      "lambda.amazonaws.com"
    ],
    "Federated": "arn:aws:iam::123456789012:saml-provider/MyProvider",
    "CanonicalUser": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be"
  }
}

// 所有人(公开访问)
{
  "Principal": "*"
}

// 所有 AWS 账户
{
  "Principal": {
    "AWS": "*"
  }
}

Action(必需):

作用:指定允许或拒绝的操作
格式:service:action

单个操作:
"Action": "s3:GetObject"

多个操作:
"Action": [
  "s3:GetObject",
  "s3:PutObject"
]

通配符:
"Action": "s3:*"           # S3 所有操作
"Action": "s3:Get*"        # 所有 Get 操作
"Action": "*"              # 所有服务的所有操作

NotAction(排除):
"NotAction": "s3:DeleteBucket"  # 除了删除 bucket 的所有操作

Resource(必需):

作用:指定策略应用的资源
格式:ARN(Amazon Resource Name)

ARN 格式:
arn:partition:service:region:account-id:resource-type/resource-id

示例:
"Resource": "arn:aws:s3:::my-bucket/*"
"Resource": "arn:aws:ec2:us-east-1:123456789012:instance/*"
"Resource": "*"  # 所有资源

多个资源:
"Resource": [
  "arn:aws:s3:::bucket1/*",
  "arn:aws:s3:::bucket2/*"
]

通配符:
"Resource": "arn:aws:s3:::my-bucket/home/${aws:username}/*"
"Resource": "arn:aws:ec2:*:*:instance/*"

NotResource(排除):
"NotResource": "arn:aws:s3:::sensitive-bucket/*"

策略条件

条件运算符完整列表

字符串条件:

StringEquals          - 精确匹配(区分大小写)
StringNotEquals       - 不等于
StringLike            - 模式匹配(支持 * 和 ?)
StringNotLike         - 不匹配模式
StringEqualsIgnoreCase - 忽略大小写匹配
StringNotEqualsIgnoreCase - 忽略大小写不等于

示例:
"StringEquals": {
  "aws:username": "alice"
}
"StringLike": {
  "s3:prefix": "home/*"
}

数值条件:

NumericEquals         - 等于
NumericNotEquals      - 不等于
NumericLessThan       - 小于
NumericLessThanEquals - 小于等于
NumericGreaterThan    - 大于
NumericGreaterThanEquals - 大于等于

示例:
"NumericLessThan": {
  "s3:max-keys": "10"
}

日期条件:

DateEquals            - 日期等于
DateNotEquals         - 日期不等于
DateLessThan          - 日期早于
DateLessThanEquals    - 日期早于等于
DateGreaterThan       - 日期晚于
DateGreaterThanEquals - 日期晚于等于

格式:ISO 8601
示例:
"DateGreaterThan": {
  "aws:CurrentTime": "2024-01-01T00:00:00Z"
}

布尔条件:

Bool                  - 布尔值比较

示例:
"Bool": {
  "aws:SecureTransport": "true",
  "aws:MultiFactorAuthPresent": "true"
}

IP 地址条件:

IpAddress             - IP 地址匹配(CIDR)
NotIpAddress          - IP 地址不匹配

示例:
"IpAddress": {
  "aws:SourceIp": [
    "203.0.113.0/24",
    "2001:DB8:1234:5678::/64"
  ]
}

ARN 条件:

ArnEquals             - ARN 精确匹配
ArnLike               - ARN 模式匹配
ArnNotEquals          - ARN 不等于
ArnNotLike            - ARN 不匹配模式

示例:
"ArnLike": {
  "AWS:SourceArn": "arn:aws:s3:::my-bucket/*"
}

Null 条件:

Null                  - 检查键是否存在

示例:
"Null": {
  "aws:TokenIssueTime": "false"  # 键必须存在
}

条件键(Condition Keys)

全局条件键:

aws:CurrentTime       - 当前时间
aws:EpochTime         - Unix 时间戳
aws:SecureTransport   - 是否使用 HTTPS
aws:SourceIp          - 请求源 IP
aws:SourceVpc         - 源 VPC ID
aws:SourceVpce        - 源 VPC 端点 ID
aws:UserAgent         - HTTP User-Agent
aws:username          - IAM 用户名
aws:userid            - 唯一用户 ID
aws:PrincipalType     - 主体类型
aws:PrincipalArn      - 主体 ARN
aws:PrincipalAccount  - 主体账户 ID
aws:PrincipalOrgID    - 组织 ID
aws:PrincipalTag/<key> - 主体标签
aws:RequestedRegion   - 请求的区域
aws:MultiFactorAuthPresent - 是否使用 MFA
aws:MultiFactorAuthAge - MFA 认证时长
aws:TokenIssueTime    - 临时令牌签发时间

服务特定条件键:

S3 条件键:

s3:prefix             - 对象键前缀
s3:max-keys           - 列出的最大对象数
s3:x-amz-acl          - ACL 设置
s3:x-amz-server-side-encryption - 加密类型
s3:x-amz-server-side-encryption-aws-kms-key-id - KMS 密钥
s3:ExistingObjectTag/<key> - 对象标签
s3:VersionId          - 对象版本 ID

示例:
"StringEquals": {
  "s3:x-amz-server-side-encryption": "AES256"
}

EC2 条件键:

ec2:InstanceType      - 实例类型
ec2:Region            - 区域
ec2:ResourceTag/<key> - 资源标签
ec2:Vpc               - VPC ID
ec2:AvailabilityZone  - 可用区
ec2:EbsOptimized      - 是否 EBS 优化
ec2:Tenancy           - 租户类型

示例:
"StringEquals": {
  "ec2:InstanceType": "t3.micro"
}

策略变量

变量语法:

${variable-name}

使用位置:
├─ Resource 元素
├─ Condition 元素
└─ 不能在 Principal 元素中使用

常用变量:

${aws:username}       - IAM 用户名
${aws:userid}         - 用户 ID
${aws:PrincipalTag/TagName} - 主体标签值
${aws:SourceIp}       - 源 IP 地址
${aws:CurrentTime}    - 当前时间
${aws:RequestedRegion} - 请求的区域

示例:用户专属 S3 文件夹
"Resource": "arn:aws:s3:::my-bucket/home/${aws:username}/*"

标签变量(ABAC):

主体标签:
${aws:PrincipalTag/Department}
${aws:PrincipalTag/Project}

资源标签:
${ec2:ResourceTag/Owner}
${s3:ExistingObjectTag/Classification}

请求标签:
${aws:RequestTag/Environment}

示例:
"Condition": {
  "StringEquals": {
    "ec2:ResourceTag/Owner": "${aws:username}"
  }
}

策略评估逻辑

评估流程

完整决策流程:

1. 默认拒绝(Implicit Deny)
   ↓
2. 收集所有适用的策略
   ├─ 身份策略(用户、组、角色)
   ├─ 资源策略
   ├─ 权限边界
   ├─ SCP(组织策略)
   └─ 会话策略
   ↓
3. 评估 SCP(如果有)
   ├─ SCP 拒绝 → 最终拒绝
   └─ SCP 允许 → 继续
   ↓
4. 评估权限边界(如果有)
   ├─ 超出边界 → 最终拒绝
   └─ 在边界内 → 继续
   ↓
5. 评估身份和资源策略
   ├─ 有显式 Deny? → 最终拒绝
   ├─ 有显式 Allow? → 继续
   └─ 无 Allow? → 最终拒绝
   ↓
6. 评估会话策略(如果有)
   ├─ 超出会话限制 → 最终拒绝
   └─ 在限制内 → 最终允许

结果:允许或拒绝

同账户 vs 跨账户评估

同账户访问:

评估逻辑:身份策略 OR 资源策略

场景 1:仅身份策略
IAM 用户 → S3 Bucket(无 Bucket 策略)
评估:用户的 IAM 策略

场景 2:仅资源策略
Lambda 函数 → S3 Bucket(有 Bucket 策略允许 Lambda)
评估:Bucket 策略

场景 3:同时存在
IAM 用户 → S3 Bucket(都有策略)
评估:任一策略允许即可(OR 关系)

跨账户访问:

评估逻辑:身份策略 AND 资源策略

必须同时满足:
├─ 源账户的身份策略允许
└─ 目标账户的资源策略允许

示例:
账户 A 用户访问账户 B 的 S3:
✓ 账户 A:用户策略允许 s3:GetObject
✓ 账户 B:Bucket 策略允许账户 A 访问
→ 最终:允许

如果任一策略拒绝:
✗ 账户 A:允许
✗ 账户 B:未明确允许
→ 最终:拒绝

策略冲突处理

Deny 优先原则:

情况 1:显式 Deny
策略 A:Allow s3:*
策略 B:Deny s3:DeleteBucket
结果:允许所有 S3 操作,除了 DeleteBucket

情况 2:多个 Allow
策略 A:Allow s3:GetObject on bucket1
策略 B:Allow s3:GetObject on bucket2
结果:可以访问两个 bucket(权限合并)

情况 3:无 Allow
没有任何策略授予权限
结果:默认拒绝(Implicit Deny)

情况 4:Deny 覆盖 Allow
策略 A:Allow *:*(管理员权限)
SCP:Deny cloudtrail:StopLogging
结果:允许一切,除了停止 CloudTrail

理解策略的类型、语法和评估逻辑是精确控制 AWS 权限的关键!