
TerraformでS3バケットの削除に失敗した場合、パブリックアクセスブロックだけ削除されることを回避したい。
はじめに
皆様こんにちは、あかいけです。
直近でTerraformで定義したS3バケット、およびパブリックアクセスブロックの削除をする際に困ったことがあったので、記事にしました。
モチベーション
こんな感じでS3バケットとパブリックアクセスブロックのリソースを作成したとします。
resource "aws_s3_bucket" "example" {
bucket = "example-bucket"
}
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
$ terraform state list
aws_s3_bucket.example
aws_s3_bucket_public_access_block.example
そしてこのリソースを削除する際に、パブリックアクセスブロックは削除、
しかしS3バケットにオブジェクトがあり、S3バケットだけ削除に失敗したとします。
$ terraform destroy
aws_s3_bucket_public_access_block.example: Destroying... [id=example-bucket]
aws_s3_bucket_public_access_block.example: Destruction complete after 0s
aws_s3_bucket.example: Destroying... [id=public-access-blockmy-tf-test-bucket-test]
╷
│ Error: deleting S3 Bucket (example-bucket): operation error S3: DeleteBucket, https response error StatusCode: 409, RequestID: XXXXXXXXXXXXXXXX, HostID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, api error BucketNotEmpty: The bucket you tried to delete is not empty
$ terraform state list
aws_s3_bucket.example
するとなんとパブリックアクセスブロックが全て無効になった脆弱なS3バケットだけが残ります…。
今回はこれを防ぐための案を考えてみました。
前提条件
実は2023年4月より、
S3のパブリックアクセスブロックはデフォルトで全て有効となっています。
なので以下のようにパブリックアクセスをすべてブロックするのであれば、
そもそもTerraform上でリソースとして定義する意味はありません。
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
逆に以下のように特定のパブリックアクセスだけ許可したい場合であれば、定義する必要があります。
(まぁこんな設定はまず使わないでしょうが…。)
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = false
block_public_policy = false
}
解決策
色々書いていますが、
基本的には案③を使えば問題ないと思います。
案①:S3 バケットの強制削除
まずはS3バケット側で force_destroy = true
を設定する場合です。
こうすることで、オブジェクトが存在していてもS3バケットが削除されます。
resource "aws_s3_bucket" "example" {
bucket = "example-bucket"
force_destroy = true
}
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
ただ誤って terraform destroy してしまった場合もS3バケットが削除されてしまうので、
実際のプロジェクトでは推奨される設定とは言えません。
案②:パブリックアクセスブロックの事前削除
次にパブリックアクセスブロックのみをステートファイルから削除する方法です。
これは既存のプロジェクト等で、すでにステートファイルで管理してしまっている場合に利用できます。
まずはパブリックアクセスブロックの記述をコメントアウト or 削除して、
# resource "aws_s3_bucket_public_access_block" "example" {
# bucket = aws_s3_bucket.example.id
# block_public_acls = true
# block_public_policy = true
# ignore_public_acls = true
# restrict_public_buckets = true
# }
パブリックアクセスブロックのみをステートファイルから削除します。
こうすることで、これ以降はパブリックアクセスブロックがTerraformで管理されなくなるので、実際のS3の設定が削除される心配もないということです。
terraform state rm aws_s3_bucket_public_access_block.example
しかしこれもTerraformのベストプラクティス的によろしくない気がするので、
あまり推奨される方法ではないでしょう…。
案③:パブリックアクセスブロックの skip_destroy の利用
最後にパブリックアクセスブロックの skip_destroy を利用するパターンです。
以下のように skip_destroy = true
とすることで、Terraform管理外となり、
terraform destroy を実行した際にもパブリックアクセスブロックは削除されなくなります。
resource "aws_s3_bucket" "example" {
bucket = "example-bucket"
force_destroy = true
}
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
skip_destroy = true
block_public_acls = false
block_public_policy = false
}
ただし、skip_destroy は AWS Providerv 6.4.0 (2025/07/18) で追加された比較的新しめのパラメータである、という点に注意が必要です。
おそらく大抵の既存プロジェクトはそれより前のバージョンを利用していると思うので、プロバイダーのバージョンアップが必要となりそうです。
ボツ案:オブジェクトの中身を判定して削除防止
最後にボツ案を共有します。
まずはデータリソースの aws_s3_objects
でS3バケットの情報を取得して、
bucket_has_objects = length(data.aws_s3_objects.example.keys) > 0
でS3バケット内のオブジェクト数を取得して、0ならtrue、1以上であればfalseをローカル変数へ設定します。
data "aws_s3_objects" "example" {
bucket = aws_s3_bucket.example.bucket
}
locals {
bucket_has_objects = length(data.aws_s3_objects.example.keys) > 0
}
あとはパブリックブロックアクセスの prevent_destroy でローカル変数を指定することで、
オブジェクト数に基づいて、削除を制御することを目論んでいました。
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
lifecycle {
prevent_destroy = local.bucket_has_objects
}
}
しかし実際に実行すると以下のエラーが出ます。
│ Error: Variables not allowed
│
│ on main.tf line 39, in resource "aws_s3_bucket_public_access_block" "example":
│ 39: prevent_destroy = local.bucket_has_objects
│
│ Variables may not be used here.
╵
╷
│ Error: Unsuitable value type
│
│ on main.tf line 39, in resource "aws_s3_bucket_public_access_block" "example":
│ 39: prevent_destroy = local.bucket_has_objects
│
│ Unsuitable value: value must be known
そうです、残念ながら prevent_destroy では静的な値しか使えないので、
この書き方はできません…。
さいごに
以上、TerraformでS3バケットの削除に失敗した場合、パブリックアクセスブロックだけ削除されることを回避してみました。
ピンポイントすぎる内容なので役立つ場面はほぼないでしょうが、
同じ事象で困ってる方の助けになれば幸いです。