Terraform

Terraform S3 + DynamoDB バックエンド設計: ロックと暗号化を正しく決める

2026-04-19
NicheeLab編集部

Terraform のリモートステートは可用性と整合性が最優先。S3 にステートを置き、DynamoDB でロックを取るのが AWS での定石です。

本稿は S3 側の暗号化設計と DynamoDB 側のロック設計に絞り、試験に出やすい論点と現場での安全運用の両立を狙います。

前提と要件整理

Terraform のステートは機微な値を含む可能性があり、誤消去や競合は致命的です。よって、耐障害性、暗号化、並行実行抑止、監査可能性の担保が必須になります。

S3 はステート保管。DynamoDB はロックの単一真実点。S3 はバージョニングで巻き戻しを可能にし、DynamoDB は排他制御で同時 apply を防ぎます。ネットワーク経路は AWS API への TLS 通信で保護されます。

試験観点では、S3 の暗号化方式選択、KMS キーの権限、バケットバージョニング、DynamoDB ロックテーブルのキー設計、強制アンロックの扱いあたりが出題されやすい領域です。

  • S3 側: バージョニング必須、暗号化は組織要件に合わせ SSE-S3 か SSE-KMS を選択
  • DynamoDB 側: パーティションキー LockID 単一、PAY_PER_REQUEST を推奨、SSE 有効化
  • IAM: 最小権限。S3 は対象プレフィックスの Get/Put/List、DynamoDB は GetItem/PutItem/DeleteItem、KMS は Encrypt/Decrypt/GenerateDataKey/DescribeKey
  • 運用: CloudTrail データイベントやアクセスログで監査、異常時はバージョン復元、強制アンロックは最後の手段

S3 バックエンド設計: キー階層・バージョニング・暗号化

キー設計はワークスペースとの衝突回避を第一に考えます。workspace_key_prefix を使い、env/ワークスペース名/モジュールごとの key で分離するのが読みやすく安全です。S3 は 2020 年以降オブジェクトの強整合性が保証されますが、ロックは依然として必要です。

バージョニングは必須。削除や上書き事故からの復旧が現実的になります。合わせて、ライフサイクルルールで古いバージョンの保持期間を決め、コスト制御を行います。MFA Delete は運用負荷が高いので監査とバージョニングで代替するのが一般的です。

暗号化は二段構えで考えます。Terraform の backend s3 設定 encrypt=true は S3 管理キーによる SSE-S3 を要求します。組織ポリシーで KMS の顧客管理キーを必須にしたい場合は、バケットのデフォルト暗号化で CMK を指定し、バケットポリシーと KMS キーポリシーで SSE-KMS を強制します。backend で特定の KMS キーを直接指定するオプションはありません。

  • key: env/${terraform.workspace}/appname/terraform.tfstate など一意なパスに
  • versioning: 有効化とライフサイクルでコスト最適化
  • encryption: SSE-S3 で簡便運用、SSE-KMS で厳格な鍵管理と監査
  • 監査: CloudTrail データイベントで GetObject/PutObject を記録
観点SSE-S3 (S3 管理キー)SSE-KMS (顧客管理キー)クライアント側暗号化
鍵管理AWS 側。ユーザーは鍵を直接管理しないCMK をユーザーが管理。ポリシーやローテーション可アプリが暗号化。鍵配布が運用課題
実装容易性とても容易。backend の encrypt=true で誘導可S3 バケットのデフォルト暗号化と KMS ポリシー設計が必要実装負荷大。復号の互換性も考慮
監査と制御限定的。KMS の詳細監査は不可KMS で鍵使用ログ、IAM/KMS ポリシーで精緻制御アプリ任せ。クラウド側の監査は限定
コスト追加コストほぼなしKMS リクエストと CMK コストが発生暗号化自体は無料だが運用コスト増
試験の狙い所encrypt=true は SSE-S3 である点S3 デフォルト暗号化と KMS 権限が必要原則非推奨。要件が特殊な場合のみ

DynamoDB ロック設計: テーブルスキーマと運用

Terraform は apply などの書き込み操作の前に DynamoDB にロックを取りにいきます。テーブルはパーティションキー LockID のみを持つ単純スキーマで、LockID に対する条件付き PutItem によってロックを獲得します。失敗時は既存のロックと見なして待機または失敗します。

