TerraformのS3バックエンドで状態更新時に発生する「AccessDenied: s3:GetObject」エラーの修正方法

intermediate🏗️ Terraform2026-06-02| Terraform 1.x、AWS S3バックエンド、IAMユーザーまたはロール(CI/CDパイプライン:GitHub Actions、GitLab CI、Jenkins)

Error Message

Error refreshing state: AccessDenied: User: arn:aws:iam::123456789:user/ci is not authorized to perform: s3:GetObject on resource
#terraform#aws#s3#iam#backend#state#accessdenied

エラーの内容

Error refreshing state: AccessDenied: User: arn:aws:iam::123456789:user/ci is not authorized to perform: s3:GetObject on resource: arn:aws:s3:::my-terraform-state-bucket/path/to/terraform.tfstate
        status code: 403, request id: XXXXXXXXXXXXXXXX

このエラーは、CI/CDパイプラインで terraform planterraform apply を実行したときに発生することが多いです。Terraformは処理を行う前にS3から現在のステートファイルを取得しようとしますが、ジョブを実行しているIAMユーザーまたはロールにそのファイルを読み取る権限がない場合に起こります。

発生原因

S3バックエンドを設定すると、Terraformは実行のたびにステートファイルを読み書きする必要があります。実行するIAMアイデンティティ(CIユーザー、EC2インスタンスプロファイル、ECSタスクロールなど)が対象バケットおよびパスに対して適切なS3権限を持っていない場合、AWSは403を返し、Terraformがこのエラーとして表示します。

よくある原因:

  • 新しいCI/CDユーザーが作成されたが、ステートバケット用のIAMポリシーがアタッチされていない。
  • TerraformのセットアップAfter、S3バケットポリシーが厳格化された(明示的な拒否や条件が追加されたなど)。
  • バックエンドの設定が、現在のIAMアイデンティティではアクセスできない別のバケットまたはキーパスに移動された。
  • ステートバケットをカバーするインラインポリシーまたはアタッチ済みポリシーを持たないIAMロールを使用している。

修正手順

1. 対象ユーザーとバケットを確認する

エラーメッセージに両方の情報が含まれています。上記の例では:

  • IAMプリンシパル: arn:aws:iam::123456789:user/ci
  • リソース:エラーメッセージ内のS3オブジェクトパス

バックエンドの設定を確認し、正しいバケットとキーを把握しておきましょう:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

2. 必要な権限を持つIAMポリシーを作成する

TerraformのS3バックエンドには、最低限以下のS3アクションが必要です:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TerraformStateAccess",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-terraform-state-bucket",
        "arn:aws:s3:::my-terraform-state-bucket/*"
      ]
    }
  ]
}

ステートロックにDynamoDBを使用している場合(推奨)、以下のステートメントも追加してください:

    {
      "Sid": "TerraformStateLock",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem",
        "dynamodb:DescribeTable"
      ],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:123456789:table/terraform-state-lock"
    }

これを terraform-state-policy.json として保存し、CLIで作成します:

aws iam create-policy \
  --policy-name TerraformStateAccess \
  --policy-document file://terraform-state-policy.json

3. IAMユーザーまたはロールにポリシーをアタッチする

CIユーザー(ci)の場合:

aws iam attach-user-policy \
  --user-name ci \
  --policy-arn arn:aws:iam::123456789:policy/TerraformStateAccess

IAMロール(EC2インスタンスプロファイル、ECSタスクロール、GitHub Actions OIDCロールなど)の場合:

aws iam attach-role-policy \
  --role-name my-ci-role \
  --policy-arn arn:aws:iam::123456789:policy/TerraformStateAccess

4. バケットポリシーやSCPの競合を確認する

適切なIAMポリシーが設定されていても、S3バケットポリシーに明示的な Deny が含まれていたり、AWS OrganizationsのService Control Policy(SCP)が設定されていたりすると、それらが優先される場合があります。バケットポリシーを確認しましょう:

aws s3api get-bucket-policy \
  --bucket my-terraform-state-bucket \
  --query Policy \
  --output text | python3 -m json.tool

ユーザーをブロックしている可能性のある "Effect": "Deny" のステートメントがないか確認してください。見つかった場合は、削除するか、CIプリンシパルの例外を追加してください。

また、バケットがカスタマーマネージドのKMSキーで暗号化されているかどうかも確認してください。その場合、IAMアイデンティティにはそのキーに対する kms:Decryptkms:GenerateDataKey の権限も必要です。

{
  "Sid": "TerraformStateKMS",
  "Effect": "Allow",
  "Action": [
    "kms:Decrypt",
    "kms:GenerateDataKey"
  ],
  "Resource": "arn:aws:kms:ap-northeast-1:123456789:key/YOUR-KEY-ID"
}

修正の確認

Terraformが使用するのと同じ認証情報を使い、AWS CLIで直接S3アクセスをテストします:

# 特定のプロファイルを使用する場合:
aws s3 cp s3://my-terraform-state-bucket/prod/terraform.tfstate /tmp/test.tfstate \
  --profile ci-user

# ロールの引き受けをテストする場合:
aws sts assume-role \
  --role-arn arn:aws:iam::123456789:role/my-ci-role \
  --role-session-name test-session
# 返された認証情報をエクスポートして、s3 cp を再試行する

成功したら、Terraformを実行します:

terraform init
terraform plan

terraform plan の出力が正常に表示されれば(変更がない場合も含め)、ステートが正しく読み込まれたことを確認できます。Error refreshing state のメッセージは表示されなくなります。

補足Tips

  • ポリシーのスコープを絞り込む。 バケット全体にワイルドカードを使うのではなく、キープレフィックスを含む完全なARN(arn:aws:s3:::bucket/path/*)を使用しましょう。特に複数チームが同じバケットを異なるステートパスで共有している場合に重要です。
  • 長期的なユーザーではなくIAMロールを使用する。 GitHub Actionsの場合はOIDCを設定し、EC2/ECSの場合はインスタンスプロファイルを使用しましょう。ci ユーザーの長期アクセスキーはローテーションの手間がかかり、セキュリティリスクにもなります。
  • IAM Access Analyzerを活用する。 実際に必要なアクションが不明な場合は、バケットでAccess Analyzerを有効にすると、実際のアクセスパターンに基づいてポリシーを生成してくれます。
  • ポリシーの反映には数秒かかる。 ポリシーをアタッチしてすぐに再試行しても失敗する場合は、10〜15秒待ってから再試行してください。IAMの変更は結果整合性のため、即時反映されないことがあります。

Related Error Notes