0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform の state とセキュリティリスクまとめ

Last updated at Posted at 2025-09-26

Terraform の state とは何か

Terraform が管理するインフラの現状を記録したデータベース(JSONファイル)

state の役割

  • 差分計算の基盤
    • Terraform は「HCL に書いた理想」と「state に記録された現状」を比較して差分を出します
      → state がなければ Terraform は「現状」を知らず、毎回すべてを新規作成しようとします
  • 実リソースとの対応付け
    • 論理名 (aws_instance.web) とクラウド上の実リソースID (i-0abc123...) をマッピングします
      → これにより Terraform は「このリソースを更新すべきか」「削除すべきか」を正しく判断できます。
  • 依存関係の解決
    • state 内には依存関係が保存されており、apply 時にリソースをどの順番で作成・変更すべきかを決定できます

stateとセキュリティリスク

セキュリティの注意点

  • Secrets Manager / Parameter Store など「外部の秘匿ストア」を使っても、Terraform が管理するなら state に値は残ります
    • 例:aws_ssm_parametervaluesecurestring にしても、state 内には平文で保存されます(暗号化されない)
  • data ソースで参照した値も state に保存されます。Secrets Manager などから読み取った機密情報もそのまま記録されるので注意が必要です
  • sensitive = true は plan や apply のログ出力を抑制するだけで、state ファイルには必ず保存されます

セキュリティ的に残るパターン

  • ECS / Fargate タスク定義の環境変数
  • Lambda の環境変数
  • Kubernetes Secret / ConfigMap
  • CloudFormation パラメータ

避けるべき書き方と推奨パターン

ECS / Fargate タスク定義の環境変数

NG(値が state に平文で残ります)

variable "db_password" {
  type      = string
  sensitive = true
}

resource "aws_ecs_task_definition" "app" {
  # 中略
  container_definitions = jsonencode([{
    name  = "app"
    image = "myimage"
    environment = [
      { name = "DB_PASSWORD", value = var.db_password } # ← ここは state に平文で残ります
    ]
  }])
}

OK(値は渡さず、Secrets/SSMの参照を渡しません)

resource "aws_ecs_task_definition" "app" {
  container_definitions = jsonencode([{
    name  = "app"
    image = "myimage"
    secrets = [
      { name = "DB_PASSWORD", valueFrom = aws_secretsmanager_secret.db.arn } # 値は残らないです
    ]
  }])
}

Lambda の環境変数

NG(値が state に平文で残ります)

variable "secret_key" {
  type      = string
  sensitive = true
}

resource "aws_lambda_function" "fn" {
  # 中略
  environment {
    variables = {
      SECRET_KEY = var.secret_key  # ← state に平文で残ります
    }
  }
}

OK(値は env に入れません。関数が実行時に取りに行きます)

# Lambda に Secrets Manager 読み取り権限を付与
resource "aws_iam_role_policy_attachment" "attach" {
  role       = aws_iam_role.lambda.name
  policy_arn = aws_iam_policy.lambda_read_secret.arn
}

resource "aws_lambda_function" "fn" {
  environment {
    variables = {
      DB_SECRET_ARN = aws_secretsmanager_secret.db.arn  # ARNのみ
    }
  }
}
# 値は Lambda 実行時に SDK などで取得

Kubernetes プロバイダ経由の Secret / ConfigMap

NG(値が state に平文で残ります)

variable "db_password" {
  type      = string
  sensitive = true
}

# ConfigMap でも同様(機密は本来 Secret を使うべきですが、state には同様に残ります)
resource "kubernetes_config_map" "cfg" {
  metadata { name = "app-config" }
  data = {
    api_key = var.db_password    # ← state に平文で残ります
  }
}

OK(External Secrets Operator 等で“参照”を管理します)

resource "kubernetes_manifest" "db_external_secret" {
  manifest = {
    apiVersion = "external-secrets.io/v1beta1"
    kind       = "ExternalSecret"
    metadata   = { name = "db-secret" }
    spec = {
      secretStoreRef = { name = "aws", kind = "ClusterSecretStore" }
      target         = { name = "db-secret" }
      data = [{
        secretKey = "password"
        remoteRef = { key = "prod/app/db_password" }  # キー名だけ残ります
      }]
    }
  }
}

CloudFormation 経由でのパラメータ渡し

NG(値が state に平文で残ります)

variable "db_password" {
  type      = string
  sensitive = true
}

resource "aws_cloudformation_stack" "app" {
  name         = "app-stack"
  template_body = file("template.yaml")
  parameters = {
    DBPassword = var.db_password  # ← state に平文で残ります
  }
}

OK(テンプレート側を“動的参照”にし、Terraform から値を渡しません)

template.yaml
Parameters:
  DBPassword:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /prod/app/db_password  # 名前だけ残ります
resource "aws_cloudformation_stack" "app" {
  name          = "app-stack"
  template_body = file("template.yaml")
  # parameters で値は渡しません
}

stateに残る、書き込まれる条件