スループットは PAY_PER_REQUEST を推奨。複数チームや CI ランナーが断続的に実行する負荷に自然対応できます。SSE は有効化を推奨しますが、テーブルには機微な値は原則入りません。万一の障害時は terraform force-unlock を使えますが、同時実行が本当に無いことを確認してからにしてください。

  • 必須のパーティションキー名は LockID(文字列)
  • 権限は ddb:GetItem/PutItem/DeleteItem/DescribeTable 程度で足りる
  • テーブルのリージョンは S3 バケットと同一にして遅延と権限管理を簡素化
  • force-unlock は最後の手段。失敗した apply の残骸が無いことを確認

Terraform ステート更新時のロックと書き込みの流れ

terraform CLI(operator/CI)DynamoDB Table(LockID)S3 Bucket(versioned)1. Acquire lock (PutItem)2. Lock OK3. Write state (PutObject)4. Release lock (DeleteItem)Terraform ステート更新時のロックと書き込みの流れ

IAM・アクセス制御: 最小権限でのロックと暗号化

ステート用の IAM 権限は最小限に絞るのが安全です。S3 は対象バケットの指定プレフィックスに対する GetObject と PutObject、ListBucket(prefix 限定)を許可し、DeleteObject は通常付与しません。SSE-KMS を使う場合は、KMS の Encrypt/Decrypt/GenerateDataKey/DescribeKey をキーの範囲で許可します。

DynamoDB はロックの取得・解放に GetItem/PutItem/DeleteItem と DescribeTable が必要です。テーブルスキャン系の権限は不要です。

backend の設定値は変数参照が効かないため、静的に書くか、-backend-config で外部化します。まずローカルステートでバケットとテーブル等を作成し、その後 terraform init -migrate-state で移行するのが安全な手順です。

  • S3 への DeleteObject は極力付与しない。誤削除をバージョニングでガード
  • KMS はキーのキーポリシーにも当該 IAM ロールを登録。S3 レプリケーション時は別途 KMS Grant が必要
  • backend の encrypt=true は SSE-S3。SSE-KMS はバケットのデフォルト暗号化で強制

S3 バケットと KMS、DynamoDB ロックテーブル、最小権限の一例(HCL、概略)

# 先に local backend で作成し、その後 remote backend に移行すること。

terraform {
  required_version = ">= 1.3"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.0"
    }
  }
  backend "s3" {
    bucket         = "my-tf-state-bucket"
    key            = "env/prod/app1/terraform.tfstate"
    region         = "ap-northeast-1"
    dynamodb_table = "terraform-lock"
    encrypt        = true  # SSE-S3 を要求。SSE-KMS はバケット側で設定する
    # 変数参照不可。変更時は terraform init -migrate-state
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_kms_key" "tf_state" {
  description         = "Terraform state at rest encryption"
  enable_key_rotation = true
}

resource "aws_s3_bucket" "tf_state" {
  bucket = "my-tf-state-bucket"
}

resource "aws_s3_bucket_versioning" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.tf_state.arn
    }
  }
}

resource "aws_s3_bucket_lifecycle_configuration" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  rule {
    id     = "expire-old-versions"
    status = "Enabled"
    noncurrent_version_expiration {
      noncurrent_days = 90
    }
  }
}

resource "aws_dynamodb_table" "tf_lock" {
  name         = "terraform-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"
  attribute {
    name = "LockID"
    type = "S"
  }
  server_side_encryption {
    enabled     = true
  }
}

# ステート操作ロール用 IAM ポリシー例(必要最小限)
data "aws_iam_policy_document" "tf_state_access" {
  statement {
    sid     = "S3StateReadWrite"
    actions = ["s3:GetObject", "s3:PutObject"]
    resources = [
      "${aws_s3_bucket.tf_state.arn}/env/prod/app1/*"
    ]
  }
  statement {
    sid     = "S3List"
    actions = ["s3:ListBucket"]
    resources = [aws_s3_bucket.tf_state.arn]
    condition {
      test     = "StringLike"
      variable = "s3:prefix"
      values   = ["env/prod/app1/*"]
    }
  }
  statement {
    sid     = "DynamoDBLock"
    actions = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem", "dynamodb:DescribeTable"]
    resources = [aws_dynamodb_table.tf_lock.arn]
  }
  statement {
    sid     = "KMSForS3State"
    actions = ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"]
    resources = [aws_kms_key.tf_state.arn]
  }
}

resource "aws_iam_policy" "tf_state_access" {
  name   = "tf-state-access"
  policy = data.aws_iam_policy_document.tf_state_access.json
}

マルチアカウントと DR: レプリケーションと鍵の扱い

