使用Terraform重命名S3 Bucket时删除对象遇AccessDenied问题
尝试通过Terraform重命名S3 Bucket时,操作会自动删除现有Bucket并创建新Bucket,但出现大量AccessDenied错误:
│ Error: emptying S3 Bucket (my-bucket): | deleting S3 Bucket (my-vuxkwr) objects: 1000 errors occurred: │ * deleting: S3 object (AWSLogs/123456789/elasticloadbalancing/us-west-2/2024/03/15/123456789_elasticloadbalancing_us-west-2_app.my-api.log.gz) version (abcde_fghij.klmno): | AccessDenied: Access Denied # omitted the rest of the AccessDenied errors
我的S3 Bucket Terraform配置如下:
data "aws_caller_identity" "current" {} data "aws_elb_service_account" "main" {} locals { bucket_arn = "arn:aws:s3:::${var.bucket_name}" } resource "aws_s3_bucket" "main" { bucket = var.bucket_name force_destroy = true tags = var.tags } resource "aws_s3_bucket_logging" "main" { bucket = aws_s3_bucket.main.id target_bucket = var.s3_access_logs_bucket_name target_prefix = "${var.bucket_name}/" } resource "aws_s3_bucket_versioning" "main" { bucket = aws_s3_bucket.main.id versioning_configuration { status = "Enabled" } } resource "aws_s3_bucket_server_side_encryption_configuration" "main" { bucket = aws_s3_bucket.main.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } resource "aws_s3_bucket_policy" "main" { bucket = aws_s3_bucket.main.id policy = data.aws_iam_policy_document.bucket_policy.json depends_on = [ aws_s3_bucket.main ] } data "aws_iam_policy_document" "bucket_policy" { statement { sid = "RequireSSL" effect = "Deny" principals { type = "*" identifiers = ["*"] } actions = ["s3:*"] resources = [ local.bucket_arn, "${local.bucket_arn}/*", ] condition { test = "Bool" variable = "aws:SecureTransport" values = [ "false" ] } } statement { sid = "AwsElbServiceAccount" actions = [ "s3:PutObject" ] resources = [ "${aws_s3_bucket.main.arn}/*" ] principals { type = "AWS" identifiers = [data.aws_elb_service_account.main.arn] } } } resource "aws_s3_bucket_ownership_controls" "main" { bucket = aws_s3_bucket.main.id rule { object_ownership = "BucketOwnerEnforced" } depends_on = [ aws_s3_bucket.main ] } resource "aws_s3_bucket_public_access_block" "main" { bucket = aws_s3_bucket.main.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true }
当前执行Terraform的角色相关权限配置:
data "aws_iam_policy_document" "provision_s3_bucket" { statement { sid = "S3Bucket" actions = [ "s3:DeleteObject", "s3:DeleteObjects", ] resources = [ "arn:aws:s3:::*", ] } }
明明已经配置了所有S3资源的对象删除权限,为何还会出现AccessDenied错误?
核心原因:版本化Bucket需要专属的删除权限
你的Bucket开启了版本化(aws_s3_bucket_versioning状态为Enabled),对于版本化Bucket中的对象,删除具体对象版本需要的是s3:DeleteObjectVersion和s3:DeleteObjectVersions权限,而非普通的s3:DeleteObject或s3:DeleteObjects。
当前角色仅配置了普通删除权限,这类权限只能用于非版本化Bucket的对象删除,或是给版本化Bucket的对象创建删除标记(Delete Marker),但无法真正删除已存在的对象历史版本。而Terraform的force_destroy = true在清空版本化Bucket时,会尝试删除所有对象版本,因此触发权限不足的错误。
额外验证:Bucket Policy的SSL限制
另外需要确认Terraform执行时是否使用HTTPS连接:你的Bucket Policy中有RequireSSL规则,拒绝所有非HTTPS的S3操作。如果Terraform调用AWS API时用了HTTP,也会触发AccessDenied,但从错误信息看,核心问题还是版本化权限缺失——错误明确指向删除特定对象版本。
解决方法
- 补充版本化对象删除权限
更新角色的IAM策略,添加针对版本化对象的删除权限:
data "aws_iam_policy_document" "provision_s3_bucket" { statement { sid = "S3Bucket" actions = [ "s3:DeleteObject", "s3:DeleteObjects", "s3:DeleteObjectVersion", "s3:DeleteObjectVersions" ] resources = [ "arn:aws:s3:::*", ] } }
临时关闭版本化(仅测试场景推荐)
如果不需要保留对象历史版本,可以先将Bucket版本化状态设为Suspended,等待Terraform清空Bucket后再重新开启。但此操作会丢失所有历史版本,生产环境不建议使用。确认SSL连接配置
确保Terraform执行环境的AWS配置使用HTTPS(默认已开启,但如果自定义了endpoint需要检查),避免触发Bucket Policy的SSL拒绝规则。
内容的提问来源于stack exchange,提问作者Karen Olson