state に残る条件

  • Terraform管理リソース(resource
    • applyimport で Terraform 管理下に入った実体は、論理名と実リソースID・属性が resources 配下に保存されます。モジュール名、タイプ、名前、インスタンス配列などの構造で保持されます
  • Data ソース(data)の参照結果
    • 実体は作りませんが、参照結果は state に mode: "data" として記録されます(キャッシュ用途)
  • Outputs
    • output に定義した値は outputs セクションに保存されます(refresh/-refresh-only でも更新される)
  • count / for_each で展開された個々のインスタンス
    • それぞれが instances 配列の要素として保存され、for_each 利用時は state 構造に対応フィールド(EachMode)が現れます
  • モジュール階層(フルアドレス)
    • module.ecs.aws_... のように、どのモジュール配下のリソースかが module フィールドで保存されます

state に書き込まれる条件

  • terraform apply
    • 望ましい定義(HCL)と state(現状) の差分をもとに作成/更新/削除し、その結果を state に反映します(差分計算の“起点”が state)
  • terraform import
    • 既存リソースを管理下に取り込むと、そのマッピングが state に追加されます
  • terraform refresh / terraform apply -refresh-only
    • コンソール等で外部から手動変更があった場合、実体に合わせて state を更新します(実体は変えない)。output も更新対象です

stateに残らない/ずれる条件

state に残らない条件

  • locals など HCL 内の変数計算結果
    • locals の評価結果は state には保持されないです。毎回再評価されるため、state に追跡されることはないです
  • terraform.tfvars や環境変数から渡した値
    • 変数値そのものは state に保存されないです。ただしリソース属性に使えば、その属性として state に残ります

state がずれる条件

  • Terraform が直接管理しない実体
    • 例:コンソールや CLI で手動作成したリソースは、terraform import をしない限り state には載らないです
  • 外部で手動変更した場合
    • Terraform の外でリソースを変更しても、その瞬間には state は更新されないです。次回 terraform plan で差分として検出され、terraform refresh で state に反映されます
  • ignore_changes を指定した属性
    • lifecycle { ignore_changes = [...] } を設定すると、対象属性が HCL と state で違っても「差分なし」と扱われます
      ただし state には最新値が反映されるので、「HCL の定義どおりに戻す」ことは行われないです。
  • state 操作コマンドを使った場合
    • terraform state rm で state からリソースを削除すると、クラウド上には実体が残りますが state には存在しなくなるです
      逆に terraform state mv を使うと論理名だけが変わり、実体は変わらないです
  • provider の制約で読み取られない属性
    • 一部のプロバイダやリソースは API 仕様の都合で「読み取れない属性」があり、その値は state に反映されないです
      この場合、plan のたびに差分が出続けることがあります

state管理のベストプラクティス

リモートバックエンド(S3 / GCS / Terraform Cloud など)を利用

state をローカルに置いておくと、紛失や同時編集でトラブルになりやすいです。
そのため、S3 や GCS、Terraform Cloud などの リモート保存先を使うのが基本です。
こうしておくと、チームで同じ state を安全に共有でき、誤って上書きする心配も少なくなります。

state ファイルを Git 管理しない

state ファイルには、パスワードなどの秘密情報やクラウドのリソース ID が含まれます。
そのため Git に入れて公開したり、チーム全員がコピーを持ったりするのは危険です。
代わりに、S3 や Terraform Cloud などの リモート保存先に置いて管理します。そこでなら自動で履歴も残り、安全に共有できます。

バージョン管理とチーム運用の注意点

  • ロックの有効化(DynamoDB もしくは use_lockfile)で同時実行を防ぎます
  • 権限の最小化(state バケット/Terraform Cloud への最小権限)と 暗号化・バージョニング を有効にします
  • plan/apply の分離(レビュー→承認→適用) を CI で徹底します

state コマンド活用

  • terraform state list:state に載っている論理リソースを一覧できます
  • terraform state show:特定リソースの state 上の属性を確認できます
  • terraform state mv:論理名やモジュール構成の変更時に 作り直さず マッピングを移せます
  • terraform state rm:実体を消さずに state からだけ除外できます(扱いは慎重にします)

drift(ずれ)を検知する方法

state と現実のインフラはズレ(drift)が必ず発生します。そのまま apply すると意図しない変更や事故につながるため、定期的に drift を検知する仕組みが重要です。

  • terraform plan
    • plan を実行すると、HCL の定義と state、実リソースを比較し、差分があれば表示されます
      → 定期的に plan を CI で流して確認すると、外部変更を早めに気づけます。
  • terraform plan -refresh-only
    • 「リソースの変更はしないで、state を実リソースに合わせる」ための差分チェックです
      → 「コンソールで変えられていないか?」の監査用途に向いています。
  • CI/CD での自動検知
    • GitHub Actions や GitLab CI で terraform plan を定期実行し、差分があれば Slack 通知します

まとめ

Terraform の state は、インフラの現状を記録する不可欠な台帳であり、差分計算や依存解決の要です。
一方で環境変数や Secret を誤って扱うと機密が平文で残るため、Secrets Manager や SSM 参照、リモートバックエンドによる安全な管理を徹底することが重要です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?