複数アカウントから同じステートを操作するのは原則避け、1 アカウントに集約し AssumeRole で越境アクセスするのが安全です。バックエンドは一箇所、作業ロールはそこへアクセスします。

S3 のクロスリージョンレプリケーションは災害対策として有効ですが、ステートは単一の正本が大前提です。レプリカ側での書き込みは禁止し、読み取り復旧手段としてのみ位置付けます。SSE-KMS を使う場合、レプリケーションロールに対する KMS Grant と、宛先側の KMS キー指定を整えます。

DynamoDB グローバルテーブルでロックテーブルを多リージョン化するのは避けてください。ロックの単一性が崩れます。ロックは S3 正本のリージョンに単一配置が原則です。

  • バックエンドは単一アカウント・単一リージョンに正本を置く
  • S3 レプリケーションを使う場合は読み取り専用の復旧用途に限定
  • KMS はリージョンごとに鍵を用意、必要に応じてマルチリージョンキーを検討
  • ロックテーブルは複製しない。強制アンロックの運用手順を準備

運用チェックリストとトラブルシュート

日常運用では、S3 のバージョニングとライフサイクル、KMS キーの有効期限やローテーション、CloudTrail のデータイベントを定期確認します。ワークスペースや key の命名規約は守り、衝突を防ぎます。

ロックが残留して apply が進まない時は、まず実行中セッションが無いことを監視基盤や CI で確認し、DynamoDB のロックアイテムを確認します。必要な場合のみ terraform force-unlock を使います。ステート破損時は S3 の直近バージョンにロールバックし、terraform state pull で健全性を確認します。

権限エラーは KMS のポリシーか S3 バケットポリシーに起因することが多いです。S3 のデフォルト暗号化で CMK を使っている場合、当該 IAM ロールが KMS キーポリシーにも許可されているかを必ず確認してください。

  • S3: Versioning 有効、Lifecycle で古いバージョンを 90 日程度保持
  • KMS: enable_key_rotation と CloudTrail 連携で鍵の使用状況を監査
  • DynamoDB: PAY_PER_REQUEST、CloudWatch で Throttle を監視
  • トラブル時手順: 実行中確認 → DDB のロック確認 → 必要なら force-unlock → S3 の前バージョンへ復元

問題で確認

Associate / Pro

問題 1

組織ポリシーで Terraform ステートを KMS の顧客管理キーで必ず暗号化したい。S3 バックエンドの正しい設計はどれか。

  1. backend の encrypt=true は SSE-S3 なので、S3 バケットのデフォルト暗号化に CMK を設定し、バケットポリシーと KMS ポリシーで SSE-KMS を強制する。
  2. backend に kms_key_id を指定すれば SSE-KMS で暗号化される。
  3. Terraform はリモートステートの暗号化をサポートしないため、アプリ側で暗号化するしかない。
  4. DynamoDB のサーバーサイド暗号化を有効にすれば S3 のステートも KMS で暗号化される。

正解: A

S3 バックエンドの encrypt=true は SSE-S3 を有効化するためのフラグで、特定の KMS キー指定は backend では行えません。SSE-KMS を必須にするには、S3 バケットのデフォルト暗号化で CMK を設定し、あわせてバケットポリシーと KMS キーポリシーで対象ロールのみが鍵を利用できるようにします。

よくある質問

ロック用 DynamoDB テーブルのキー設計は固定ですか?

はい。パーティションキー名が LockID の単一キーである必要があります。ソートキーは不要です。Terraform は LockID を条件付き PutItem で使用し、重複があればロック取得を拒否します。TTL は Terraform のロック制御には使われません。

S3 バックエンドで DynamoDB を指定しないとどうなりますか?

ロックが無効となり、同時実行でステート破損リスクが高まります。小規模や個人用途を除き、実務では dynamodb_table を必ず設定してください。

SSE-KMS 利用時に必要な KMS 権限は?

一般的には kms:Encrypt、kms:Decrypt、kms:GenerateDataKey、kms:DescribeKey が必要です。S3 のデフォルト暗号化で CMK を使う場合でも、呼び出し主体が当該キーを利用できるよう IAM とキーポリシーの両方で許可を与えてください。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
Terraform

Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける

Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...

Terraform

Terraform Authoring & Ops Pro: 上位資格の範囲と対策

上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...

Terraform

Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす

Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...

Terraform

Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義

Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...

Terraform

Terraform Data Source徹底理解:既存リソースの参照で壊さず足す

Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...

Terraformの記事一覧 (102件)
© 2026 NicheeLab All rights reserved.