Skip to content

为什么DataSync需要ExternalID

sts:ExternalId 是 AWS IAM 中用于防止”confused deputy”(混淆代理)攻击的安全机制。让我详细解释:

External ID 是一个额外的验证密钥,在跨账户角色代入(AssumeRole)时使用,确保只有知道这个密钥的实体才能代入角色。

假设没有 External ID 的情况:

graph TB
    subgraph "场景:恶意攻击"
        A[攻击者 - 账户 C] -->|诱骗| B[DataSync 服务<br/>在账户 A]
        B -->|代入角色| C[受害者角色<br/>在账户 B]
        C -->|访问| D[受害者数据<br/>S3 存储桶]
    end
    
    style A fill:#ff6b6b
    style D fill:#ffe1e1

攻击步骤:

  1. 账户 B(受害者)配置了跨账户角色,允许账户 A 代入
  2. **攻击者(账户 C)**知道了这个角色的 ARN
  3. 攻击者在账户 A 的 DataSync 服务中配置目标位置,使用账户 B 的角色 ARN
  4. DataSync(在账户 A)成功代入了账户 B 的角色
  5. 攻击者通过账户 A 的 DataSync 访问了账户 B 的数据
graph TB
    subgraph "安全场景:带 External ID"
        A[攻击者] -.->|不知道 External ID| B[DataSync 服务]
        B -.->|代入失败| C[受害者角色<br/>需要 External ID]
        D[合法用户] -->|提供正确 External ID| B
        B -->|代入成功| C
    end
    
    style A fill:#ff6b6b
    style D fill:#51cf66
    style C fill:#e1f5ff

有了 External ID,攻击者即使知道角色 ARN,也无法成功代入角色,因为他们不知道 External ID。

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole"
}
]
}

问题: 账户 A 的任何服务或用户都可以代入这个角色

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "datasync-cross-account-transfer-secret-key-12345"
}
}
}
]
}

安全性: 只有知道 External ID 的人才能代入角色

External ID 应该是唯一且难以猜测的字符串:

# 方法 1:使用 uuidgen
uuidgen
# 输出:e5c8f3a7-4b2d-4f1e-9c3a-8d7e6f5a4b3c
# 方法 2:使用 openssl
openssl rand -hex 32
# 输出:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
# 方法 3:自定义有意义的字符串
# datasync-cross-account-project-phoenix-2024
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "你生成的External-ID"
}
}
}
]
}

3. 在源账户使用 External ID 代入角色

Section titled “3. 在源账户使用 External ID 代入角色”

通过 AWS CLI:

# 测试角色代入
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/DataSyncDestinationRole \
--role-session-name test-session \
--external-id "你生成的External-ID" \
--profile account-a
# 如果 External ID 不匹配,会收到错误:
# An error occurred (AccessDenied) when calling the AssumeRole operation:
# User: ... is not authorized to perform: sts:AssumeRole

在 DataSync 中使用:

创建目标位置时,DataSync 会自动使用 External ID(如果在角色中配置了):

aws datasync create-location-s3 \
--s3-bucket-arn arn:aws:s3:::destination-bucket \
--s3-config '{
"BucketAccessRoleArn": "arn:aws:iam::222222222222:role/DataSyncDestinationRole"
}' \
--profile account-a

DataSync 会尝试代入角色,如果需要 External ID,操作会失败并提示需要提供 External ID。

不需要 External ID:

  • 你完全控制两个账户
  • 信任度高
  • 内部使用
{
"Condition": {
"StringEquals": {
"sts:ExternalId": "internal-datasync-2024"
}
}
}

必须使用 External ID:

  • 使用第三方 SaaS 服务
  • 服务提供商要求配置跨账户角色
  • 安全性要求高
{
"Condition": {
"StringEquals": {
"sts:ExternalId": "customer-12345-unique-secret-key"
}
}
}

推荐使用唯一的 External ID:

  • 为每个客户/项目生成不同的 External ID
  • 防止客户之间的相互访问
# 客户 A
EXTERNAL_ID="customer-a-$(uuidgen)"
# 客户 B
EXTERNAL_ID="customer-b-$(uuidgen)"
# 推荐:使用强随机字符串
EXTERNAL_ID=$(openssl rand -hex 32)
echo "External ID: $EXTERNAL_ID"
# 或者使用有意义但难以猜测的组合
EXTERNAL_ID="datasync-$(date +%Y%m%d)-$(openssl rand -hex 16)"
# 方式 1:存储在 AWS Systems Manager Parameter Store
aws ssm put-parameter \
--name "/datasync/external-id/destination-role" \
--value "$EXTERNAL_ID" \
--type "SecureString" \
--profile account-a
# 方式 2:存储在 AWS Secrets Manager
aws secretsmanager create-secret \
--name datasync-external-id \
--secret-string "$EXTERNAL_ID" \
--profile account-a
# 从 Parameter Store 获取
EXTERNAL_ID=$(aws ssm get-parameter \
--name "/datasync/external-id/destination-role" \
--with-decryption \
--query 'Parameter.Value' \
--output text \
--profile account-a)
# 从 Secrets Manager 获取
EXTERNAL_ID=$(aws secretsmanager get-secret-value \
--secret-id datasync-external-id \
--query 'SecretString' \
--output text \
--profile account-a)
# 每季度更新 External ID
NEW_EXTERNAL_ID=$(openssl rand -hex 32)
# 1. 更新目标角色的信任策略
aws iam update-assume-role-policy \
--role-name DataSyncDestinationRole \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111111111111:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"sts:ExternalId": "'$NEW_EXTERNAL_ID'"}
}
}]
}' \
--profile account-b
# 2. 更新 Parameter Store
aws ssm put-parameter \
--name "/datasync/external-id/destination-role" \
--value "$NEW_EXTERNAL_ID" \
--type "SecureString" \
--overwrite \
--profile account-a
  1. 总是使用 External ID:即使在内部跨账户场景
  2. 保持 External ID 机密:不要在代码库中硬编码
  3. 使用强随机字符串:至少 32 字符
  4. 定期轮换:建议每 3-6 个月更换
  5. 记录 External ID:在安全的密钥管理系统中
  6. 限制访问:只有必要的人员知道 External ID

External ID 是跨账户角色安全的重要组成部分:

  • 目的:防止混淆代理攻击
  • 本质:额外的验证密钥
  • 使用:在信任策略中配置,代入角色时提供
  • 最佳实践:使用强随机字符串,安全存储,定期轮换

在 DataSync 跨账户场景中,虽然 External ID 是”可选”的,但强烈推荐使用以增强安全性。