背景
在做多云安全架构的 Landing Zone 对比中,重点研究了各云的组织治理服务——AWS 的 Control Tower、阿里云的 CGC、腾讯云的 Control Center、华为云的 RGC。在实际落地 AWS Control Tower 时,遇到了一个 IaC 场景下的典型冲突:Mandatory Guardrail 生成的 SCP 拦截了 Terraform 的正常操作。
具体表现为:CT 的部分 Mandatory Guardrail 使用 Resource = "*" 的 SCP 拦截操作(如 SSE 加密、生命周期配置),且只豁免了 AWSControlTowerExecution 这一个角色。Terraform 默认使用的 OrganizationAccountAccessRole 不在豁免名单中,直接导致 Apply 失败。
本文记录这个问题的完整排查过程和已验证的解决方案。
问题复现
执行 Terraform 配置 S3 生命周期策略时报错:
1
2
Error: error putting S3 Bucket Lifecycle Configuration:
AccessDenied: User: ... is not authorized to perform ...
根源是 CT 的一条 Mandatory Guardrail——AWS-GR_AUDIT_BUCKET_RETENTION_POLICY,其底层 SCP 策略类似:
1
2
3
4
5
6
7
8
9
10
{
"Effect": "Deny",
"Action": "s3:PutLifecycleConfiguration",
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalARN": "arn:aws:iam::*:role/AWSControlTowerExecution"
}
}
}
简化理解:除 AWSControlTowerExecution 角色外,所有主体都被禁止执行该操作。
排查过程
关键发现:AWSControlTowerExecution 在 Management 账号不存在,仅存在于 Control Tower 自动创建的成员账号(Log-Archive、Audit 等)中。
尝试在 Management 账号查找该角色:
1
2
aws iam get-role --role-name AWSControlTowerExecution
# → NoSuchEntity,Management 账号中不存在
但在 Log-Archive 账号中,可以直接通过 IAM User 跨账号 Assume:
1
2
3
4
aws sts assume-role \
--role-arn "arn:aws:iam::575267657812:role/AWSControlTowerExecution" \
--role-session-name test-ct-bypass
# ✅ 成功!
这意味着 Control Tower 自动为该角色配置了跨账号信任——Management 账号的 IAM User 可以直接 Assume 成员账号的 AWSControlTowerExecution,无需额外配置。
信任链如下:
1
2
IAM User (terraform-bootstrap-admin)
→ Log-Archive 的 AWSControlTowerExecution(绕过 CT Guardrail)
Terraform 配置方案
添加 Provider
在 providers.tf 中增加一个使用 CT 角色的 provider:
1
2
3
4
5
6
7
8
provider "aws" {
alias = "log_archive_ct"
region = var.aws_region
profile = var.aws_profile
assume_role {
role_arn = "arn:aws:iam::575267657812:role/AWSControlTowerExecution"
}
}
模块声明
模块需声明接受第二个 provider alias:
1
2
3
4
5
6
7
8
9
# modules/logging/terraform.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
configuration_aliases = [aws.log_archive_ct]
}
}
}
模块调用
主模块传入 alias:
1
2
3
4
5
6
7
8
module "cross_cloud_logging" {
source = "../../modules/logging"
providers = {
aws = aws.log_archive
aws.log_archive_ct = aws.log_archive_ct
}
}
资源指定
在被拦截的资源上显式指定 CT provider:
1
2
3
4
resource "aws_s3_bucket_lifecycle_configuration" "this" {
provider = aws.log_archive_ct
# ...
}
适用场景
| 场景 | 使用 OrganizationAccountAccessRole | 使用 AWSControlTowerExecution |
|---|---|---|
| 创建 S3 桶 | ✅ 可以 | — |
| 配置版本控制 | ✅ 可以 | — |
| 配置 Object Lock | ✅ 可以 | — |
| 配置 SSE 加密 | ❌ 被 CT Guardrail 拦截 | ✅ 可以 |
| 配置生命周期 | ❌ 被 CT Guardrail 拦截 | ✅ 可以 |
| 配置桶策略 | ❌ 被 CT Guardrail 拦截 | ✅ 可以 |
注意事项
只在必要时使用——推荐在被 Guardrail 拦截的具体资源上指定 CT provider,常规资源仍用
OrganizationAccountAccessRole,维持最小权限原则。角色位置——
AWSControlTowerExecution不在 Management 账号,只在 Control Tower 创建的成员账号(Log-Archive、Audit 等)中。对不同的成员账号使用,只需修改role_arn中的账号 ID。复用性——如 Audit 账号也面临相同的 Guardrail 限制,同样的方案只需修改账号 ID 即可复用。
不要滥用绕过——Guardrail 本质是安全基线,绕过前应评估是否有更优方案。在实际项目中最终选择的方案是关闭过宽的 Mandatory Guardrail,用自定义 SCP 替代,
AWSControlTowerExecution作为备用方案存档。
总结
AWSControlTowerExecution 可以被 IAM User 直接从 Management 账号跨账号 Assume,这是控制台不直接透露、实测才确认的事实。当 IaC 工具与 Control Tower 的 Mandatory Guardrail 冲突时,这是一个已验证的后备方案。
这个案例也反映了一个更普遍的问题:AWS Control Tower 的设计默认假设管理员通过 CT Console 操作,未充分考虑 IaC 场景。当安全基线 SCP 以 Resource = "*" 无差别拦截时,留给运维人员的通道只有 AWSControlTowerExecution 这一个狭窄入口,跨云架构师需要提前了解这个限制。