Terraformコードを安全にGit管理 — .gitignore/pre-commit/tflint/tfsec実践ガイド

Git/GitHub × Terraform 実践シリーズ(全5弾)


目次

Section 1: はじめに

1-1. 第3弾からの流れ — セキュリティという次の課題

第3弾(ブランチ戦略)では、GitFlow・GitHub Flow・trunk-based development の3戦略を比較し、Terraform IaC 運用に適した戦略選択と、git tag によるモジュールバージョニングを習得しました。これで「チームでのコード管理フロー」は整いました。

しかし、ブランチ戦略を整備しても、次の問題が残ります。

  • AWS アクセスキーを誤って .tfvars に書いてしまい、GitHub に push してしまった
  • terraform.tfstate にリソースのシークレット情報が平文で含まれていた
  • .terraform/ ディレクトリ(プロバイダバイナリ数百MB)がうっかりコミットされた
  • コードの構文エラーや非推奨の書き方が、apply 直前まで気づかれなかった

これらは「セキュリティの欠如」が引き起こす典型的な問題です。本記事では、Terraform コードを安全に Git 管理するためのセキュリティ実装を、ハンズオン形式で一気通貫で習得します。


1-2. 本記事で習得できること

本記事を最後まで読むと、以下のことができるようになります。

ゴール説明
.gitignore 完全設計Terraform 専用 .gitignore を理由とともに設計できる
シークレット管理AWS キー等を安全に扱う3つの手法を選択・実装できる
漏洩スキャンgitleaks で既存リポジトリの漏洩履歴を検出・対処できる
pre-commit 自動化コミット前の自動品質ゲートを構築・運用できる
静的解析tflint / tfsec でコードの問題を push 前に検出できる

本記事の構成は以下の通りです。

Section内容目安時間
Section 1はじめに・前提環境5分
Section 2Terraform向け .gitignore 完全版20分
Section 3シークレット管理3選30分
Section 4gitleaks による漏洩スキャン20分
Section 5pre-commit hooks 自動化25分
Section 6-7tflint / tfsec 静的解析30分

1-3. 前提環境

本記事のハンズオンを実施するには、以下のツールが必要です。

OS: macOS または Linux(コマンド例は bash/zsh)

必須ツールとバージョン確認

# Git
git --version
# git version 2.39.x 以上

# Terraform
terraform --version
# Terraform v1.7.x 以上

# Python(pre-commit 用)
python3 --version
# Python 3.10.x 以上
ツール最低バージョンインストール方法
Git2.39 以上brew install git
Terraform1.7 以上brew tap hashicorp/tap && brew install hashicorp/tap/terraform
Python3.10 以上brew install python
pre-commit3.x 以上pip3 install pre-commit
gitleaks8.x 以上brew install gitleaks
tflint0.50 以上brew install tflint
tfsec1.28 以上brew install tfsec

AWS アカウントは Section 3 のシークレット管理手法の説明のみで参照します。 ハンズオンのほとんどはローカル環境で完結するため、AWS の費用は発生しません。

本記事では新しいディレクトリ terraform-security-demo/ を使用します。第1〜3弾で使用した terraform-git-handson/ とは独立した環境です。


1-4. なぜTerraformのセキュリティは特別か

一般的なアプリケーション開発と比べ、Terraform コードのセキュリティには特有のリスクがあります。

tfstate にシークレットが平文保存される

terraform.tfstate は Terraform が管理するインフラの状態を記録したファイルです。このファイルには、apply 時に設定したパスワード・APIキー・証明書が 平文(JSON形式) で含まれる場合があります。

# 例: RDS パスワードを直接指定した場合
resource "aws_db_instance" "main" {
  identifier  = "mydb"
  engine= "mysql"
  instance_class = "db.t3.micro"
  username = "admin"
  password = "dummy_db_password_here"  # tfstate に平文で記録される
}

apply 後の terraform.tfstate には "password": "dummy_db_password_here" が記録され、このファイルが GitHub に push されると即座に漏洩します。

.terraform/ ディレクトリのモジュールキャッシュ

terraform init を実行すると、.terraform/ ディレクトリにプロバイダバイナリとモジュールのキャッシュが作成されます。

.terraform/
├── providers/
│└── registry.terraform.io/
│ └── hashicorp/
│  └── aws/
│└── 5.x.x/
│ └── linux_amd64/
│  └── terraform-provider-aws_v5.x.x  # 数百MB
└── modules/
 └── vpc/
  └── ...

このディレクトリはリポジトリにコミットすべきでない理由が2つあります。

  1. 容量: プロバイダバイナリは数百MBに達し、Git リポジトリを肥大化させる
  2. 再生成可能: terraform init で常に再生成できるため、コミットする価値がない

.tfvars に書かれたAWSキーがコミットされる事故例

環境別の設定値を .tfvars ファイルに記述することは一般的な運用です。しかし、ローカル開発用に AWS アクセスキーを直接記載し、そのままコミットしてしまう事故が頻発しています。

# terraform.tfvars(本来なら .gitignore で除外すべき)
aws_access_key = "AKIA_DUMMY_ACCESS_KEY_12345"
aws_secret_key = "dummy_secret_key_abcdefghijklmnopqrstuvwxyz"
environment = "dev"

GitHub は公開リポジトリにシークレットが push されると自動検知し、該当する AWS キーを無効化することがあります(Secret Scanning 機能)。しかし、プライベートリポジトリでは検知されず、内部での漏洩リスクが残ります。


Section 2: Terraform向け .gitignore 完全版

2-1. 最低限の .gitignore(初心者向け)

まず、Terraform プロジェクトで 最低限必要な .gitignore から始めましょう。

cat > .gitignore << 'EOF'
# Terraform ディレクトリ(プロバイダ・モジュールキャッシュ)
.terraform/

# ロックファイル(チーム開発ではコミットする場合あり。後述)
.terraform.lock.hcl

# State ファイル(シークレット含む)
*.tfstate
*.tfstate.backup

# 変数ファイル(シークレット含む可能性)
*.tfvars
*.tfvars.json

# ローカルオーバーライドファイル
override.tf
override.tf.json
*_override.tf
*_override.tf.json
EOF

この最小構成でも主要なリスクは防げますが、実際のプロジェクトでは追加の除外が必要です。


2-2. 各エントリの解説(なぜ除外すべきか)

ファイル / ディレクトリ除外理由注意点
.terraform/プロバイダバイナリ・モジュールキャッシュ(容量大・再生成可)terraform init で再生成可能
*.tfstateAWSリソースIDとシークレットが平文で含まれるリモートバックエンド(S3等)を使うこと
*.tfstate.backuptfstate の前バージョン(同様のリスク)
*.tfvars環境別シークレット・設定値が含まれることが多いテンプレート(*.tfvars.example)はコミット可
*.tfvars.jsonJSON 形式の変数ファイル(同様のリスク)
.terraform.lock.hclプロバイダバージョンのロックファイルチームではコミット推奨(後述)
override.tfローカル開発用の上書きファイル(個人設定が混入しやすい)

.terraform.lock.hcl だけは扱いが特殊です(後述の 2-4 で詳しく解説します)。


2-3. 本番推奨 .gitignore 完全版

GitHub 公式の Terraform テンプレートをベースに、セキュリティと運用の観点から拡張した完全版です。各行にコメントで除外理由を記載します。

# =============================================
# Terraform
# =============================================

# プロバイダバイナリ・モジュールキャッシュ
# terraform init で再生成可能。数百MBになる場合あり
.terraform/

# State ファイル群(シークレットが平文で含まれる)
# リモートバックエンド(S3 + DynamoDB 等)を必ず使うこと
*.tfstate
*.tfstate.backup

# 変数ファイル(AWSキー・パスワード等が書かれる可能性)
# テンプレートは *.tfvars.example としてコミットする
*.tfvars
*.tfvars.json

# ローカルオーバーライドファイル(個人設定の混入防止)
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# tfplan バイナリ(terraform plan -out=tfplan で生成)
# バイナリ形式でシークレットが含まれる場合あり
*.tfplan
tfplan

# 生成されたファイル(terraform-docs 等が出力するファイル)
.terraform.tfstate.lock.info

# =============================================
# 認証情報・シークレット(直接配置の防止)
# =============================================

# AWS 認証情報(誤配置防止)
.aws/
credentials
*_credentials

# SSH 秘密鍵
*.pem
*.key
id_rsa
id_ed25519

# 環境変数ファイル
.env
.env.local
.env.*.local

# =============================================
# OS / エディタ固有
# =============================================

# macOS
.DS_Store
.AppleDouble
.LSOverride

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini

# Linux
*~

# JetBrains IDE (IntelliJ, GoLand 等)
.idea/
*.iml

# Visual Studio Code
.vscode/
*.code-workspace

# Vim
*.swp
*.swo

# =============================================
# CI/CD ローカル実行ファイル
# =============================================

# act (GitHub Actions ローカル実行)
.actrc

# =============================================
# ログ・一時ファイル
# =============================================
*.log
*.tmp
*.bak
crash.log
crash.*.log

このテンプレートをプロジェクトルートの .gitignore として使用することを推奨します。


2-4. .terraform.lock.hcl の扱い(チーム vs ソロ)

.terraform.lock.hcl はプロバイダのバージョンをロックするファイルです。npmpackage-lock.json に相当します。

# .terraform.lock.hcl の例
provider "registry.terraform.io/hashicorp/aws" {
  version  = "5.47.0"
  constraints = "~> 5.0"
  hashes = [
 "h1:abcdefghijklmnopqrstuvwxyz0123456789...",
  ]
}

コミットするかどうかは、開発スタイルによって判断します。

開発スタイル推奨理由
チーム開発コミットする全員が同じプロバイダバージョンを使用でき、環境差異による再現不能バグを防げる
ソロ / CI自動生成.gitignore で除外CI パイプラインで terraform init を実行するたびに最新版が使われる
OSS モジュールコミットしない利用者が使うバージョンを強制することになるため

チーム開発では .terraform.lock.hcl を .gitignore から外してコミットすることを強く推奨します。 本記事の完全版 .gitignore ではデフォルトで除外していますが、チーム環境では以下のように対応してください。

# .gitignore から .terraform.lock.hcl の行を削除するか、否定パターンを追加
echo "!.terraform.lock.hcl" >> .gitignore

# ロックファイルをトラッキング対象に追加
git add .terraform.lock.hcl
git commit -m "chore: track terraform lock file for consistent provider versions"

2-5. ハンズオン: .gitignore の動作確認

実際に Terraform プロジェクトを作成し、.gitignore が正しく機能することを確認します。

Step 1: プロジェクトディレクトリを作成

mkdir terraform-security-demo && cd terraform-security-demo
git init
echo "Initialized Git repository: $(pwd)"

Step 2: .gitignore を配置

先ほどの完全版 .gitignore をコピーするか、以下の最小版を使用します。

cat > .gitignore << 'EOF'
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfvars
*.tfvars.json
override.tf
override.tf.json
*_override.tf
*_override.tf.json
*.tfplan
.env
*.pem
*.key
EOF

Step 3: Terraform 設定ファイルを作成

# providers.tf
terraform {
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
  required_version = ">= 1.7"
}

provider "aws" {
  region = var.aws_region
}
cat > providers.tf << 'EOF'
terraform {
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
  required_version = ">= 1.7"
}

provider "aws" {
  region = var.aws_region
}
EOF

cat > variables.tf << 'EOF'
variable "aws_region" {
  description = "AWS region"
  type  = string
  default  = "ap-northeast-1"
}
EOF

Step 4: 除外対象ファイルを作成

# .tfvars(シークレットを含む変数ファイル)
cat > terraform.tfvars << 'EOF'
aws_region = "ap-northeast-1"
# 本来ここにシークレットが書かれることがある
EOF

# ダミーの .env ファイル
cat > .env << 'EOF'
AWS_ACCESS_KEY_ID=AKIA_DUMMY_ACCESS_KEY_12345
AWS_SECRET_ACCESS_KEY=dummy_secret_key_abcdefghijklmnopqrstuvwxyz
EOF

Step 5: terraform init で .terraform/ を生成

terraform init

init 完了後、ディレクトリ構造を確認します。

ls -la
# .terraform/ ディレクトリと .terraform.lock.hcl が生成されている

Step 6: git status で除外状況を確認

git status

期待される出力(除外対象が表示されないこと):

On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
  .gitignore
  providers.tf
  variables.tf

nothing added to commit but untracked files present (use "git add" to track)

.terraform/.terraform.lock.hclterraform.tfvars.envUntracked files に表示されていないことを確認してください。これらは .gitignore によって正しく除外されています。

Step 7: git check-ignore で個別確認

特定のファイルが .gitignore で除外されているか確認できます。

git check-ignore -v terraform.tfvars .env .terraform/

出力例:

.gitignore:5:*.tfvars terraform.tfvars
.gitignore:11:.env .env
.gitignore:1:.terraform/ .terraform/

各ファイルがどの .gitignore ルールによって除外されているかが表示されます。

Step 8: コミットして確認

git add .gitignore providers.tf variables.tf
git commit -m "feat: add terraform project with security-focused .gitignore"
git show --stat HEAD

git show --stat の出力に .terraform/terraform.tfvars.env が含まれていないことを確認します。

Section 2 チェックリスト

  • [ ] .terraform/ が git status に表示されない
  • [ ] *.tfvars が git status に表示されない
  • [ ] .env が git status に表示されない
  • [ ] git check-ignore で各ルールが正しく機能していることを確認した
  • [ ] *.tfvars.example テンプレートファイルを作成してコミットした(推奨)

補足: .tfvars.example テンプレートの運用

シークレットを含む .tfvars は .gitignore で除外しますが、どの変数が必要かを伝えるためのテンプレートファイルはコミットします。

cat > terraform.tfvars.example << 'EOF'
# このファイルをコピーして terraform.tfvars を作成してください
# terraform.tfvars は .gitignore で除外されています(コミットしないこと)

aws_region = "ap-northeast-1"
# db_password = "your_secure_password_here"
# api_key  = "your_api_key_here"
EOF

git add terraform.tfvars.example
git commit -m "docs: add tfvars template for local setup"

このパターンにより「どの変数が必要か」をチームで共有しつつ、実際の値は各自のローカル環境に留めることができます。


Section 3: シークレット管理の基本

Section 1・2 で .gitignore の設計と pre-commit による自動チェックを学びました。しかし「そもそもシークレット(認証情報・パスワード・APIキー)をどこに置くか」というアーキテクチャ設計が誤っていると、フックで止める以前の問題になります。Section 3 では Terraform でシークレットを安全に扱う 3 つのアプローチと、tfstate に潜むシークレット問題を解説します。


3-1. やってはいけない例(アンチパターン)

まず、絶対にやってはいけないパターンを確認します。

# ❌ 絶対にやってはいけない例 — provider.tf
provider "aws" {
  access_key = "AKIAIOSFODNN7EXAMPLE"
  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  region  = "ap-northeast-1"
}

AWS アクセスキーを Terraform コードにハードコードし、git commit した場合、何が起きるのでしょうか。

なぜ危険なのか

① gitログに永久に残る

git commit した時点でオブジェクト(コミット・ツリー・ブロブ)としてリポジトリに刻み込まれます。後からファイルを削除しても、コミット履歴には残り続けます。

# ファイルを削除してコミットしても…
git rm provider.tf
git commit -m "remove credentials"

# git log で過去のコミットを参照すると消えていない
git show abc1234:provider.tf
# → access_key = "AKIAIOSFODNN7EXAMPLE" がまだ見える

② force-push しても消えない

git push --force でコミット履歴を書き換えれば消える」と思うかもしれませんが、これも不完全です。

  • リモートリポジトリ(GitHub/GitLab)の reflog にキャッシュが残る
  • 他のメンバーが git fetch 済みならローカルにコピーが存在する
  • GitHub では削除リクエストを送っても CDN キャッシュにしばらく残る

正しい対処は「キーを即座に無効化し、新しいキーを発行する」ことです。コード上で消すより先にキーを失効させるのが最優先です。

③ パブリックリポジトリでは特に深刻

GitHub にプッシュした瞬間、ボット(悪意あるスクリプト)がスキャンし、数分以内に AWSアカウントが不正利用されることが報告されています。AWS 自身も GitHub をスキャンして危険なキーを検出し、アカウント所有者に通知する仕組みを持っていますが、通知が来るころには手遅れのケースも多くあります。


3-2. アプローチ1: 環境変数を使う(最も簡単)

最もシンプルな方法は、シェルの環境変数に認証情報を設定し、Terraform のコードには一切書かないことです。

環境変数の設定

# ~/.bashrc または ~/.zshrc に追記(bashの場合は ~/.bashrc)
export AWS_ACCESS_KEY_ID="AKIA_DUMMY_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="dummy_secret_access_key_here"
export AWS_DEFAULT_REGION="ap-northeast-1"

設定後、シェルを再起動するか source で反映します。

# 設定を即座に反映
source ~/.zshrc# zsh の場合
source ~/.bashrc  # bash の場合

# 正しく設定されているか確認
echo $AWS_ACCESS_KEY_ID
# → AKIA_DUMMY_ACCESS_KEY(実際のキーが表示される)

Terraform コードには認証情報を書かない

# provider.tf — 認証情報を一切書かない
provider "aws" {
  region = var.aws_region
  # access_key, secret_key は書かない
  # → Terraform が環境変数 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY を自動で読む
}
# variables.tf
variable "aws_region" {
  description = "AWSリージョン"
  type  = string
  default  = "ap-northeast-1"
}

動作確認

# 環境変数から自動で認証情報を読み、planが成功する
terraform plan

# 出力例
# Refreshing Terraform state in-memory prior to plan...
# ...
# Plan: 1 to add, 0 to change, 0 to destroy.

この方法のメリット・注意点

項目内容
メリット設定が簡単・Terraform コードがシンプル
メリット~/.zshrc~/.bashrc は通常 Git 管理外
注意点シェルを終了すると消える(export は永続しない場合あり)
注意点複数 AWS アカウントを切り替える場合は手間
注意点CI 環境ではシークレットマネージャーや IAM ロールを使うべき

複数のAWSプロファイルを管理する場合は、AWS_PROFILE 環境変数と ~/.aws/credentials の組み合わせも有効です。

# ~/.aws/credentials(このファイル自体も Git 管理外に置く)
[default]
aws_access_key_id  = AKIA_DUMMY_DEFAULT_KEY
aws_secret_access_key = dummy_secret_default

[staging]
aws_access_key_id  = AKIA_DUMMY_STAGING_KEY
aws_secret_access_key = dummy_secret_staging

# 環境を切り替える場合
export AWS_PROFILE=staging
terraform plan# staging プロファイルの認証情報を使用

3-3. アプローチ2: terraform.tfvars(.gitignoreで除外必須)

DB パスワードや外部サービスの API キーなど、AWS 認証情報以外のシークレットを Terraform に渡す場合は terraform.tfvars が便利です。ただし .gitignore への登録が必須です。

ディレクトリ構成

terraform-git-handson/
├── main.tf
├── variables.tf
├── provider.tf
├── terraform.tfvars ← ❌ Git 管理外(.gitignore に追加)
├── terraform.tfvars.example  ← ✅ Git 管理対象(テンプレート)
└── .gitignore

.gitignore への追記

# .gitignore(Section 2 で作成したものに追記)
# Terraform secrets
terraform.tfvars
*.tfvars
!terraform.tfvars.example  # exampleファイルだけは追跡対象

! プレフィックスで .tfvars 全体を除外しつつ、terraform.tfvars.example だけ追跡対象に戻しています。

terraform.tfvars(Git 管理外)

# terraform.tfvars — .gitignore に追記して除外する
db_password  = "dummy_db_password_here"
api_secret_key  = "dummy_api_secret_here"
slack_webhook= "https://hooks.slack.com/services/dummy/dummy/dummy"

terraform.tfvars.example(Git 管理対象)

チームメンバーが必要な変数を把握できるよう、値を空(またはダミー値)にしたサンプルを用意します。

# terraform.tfvars.example — 実際の値は書かない。Git管理対象
db_password  = ""
api_secret_key  = ""
slack_webhook= ""

variables.tf — sensitive = true でマスク

# variables.tf
variable "db_password" {
  description = "RDS データベースのパスワード"
  type  = string
  sensitive= true  # terraform plan / apply の出力でマスク表示される
}

variable "api_secret_key" {
  description = "外部APIシークレットキー"
  type  = string
  sensitive= true
}

variable "slack_webhook" {
  description = "Slack Webhook URL"
  type  = string
  sensitive= true
}

sensitive = true を設定すると、terraform plan の出力でその変数の値が (sensitive value) と表示され、ログやターミナルに実際の値が露出しません。

動作確認

# terraform.tfvars が存在すれば自動で読み込まれる
terraform plan

# sensitive = true の変数は出力でマスクされる
# Changes to Outputs:
#+ db_endpoint = (sensitive value)

チームへの引き継ぎ方法

新しいメンバーがリポジトリを git clone した後の手順を README に明示します。

# 1. exampleファイルをコピー
cp terraform.tfvars.example terraform.tfvars

# 2. 実際の値をチームの共有パスワードマネージャーから取得して設定
vim terraform.tfvars

# 3. Gitに追加しないことを確認
git status
# → terraform.tfvars が出力されないことを確認(.gitignore に含まれている)

3-4. アプローチ3: AWS Secrets Manager連携

本番環境やチーム開発では、シークレットをファイルに置くのではなく AWS Secrets Manager に集中管理し、Terraform からデータソースとして参照するのがベストプラクティスです。

Secrets Manager へのシークレット登録(AWSコンソール)

  1. AWSコンソール → AWS Secrets Manager を開く
  2. 「シークレットを保存」 をクリック
  3. シークレットの種類: 「その他のシークレットのタイプ」 を選択
  4. キーと値のペアを入力:
  5. キー: password、値: dummy_db_password_here
  6. シークレット名: prod/myapp/db_password
  7. 自動ローテーション: 今回は無効のまま(本番では有効化を推奨)
  8. 「保存」 をクリック

Secrets Manager からデータソースとして参照

# secrets.tf
data "aws_secretsmanager_secret" "db_password" {
  name = "prod/myapp/db_password"
}

data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = data.aws_secretsmanager_secret.db_password.id
}
# main.tf — RDS インスタンスに Secrets Manager の値を渡す
resource "aws_db_instance" "main" {
  identifier  = "myapp-db"
  engine= "mysql"
  engine_version = "8.0"
  instance_class = "db.t3.micro"
  allocated_storage = 20

  db_name  = "myappdb"
  username = "admin"
  password = data.aws_secretsmanager_secret_version.db_password.secret_string

  skip_final_snapshot = true
}

JSON形式のシークレットの場合

複数の値をまとめて管理する場合は JSON 形式で保存します。

# Secrets Manager に保存されている JSON 例
# {"username": "admin", "password": "dummy_db_password_here", "host": "myapp-db.cluster-xxxx.ap-northeast-1.rds.amazonaws.com"}

data "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = "prod/myapp/db_credentials"
}

locals {
  db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string)
}

resource "aws_db_instance" "main" {
  username = local.db_credentials["username"]
  password = local.db_credentials["password"]
}

既存シークレットの terraform import

AWS コンソールで作成したシークレットを Terraform の管理下に取り込む場合は terraform import を使います。

# シークレットの ARN を確認
aws secretsmanager describe-secret --secret-id prod/myapp/db_password \
  --query 'ARN' --output text
# → arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:prod/myapp/db_password-AbCdEf

# terraform import で管理下に取り込む
terraform import aws_secretsmanager_secret.db_password \
  arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:prod/myapp/db_password-AbCdEf
# Secrets Manager シークレット自体を Terraform で管理する場合
resource "aws_secretsmanager_secret" "db_password" {
  name  = "prod/myapp/db_password"
  description = "RDS データベースのパスワード"

  recovery_window_in_days = 7  # 削除後7日間は復旧可能
}

IAM ポリシー設定(Terraform 実行ロールへの権限付与)

Terraform が Secrets Manager からシークレットを読み取るには、実行ロールに権限が必要です。

# iam.tf — Terraform 実行ロールに Secrets Manager 読み取り権限を付与
data "aws_iam_policy_document" "secrets_read" {
  statement {
 effect = "Allow"
 actions = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
 ]
 resources = [
"arn:aws:secretsmanager:ap-northeast-1:*:secret:prod/myapp/*"
 ]
  }
}

resource "aws_iam_policy" "secrets_read" {
  name= "terraform-secrets-read"
  policy = data.aws_iam_policy_document.secrets_read.json
}

3-5. tfstate のシークレット問題とリモート state

アプローチ1〜3 でコードへのシークレット記載を防いでも、見落とされがちな問題が terraform.tfstate です。

tfstate に何が書かれているのか

terraform apply が完了すると、Terraform は作成・管理したリソースの情報を terraform.tfstate に書き出します。このファイルには適用後のリソースの実際の値が平文で含まれます。

// terraform.tfstate の一部(例)
{
  "resources": [
 {
"type": "aws_db_instance",
"name": "main",
"instances": [
  {
 "attributes": {
"password": "dummy_db_password_here",
"username": "admin",
"endpoint": "myapp-db.xxxx.ap-northeast-1.rds.amazonaws.com"
 }
  }
]
 }
  ]
}

variables.tfsensitive = true を設定していても、tfstate には平文で書き込まれます

なぜ tfstate を Git 管理してはいけないのか

理由説明
シークレットの露出パスワード・APIキーが平文で含まれる
競合リスク複数人が同時に terraform apply すると state が壊れる
ファイルサイズ大規模環境では数MB〜数十MBに達し Git に不向き
変更頻度apply のたびに変更されるため diff が汚れる

.gitignore に必ず追加します(Section 2 で設定済みのはずですが、再確認)。

# .gitignore
*.tfstate
*.tfstate.backup
.terraform/

S3 + DynamoDB リモート state の設定

チーム開発では、tfstate を S3 に保存し DynamoDB で排他制御(ロック)する リモートバックエンドが標準的です。

# backend.tf — S3 リモートバックエンドの設定
terraform {
  backend "s3" {
 bucket= "my-tfstate-bucket"
 key= "prod/terraform.tfstate"
 region= "ap-northeast-1"
 encrypt  = true # S3 SSE-S3 暗号化(保存時暗号化)
 dynamodb_table = "terraform-lock"  # 同時実行防止のロックテーブル
  }
}

リモートバックエンドに必要な S3 バケットと DynamoDB テーブルの作成手順は以下の通りです。

# S3 バケットを作成(バージョニングを有効化)
aws s3api create-bucket \
  --bucket my-tfstate-bucket \
  --region ap-northeast-1 \
  --create-bucket-configuration LocationConstraint=ap-northeast-1

aws s3api put-bucket-versioning \
  --bucket my-tfstate-bucket \
  --versioning-configuration Status=Enabled

# DynamoDB テーブルを作成(LockID がパーティションキー)
aws dynamodb create-table \
  --table-name terraform-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region ap-northeast-1

バックエンドを S3 に切り替えるには terraform init を再実行します。

# リモートバックエンドに移行
terraform init

# 出力例(ローカル state を S3 に移行するか確認される)
# Do you want to copy existing state to the new backend?
# → yes を入力

S3 バケットの追加セキュリティ設定

# s3_tfstate.tf — tfstate バケットのセキュリティ設定
resource "aws_s3_bucket" "tfstate" {
  bucket = "my-tfstate-bucket"
}

# バージョニング(誤った apply からのロールバック用)
resource "aws_s3_bucket_versioning" "tfstate" {
  bucket = aws_s3_bucket.tfstate.id
  versioning_configuration {
 status = "Enabled"
  }
}

# 暗号化(保存時)
resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
  bucket = aws_s3_bucket.tfstate.id
  rule {
 apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
 }
  }
}

# パブリックアクセスのブロック(最重要)
resource "aws_s3_bucket_public_access_block" "tfstate" {
  bucket= aws_s3_bucket.tfstate.id
  block_public_acls = true
  block_public_policy  = true
  ignore_public_acls= true
  restrict_public_buckets = true
}

3-6. 比較表: シークレット管理3手法

3つのアプローチを整理して比較します。

手法設定難易度セキュリティチーム共有CI/CD対応推奨ケース
環境変数★★★△(個人設定)○(CI で secrets 変数に設定)個人・小チーム・AWS認証情報
tfvars(除外)⭐⭐★★△(共有不可)△(手動配布が必要)ローカル開発・DB パスワード
Secrets Manager⭐⭐⭐★★★★★✅(IAM ロールで透過的)本番環境・チーム・自動ローテーション

どれを選ぶべきか

個人開発・学習目的: 環境変数(アプローチ1)から始めるのが最もシンプルです。AWS CLI の aws configure と組み合わせれば認証情報の管理が楽になります。

チーム開発(ステージング以上): Secrets Manager(アプローチ3)を基本とし、AWS 認証情報は IAM ロール(EC2/ECS/Lambda のインスタンスプロファイルや GitHub Actions の OIDC)で解決するのがベストプラクティスです。アクセスキー自体を発行しない設計が理想です。

ローカル開発の DB パスワード等: tfvars(アプローチ2)は手軽ですが、.gitignore への登録を忘れると致命的です。pre-commit フックで detect-secrets または git-secrets を設定し、コミット前に自動検出する仕組みと組み合わせてください(Section 2 参照)。

まとめ: セキュアな Terraform シークレット管理の鉄則

1. コードにシークレットを書かない(ハードコード禁止)
2. tfvars は必ず .gitignore に追加する
3. tfstate は Git 管理しない → S3 リモートバックエンドへ
4. 本番は Secrets Manager + IAM ロール(アクセスキー不要)
5. pre-commit フックで誰かのミスも自動でキャッチする

次の Section 4 では、.gitignore の自動生成・管理ツールと、シークレット検出の自動化ツール群(detect-secretsgitleakstfsec)を組み合わせたワークフローを解説します。


Section 4: シークレット漏洩防止ツール

4-1. なぜスキャンツールが必要か

GitリポジトリにAWSアクセスキーやAPIトークンなどのシークレットが混入するインシデントは後を絶たない。.gitignoreで除外しているつもりのファイルが一度でもコミットされると、そのシークレットはGitの全履歴に刻まれる。git rmでファイルを削除しても履歴には残り続けるため、リモートリポジトリをPublicに変更した瞬間にクローラーに収集されるリスクがある。

代表的な漏洩パターン:

  • .envファイルを誤ってコミット → .gitignoreに追加しても手遅れ
  • terraform.tfvarsにハードコードしたaws_secret_access_key
  • デバッグ用に一時的に貼り付けたAPIキーをそのままpush
  • CI/CDの設定ファイルにベタ書きしたトークン

こうしたミスをコミット前に検出し、既存履歴をスキャンするのがシークレットスキャンツールの役割だ。本セクションでは最も広く使われる gitleaks(推奨)と、AWS特化の git-secrets を解説する。


4-2. gitleaks — インストールと基本スキャン

gitleaksはGo製の単一バイナリで動作するシークレットスキャンツール。100以上のパターンをデフォルトで持ち、AWS・GCP・GitHub・Slackなど主要サービスのシークレットを検出できる。

インストール

macOSの場合はHomebrewが最も簡単:

brew install gitleaks

Linuxでは公式リリースページからバイナリを直接取得する:

# 最新バージョンを確認してURLを差し替える
GITLEAKS_VERSION="8.21.2"
curl -sSfL \
  "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
  | tar -xz
sudo mv gitleaks /usr/local/bin/

インストール確認:

gitleaks version

基本スキャン

カレントディレクトリのリポジトリ全履歴をスキャン:

gitleaks detect --source .

ステージング済みの変更のみスキャン(コミット前フックで使用):

gitleaks detect --source . --staged

特定のブランチやコミット範囲を指定:

# mainブランチの全履歴
gitleaks detect --source . --log-opts="main"

# 直近10コミット分のみ
gitleaks detect --source . --log-opts="-10"

レポートをJSONで出力(CI/CDでの後処理に有用):

gitleaks detect --source . --report-format json --report-path gitleaks-report.json

4-3. gitleaks の出力を読む

漏洩が検出されると、gitleaksは以下のような出力を表示する:

Finding:  AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Secret:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
RuleID:aws-secret-access-key
Entropy:  4.70
File:  terraform/variables.tf
Line:  12
Commit:a3f8c2d1b9e4f076a1234567890abcdef1234567
Author:Jane Doe
Email: jane@example.com
Date:  2024-03-15T10:23:45Z
Fingerprint: a3f8c2d1b9e4f076a1234567890abcdef1234567:terraform/variables.tf:aws-secret-access-key:12

各フィールドの意味:

フィールド説明
Finding検出されたシークレット(一部マスク表示)
RuleIDマッチしたルールの識別子
Entropyエントロピー値(高いほどランダム文字列の可能性が高い)
Fileシークレットが含まれるファイルパス
Commit最初にコミットされたハッシュ
Fingerprint同一漏洩を追跡するための一意ID

検出後の対処フロー:

1. AWSコンソールでキーを即座に無効化(IAM → アクセスキー → 無効化)
2. CloudTrailで過去の不正利用がないか確認(直近7日間)
3. git-filter-repo で履歴からシークレットを完全削除
4. force-push で上書き(チーム全員に周知・ローカルリポジトリの再クローンを依頼)
5. 新しいキーを発行し、各環境(CI/CD含む)の環境変数を更新

git filter-repo による履歴削除

# インストール
pip install git-filter-repo

# 特定文字列をREDACTEDに置換(シークレット値を指定)
git filter-repo --replace-text <(echo "AKIAIOSFODNN7EXAMPLE==>REDACTED_ACCESS_KEY_ID")

# ファイルごと削除(.envファイルを完全に除去)
git filter-repo --path .env --invert-paths

履歴削除後のforce-push:

git push origin --force --all
git push origin --force --tags

4-4. .gitleaks.toml でカスタムルール

プロジェクト固有のシークレット形式(社内システムのAPIキーなど)は .gitleaks.toml でカスタムルールを追加できる。

基本構成

# .gitleaks.toml

# デフォルトルールセットを継承
[extend]
useDefault = true

# カスタムルール: 社内APIキー
[[rules]]
id = "internal-api-key"
description = "Internal API Key for Company Systems"
regex = '''(?i)internal[_-]?api[_-]?key\s*=\s*['"]([a-zA-Z0-9]{32,64})['"]'''
tags  = ["api", "internal"]

# エントロピーフィルタ(ランダム度が低い値は除外)
[rules.entropy]
min = 3.5
max = 8.0

特定パターンを allowlist に追加

テスト用のダミー値や既知の偽陽性を除外する:

# .gitleaks.toml

[[rules]]
id = "aws-access-key-id"
# デフォルトルールを継承しつつ allowlist を追加

[rules.allowlist]
description = "Ignore test fixtures and documentation examples"
regexes  = [
  "AKIAIOSFODNN7EXAMPLE",  # AWSドキュメントのサンプル値
  "AKIAI44QH8DHBEXAMPLE",
]
commits  = [
  "a3f8c2d1b9e4f076a1234567890abcdef1234567",# 既知のテスト用コミット
]

設定ファイルの検証

gitleaks detect --source . --config .gitleaks.toml --verbose

4-5. pre-commit フックとの統合

gitleaksをpre-commitフックに組み込むことで、シークレットを含むコミットをコミット前にブロックできる。

方法1: Gitネイティブフック

# .git/hooks/pre-commit
#!/bin/bash
set -e

echo "Running gitleaks scan..."
gitleaks detect --source . --staged --no-banner
if [ $? -ne 0 ]; then
  echo "ERROR: gitleaks detected secrets. Commit blocked."
  exit 1
fi

実行権限を付与:

chmod +x .git/hooks/pre-commit

方法2: pre-commit フレームワーク

.pre-commit-config.yaml に追記:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
 rev: v8.21.2
 hooks:
- id: gitleaks

インストールと有効化:

pip install pre-commit
pre-commit install

これでチームメンバー全員が pre-commit install を実行するだけで同一のフックが適用される。


4-6. GitHub Actions による CI/CD 統合

ローカルフックを抜けてpushされたケースに備え、CI側でもスキャンを実施する。

公式GitHub Action を使用

# .github/workflows/secret-scan.yml
name: Secret Scan

on:
  push:
 branches: ["**"]
  pull_request:
 branches: [main, develop]

jobs:
  gitleaks:
 runs-on: ubuntu-latest
 steps:
- name: Checkout
  uses: actions/checkout@v4
  with:
 fetch-depth: 0  # 全履歴を取得(shallow clone では履歴スキャン不可)

- name: Run gitleaks
  uses: gitleaks/gitleaks-action@v2
  env:
 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

fetch-depth: 0 が重要。デフォルトの shallow clone(--depth 1)では直近のコミットしか取得されず、過去の履歴に埋まったシークレットを見逃す。

カスタム設定ファイルを使用する場合

- name: Run gitleaks with custom config
  uses: gitleaks/gitleaks-action@v2
  with:
 config-path: .gitleaks.toml
  env:
 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

スキャン結果をSARIFとしてGitHub Security タブに表示:

- name: Run gitleaks (SARIF output)
  uses: gitleaks/gitleaks-action@v2
  with:
 args: "--report-format sarif --report-path gitleaks.sarif"
  env:
 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
 sarif_file: gitleaks.sarif

4-7. git-secrets — AWS特化の代替ツール

git-secretsはAWSが開発したシークレット検出ツールで、AWSクレデンシャルのパターンに特化している。シンプルな用途やAWSシークレットのみ検出したい場合に選択肢となる。

インストール

# macOS
brew install git-secrets

# Linux(手動インストール)
git clone https://github.com/awslabs/git-secrets.git
cd git-secrets
sudo make install

基本セットアップ

# AWSパターンをグローバルに登録
git secrets --register-aws --global

# リポジトリにhookをインストール
cd /path/to/your/repo
git secrets --install

# 既存のリポジトリ全履歴をスキャン
git secrets --scan -r

--register-aws によって以下のパターンが自動登録される:

AWS Access Key ID: AKIA[0-9A-Z]{16}
AWS Secret Access Key: [0-9a-zA-Z/+]{40}

カスタムパターンの追加

# 禁止パターン(検出対象)を追加
git secrets --add 'MY_CUSTOM_SECRET=[A-Za-z0-9]{20}'

# 許可パターン(偽陽性除外)を追加
git secrets --add --allowed 'AKIAIOSFODNN7EXAMPLE'

4-8. gitleaks vs git-secrets 比較

項目gitleaksgit-secrets
対応シークレット汎用(100+パターン)AWS特化
インストールバイナリ1つ / brewbrew / make install
カスタムルール.gitleaks.toml(柔軟)--addコマンド(簡易)
CI/CD統合GitHub Actions公式サポート手動設定が必要
出力形式JSON / SARIF / CSVテキストのみ
履歴スキャンgitleaks detectgit secrets --scan -r
pre-commitフックpre-commitフレームワーク対応git secrets --install
メンテナンス状況活発(2024年も更新)更新頻度が低い
推奨✅ 新規プロジェクト全般AWSのみ・既存環境での追加導入

選択指針:
– 新規プロジェクトや汎用的な検出が必要な場合 → gitleaks
– AWS CloudFormation/CDK主体でAWSシークレットのみ検出すれば十分 → git-secrets
– チームにGitHub Actionsの知識がある → gitleaks(公式Actionが充実)


4-9. 漏洩した場合の緊急対応フロー

シークレットが漏洩したことが発覚した場合、速度が最優先。1時間以内に無効化できれば被害を最小化できる。

Step 1: シークレットを即座に無効化
  → AWSコンソール: IAM → ユーザー → アクセスキー → 無効化
  → 可能であれば「削除」まで実施

Step 2: 不正利用の確認
  → CloudTrail: 過去7日間の該当Access Key IDの操作ログを確認
  → GuardDutyのアラートを確認
  → Cost Explorerで異常なコスト増がないか確認

Step 3: Git履歴から削除
  → git-filter-repo を使用(git filter-branch は非推奨)
  → 対象ファイルまたは文字列を指定して履歴を書き換え

Step 4: force-push
  → チーム全員に作業を周知し、一時的にpushを停止してもらう
  → git push origin --force --all
  → git push origin --force --tags
  → チーム全員がローカルリポジトリを再クローン

Step 5: 新しいシークレットを発行・配布
  → 新しいIAMアクセスキーを発行
  → CI/CD環境変数(GitHub Actions Secrets等)を更新
  → ローカル開発環境の ~/.aws/credentials を更新

Step 6: 再発防止策の実施
  → gitleaks pre-commit フックのインストール
  → GitHub Actions にスキャンワークフローを追加
  → .gitleaks.toml でプロジェクト固有のルールを設定

AWS IAM での緊急無効化

# AWS CLIでアクセスキーを無効化(GUI操作が困難な場合)
aws iam update-access-key \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --status Inactive \
  --user-name target-user

# キーの完全削除
aws iam delete-access-key \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --user-name target-user

CloudTrailで不正利用を確認

# 過去24時間の該当Access Key IDのアクティビティを確認
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
  --start-time $(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ') \
  --query 'Events[*].{Time:EventTime,Event:EventName,Resource:Resources[0].ResourceName}' \
  --output table

4-10. Terraform での Best Practice

Terraformプロジェクトでのシークレット管理はgitleaks導入と合わせて以下を徹底する。

tfvarsファイルのgitignore設定

# .gitignore
*.tfvars
*.tfvars.json
!example.tfvars # サンプルファイルは追跡対象に残す
.terraform/
terraform.tfstate
terraform.tfstate.*

環境変数経由でシークレットを渡す

# シークレットをtfvarsに書かず環境変数で渡す
export TF_VAR_db_password="$(aws secretsmanager get-secret-value \
  --secret-id prod/db/password --query SecretString --output text)"

terraform apply

AWS Secrets Manager / Parameter Store との統合

# secrets.tf
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "prod/rds/master-password"
}

resource "aws_db_instance" "main" {
  # ...
  password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

この構成ではシークレット値がTerraformコードに一切登場しないため、gitleaksのスキャンにも引っかからない。


まとめ: シークレット漏洩は「起きてから対処」では遅い。gitleaksのpre-commitフックとCI/CDスキャンを組み合わせて多層防御を構築し、Terraformではtfvarsへの直書きを避けてSecrets Managerと統合することでリスクを根本から排除できる。

Section 5: pre-commit hooks セットアップ

5-1. pre-commit フレームワークとは

Git には「hooks」と呼ばれる仕組みがある。特定のイベント(コミット・プッシュ等)の前後にシェルスクリプトを自動実行できる機能で、.git/hooks/ ディレクトリに配置する。しかし素の Git hooks にはいくつかの課題がある。

課題内容
個人設定.git/hooks/ はリポジトリ管理外のため、チーム全員に配布できない
言語依存フックをシェルスクリプトで自分で書く必要がある
バージョン管理不可フックのバージョンを固定・更新する仕組みがない

pre-commit フレームワークはこれらを解決する。設定ファイル .pre-commit-config.yaml をリポジトリにコミットすることで、チーム全員が同一のフックを利用できる。フックは外部リポジトリから取得するため、バージョン固定と更新管理も容易だ。

リポジトリ
├── .pre-commit-config.yaml  ← チーム共有の設定ファイル
├── .git/
│└── hooks/
│ └── pre-commit ← pre-commit install で自動生成
└── terraform/
 └── main.tf

pre-commit が担うコミット前の自動チェック項目は多岐にわたる。

  • コードフォーマット: Terraform、Python、JavaScript 等の自動整形
  • 構文検証: YAML 構文エラー、マージコンフリクトマーカー検出
  • セキュリティ: シークレット・認証情報の混入検出(gitleaks)
  • ドキュメント: Terraform モジュールの README 自動更新

5-2. インストール

pre-commit は Python で実装されており、macOS・Linux・Windows に対応している。

# macOS(Homebrew)
brew install pre-commit

# pip(Linux / Windows / 仮想環境)
pip install pre-commit

# pipx(グローバルインストールに推奨)
pipx install pre-commit

# バージョン確認
pre-commit --version
# pre-commit 3.7.1

CI 環境(GitHub Actions など)では、アクション側でインストールが行われるため、手動インストールは不要だ。ローカル開発環境での初期セットアップは以下の手順で完了する。

# 1. リポジトリのルートに移動
cd /path/to/your-terraform-repo

# 2. .pre-commit-config.yaml を作成(次のセクションで詳述)
# 3. フックをインストール(.git/hooks/pre-commit が生成される)
pre-commit install

# インストール確認
cat .git/hooks/pre-commit
# #!/usr/bin/env bash
# # File generated by pre-commit: https://pre-commit.com
# ...

pre-commit install を実行すると、以降 git commit のたびに自動的にフックが走る。

5-3. 基本的な .pre-commit-config.yaml

まず汎用フック + Terraform 専用フック + gitleaks を組み合わせた標準構成を示す。

# .pre-commit-config.yaml
repos:
  # ── 汎用フック ──────────────────────────────────────
  - repo: https://github.com/pre-commit/pre-commit-hooks
 rev: v4.5.0
 hooks:
- id: trailing-whitespace  # 行末空白を削除
- id: end-of-file-fixer # ファイル末尾の改行を統一
- id: check-yaml  # YAML 構文チェック
- id: check-merge-conflict # マージコンフリクトマーカー検出
- id: detect-private-key# 秘密鍵の混入チェック

  # ── Terraform 専用フック ────────────────────────────
  - repo: https://github.com/antonbabenko/pre-commit-terraform
 rev: v1.88.0
 hooks:
- id: terraform_fmt  # terraform fmt 自動実行
- id: terraform_validate# terraform validate 実行
- id: terraform_docs # README.md を自動更新
  args:
 - --args=--output-file README.md

  # ── シークレット検出 ────────────────────────────────
  - repo: https://github.com/gitleaks/gitleaks
 rev: v8.18.0
 hooks:
- id: gitleaks

重要: rev はコミットハッシュではなくタグで固定する。latestmain は禁止だ。pre-commit autoupdate でまとめて更新できる。

5-4. フックの初回実行

インストール後、全ファイルに対して手動実行し、現状のリポジトリがフックをパスするか確認する。

# 全ファイルに対して実行(初回チェック)
pre-commit run --all-files

成功時の出力例:

[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Initializing environment for https://github.com/antonbabenko/pre-commit-terraform.
[INFO] Initializing environment for https://github.com/gitleaks/gitleaks.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
trailing-whitespace..................................................Passed
end-of-file-fixer....................................................Passed
check-yaml...........................................................Passed
check-merge-conflict.................................................Passed
detect-private-key...................................................Passed
terraform_fmt........................................................Passed
terraform_validate...................................................Passed
terraform_docs.......................................................Passed
gitleaks.............................................................Passed

特定フックのみ実行したい場合:

# terraform_fmt だけ実行
pre-commit run terraform_fmt --all-files

# gitleaks だけ実行
pre-commit run gitleaks --all-files

5-5. terraform_fmt フックの動作確認

意図的にフォーマットが崩れた Terraform ファイルを作成し、コミット時の動作を確認する。

# フォーマットが崩れた main.tf を作成
cat > main.tf << 'EOF'
resource "aws_s3_bucket" "example" {
bucket = "my-test-bucket"
  tags = {
Name = "example"
  }
}
EOF

# コミット試行
git add main.tf
git commit -m "test: フォーマット崩れのテスト"

terraform_fmt フックが失敗し、自動修正が行われる。

terraform_fmt........................................................Failed
- hook id: terraform_fmt
- files were modified by this hook

main.tf

フックは自動修正後にステージングを要求するため、修正済みファイルを再度ステージングしてコミットする。

# 自動修正されたファイルを確認
git diff main.tf

# 再ステージング → コミット成功
git add main.tf
git commit -m "test: フォーマット修正済みのテスト"
terraform_fmt........................................................Passed
terraform_validate...................................................Passed
terraform_docs.......................................................Passed
gitleaks.............................................................Passed
[main 1a2b3c4] test: フォーマット修正済みのテスト
 1 file changed, 3 insertions(+), 3 deletions(-)

5-6. gitleaks フックでシークレット検出

gitleaks は AWS 認証情報・API キー・パスワード等のシークレットパターンをコミット前に検出する。

# ダミーのシークレットを含むファイルを作成
echo 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' >> test.env

git add test.env
git commit -m "test: シークレット混入のテスト"

gitleaks がシークレットを検出してコミットをブロックする。

gitleaks.............................................................Failed
- hook id: gitleaks
- exit code: 1

 ○
 │╲
 │ ○
 ○ ░
 ░ gitleaks

Finding:  AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Secret:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
RuleID:aws-secret-access-key
Entropy:  4.234
File:  test.env
Line:  1
Fingerprint: test.env:aws-secret-access-key:1

対処方法: シークレットを環境変数や AWS Secrets Manager に移し、ファイルからは削除する。

# test.env からシークレットを削除
rm test.env

# .gitignore に追加して誤コミットを防ぐ
echo "*.env" >> .gitignore
git add .gitignore
git commit -m "chore: .env ファイルを .gitignore に追加"

テスト・ダミー値を意図的に含める場合

テストコードやドキュメント中にダミー値が必要な場合は .gitleaksignore で除外できる。

# .gitleaksignore を作成
cat > .gitleaksignore << 'EOF'
# テスト用ダミー値のフィンガープリントを除外
test/fixtures/dummy_secrets.tf:aws-secret-access-key:5
docs/examples/terraform.tfvars:aws-access-token:12
EOF

フィンガープリントは gitleaks の出力に含まれる Fingerprint: の値をそのまま使用する。

5-7. terraform_docs フックで README を自動更新

terraform_docs フックを利用すると、Terraform モジュールの変数・出力・プロバイダー情報を README.md に自動反映できる。

まず terraform-docs ツールのインストールが必要だ。

# macOS
brew install terraform-docs

# バージョン確認
terraform-docs --version
# terraform-docs version v0.17.0

モジュールディレクトリに README.md のマーカーを追加する。

<!-- terraform-docs の出力先マーカー(README.md に追記) -->
<!-- BEGIN_TF_DOCS -->
<!-- END_TF_DOCS -->

コミット時に terraform_docs フックが <!-- BEGIN_TF_DOCS --><!-- END_TF_DOCS --> の間を自動更新する。

# variables.tf を変更してコミット試行
git add terraform/modules/s3/variables.tf
git commit -m "feat: S3 モジュールに新しい変数を追加"
terraform_docs.......................................................Failed
- hook id: terraform_docs
- files were modified by this hook

terraform/modules/s3/README.md

README.md が自動更新されるため、再ステージングして再コミットする。

git add terraform/modules/s3/README.md
git commit -m "feat: S3 モジュールに新しい変数を追加"
# terraform_docs.......................................................Passed

5-8. pre-commit の CI/CD 統合(GitHub Actions)

ローカルでの pre-commit はあくまで開発者の補助だ。コードレビューや CI 段階でも同一チェックを走らせることで、フックのスキップや未インストールを防ぐ。

# .github/workflows/pre-commit.yml
name: pre-commit

on:
  pull_request:
  push:
 branches: [main]

jobs:
  pre-commit:
 runs-on: ubuntu-latest
 steps:
- uses: actions/checkout@v4
  with:
 fetch-depth: 0  # gitleaks の全履歴スキャンに必要

- uses: actions/setup-python@v5
  with:
 python-version: "3.11"

- uses: pre-commit/action@v3.0.1

pre-commit/action.pre-commit-config.yaml を自動検出し、変更ファイルのみにフックを適用する。PR のたびに差分ファイルだけチェックするため実行時間が短い。

gitleaks でリポジトリ全履歴をスキャンしたい場合は fetch-depth: 0 が必要だ。デフォルトの shallow clone では過去のコミットが含まれず、スキャン範囲が限定される。

5-9. フックの一時スキップと運用ルール

緊急デプロイ等でフックをスキップする必要がある場合は SKIP 環境変数を使う。--no-verify は全フックをスキップするため、特定フックのみの SKIP が推奨だ。

# 特定フックのみスキップ(カンマ区切りで複数指定可)
SKIP=terraform_docs git commit -m "docs: ドキュメント更新は後回し"

# 複数フックをスキップ
SKIP=terraform_docs,terraform_validate git commit -m "wip: 一時的なスキップ"

--no-verify の使用について: セキュリティフック(gitleaks 等)もすべてスキップされるため、チームポリシーとして禁止することを強く推奨する。CI でも同じフックが走るため、--no-verify はローカルでのみ一時回避できるにすぎず、最終的には CI で検出される。

フックのバージョン管理

# 全フックのバージョンを最新に更新
pre-commit autoupdate

# 更新後の .pre-commit-config.yaml の差分確認
git diff .pre-commit-config.yaml

# キャッシュクリア(フック動作がおかしい場合)
pre-commit clean

pre-commit autoupdate は各リポジトリの最新タグに rev を更新する。月次での実行を推奨する(CI での自動 PR 作成も有効だ)。

チーム向け運用ルール

ルール内容
--no-verify 禁止CI でも同じチェックが走るため意味がない。シークレット混入リスクを高める
rev はタグ固定latest / main は禁止。再現性確保のため必ずバージョンタグを指定
月次 autoupdateセキュリティフックの定義更新を逃さないため月1回 autoupdate を実行
初回 --all-files新規参加者が pre-commit install 後に --all-files で全チェックを確認

5-10. まとめ

本セクションでは pre-commit フレームワークを使ったコミット前自動検証の仕組みを構築した。

フック目的特記事項
trailing-whitespace行末空白削除自動修正あり
end-of-file-fixerファイル末尾改行統一自動修正あり
check-yamlYAML 構文検証エラー時はコミットブロック
check-merge-conflictマージコンフリクトマーカー検出エラー時はコミットブロック
terraform_fmtTerraform 自動フォーマット自動修正後に再ステージング必要
terraform_validateTerraform 構文・整合性検証init 済み環境が必要
terraform_docsREADME 自動生成terraform-docs インストール必要
gitleaksシークレット漏洩検出.gitleaksignore で除外可能

pre-commit によるローカルゲートと GitHub Actions による CI ゲートの二重構造により、問題をコミット段階で早期検出できる。次のセクションでは、GitHub の branch protection rules と組み合わせてマージ前のセキュリティチェックを必須化する方法を解説する。

Section 6: tflint セットアップ・基本ルール・実行例

6-1. tflint とは

terraform validate はHCL構文エラーを検出しますが、クラウド固有の問題——存在しないインスタンスタイプの指定、非推奨の引数名、必須タグの欠落——は検出できません。tflint はこのギャップを埋める静的解析ツール(linter)です。

ツール役割検出例
terraform validateHCL構文・型チェックami の型ミス
tflintクラウド固有ルール存在しない instance_type
tfsecセキュリティルールS3パブリックアクセス未制限

tflint はプラグイン機構を持ち、AWSルールセット(tflint-ruleset-aws)を追加することでAWS固有の検証が可能になります。


6-2. tflint のインストール

macOS

brew install tflint

# バージョン確認
tflint --version
# TFLint version 0.50.3

Linux(公式インストールスクリプト)

curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash

tflint --version

バイナリ直接ダウンロード(バージョン固定したい場合)

TFLINT_VERSION="0.50.3"
curl -sLo tflint.zip \
  "https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/tflint_linux_amd64.zip"
unzip tflint.zip -d /usr/local/bin/
chmod +x /usr/local/bin/tflint
tflint --version

6-3. .tflint.hcl 設定ファイル

tflint の動作はリポジトリルートに置く .tflint.hcl で制御します。プロジェクトルートで以下のファイルを作成してください。

# .tflint.hcl
plugin "aws" {
  enabled = true
  version = "0.29.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}

rule "terraform_required_version" {
  enabled = true
}

rule "terraform_required_providers" {
  enabled = true
}

rule "terraform_naming_convention" {
  enabled = true

  resource {
 format = "snake_case"
  }

  variable {
 format = "snake_case"
  }
}

rule "aws_instance_invalid_type" {
  enabled = true
}

rule "aws_s3_bucket_name" {
  enabled = false  # バケット名のカスタム規則がある場合は無効化
}

主要ルールの説明

ルール名内容
terraform_required_versionterraform {} ブロックの required_version 記述を必須化
terraform_required_providersrequired_providers ブロックの記述を必須化
terraform_naming_conventionリソース名・変数名の命名規則(スネークケース等)を強制
aws_instance_invalid_type存在しないEC2インスタンスタイプを検出

6-4. プラグインの初期化と実行

設定ファイルを作成したら、まずプラグインをダウンロードします。

# AWSプラグインをダウンロード(初回のみ)
tflint --init
# Initializing plugins...
# - aws (0.29.0) installed

# カレントディレクトリをスキャン
tflint

# サブディレクトリも含めてスキャン(modules/ 配下も検証)
tflint --recursive

# 出力形式を JSON にする(CI/CDでの機械処理向け)
tflint --format json

# 特定ファイルのみスキャン
tflint main.tf

.tflint.hcl がない場合はデフォルトルールのみで動作しますが、AWSプラグインを活用するために設定ファイルの作成を推奨します。


6-5. tflint の出力を読む(違反例と修正)

違反例1: 存在しないEC2インスタンスタイプ

以下のコードを意図的に誤ったインスタンスタイプで作成します。

# main.tf(意図的な誤り)
resource "aws_instance" "web" {
  ami  = "ami-0c55b159cbfafe1f0"
  instance_type = "t9.micro"  # 存在しないタイプ
}

tflint を実行すると次のエラーが出力されます。

tflint
# 1 issue(s) found:
#
# Error: "t9.micro" is an invalid value as instance_type (aws_instance_invalid_type)
#
#on main.tf line 3:
# 3:instance_type = "t9.micro"
#
# Reference: https://github.com/terraform-linters/tflint-ruleset-aws/blob/v0.29.0/rules/aws_instance_invalid_type.md

修正します。

# main.tf(修正後)
resource "aws_instance" "web" {
  ami  = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"  # 正しいタイプ
}

違反例2: required_version の未記載

# terraform.tf(required_version なし)
terraform {
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}
tflint
# 1 issue(s) found:
#
# Warning: terraform "required_version" attribute is required (terraform_required_version)
#
#on terraform.tf line 1:
# 1: terraform {

required_version を追記して解消します。

# terraform.tf(修正後)
terraform {
  required_version = ">= 1.6.0"

  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

6-6. tflint を pre-commit に組み込む

Section 5 で設定した .pre-commit-config.yaml に tflint フックを追加します。

# .pre-commit-config.yaml(tflint 追記分)
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
 rev: v1.88.0
 hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
  args:
 - --args=--init # プラグインを自動DL
 - --args=--recursive  # サブディレクトリも対象

antonbabenko/pre-commit-terraform は tflint を含むTerraform向け pre-commit フックのコレクションです。別途 terraform-linters/tflint リポジトリのフックを使う方法もありますが、Terraformツール群をまとめて管理できる前者が実用的です。

フックの動作確認:

# 全ファイルに対して手動実行
pre-commit run terraform_tflint --all-files
# tflint.......................Passed

6-7. GitHub Actions での tflint 実行

PR時とmainブランチへのプッシュ時に自動でlintを実行するワークフローを作成します。

# .github/workflows/tflint.yml
name: tflint

on:
  pull_request:
  push:
 branches:
- main

jobs:
  tflint:
 runs-on: ubuntu-latest
 permissions:
contents: read

 steps:
- uses: actions/checkout@v4

- name: Setup TFLint
  uses: terraform-linters/setup-tflint@v4
  with:
 tflint_version: v0.50.3

- name: Show version
  run: tflint --version

- name: Init TFLint
  run: tflint --init
  env:
 GITHUB_TOKEN: ${{ github.token }}  # プラグインDL時のrate limit回避

- name: Run TFLint
  run: tflint --recursive --format compact

GITHUB_TOKEN をセットすることで、GitHubからプラグインをダウンロードする際のAPI rate limit(未認証: 60req/h)を回避できます。

ワークフロー追加後の動作確認

git add .github/workflows/tflint.yml .tflint.hcl
git commit -m "ci: add tflint workflow"
git push origin feature/add-security-checks

PRを開くと tflint チェックがCI一覧に表示され、違反があればマージがブロックされます。


6-8. tflint をローカルで日常的に使う

CI だけでなく、コーディング中にも随時実行する習慣をつけると手戻りが減ります。

# plan前の標準チェックフロー
terraform fmt -recursive
tflint --recursive
terraform validate
terraform plan

エイリアスを設定しておくと便利です。

# ~/.zshrc または ~/.bashrc
alias tfcheck='terraform fmt -recursive && tflint --recursive && terraform validate'

これで tfcheck と入力するだけで fmt・lint・validate を一括実行できます。


Section 7: tfsec セットアップ・基本スキャン・実行例

7-1. tfsec とは

tfsec は Terraform コードに特化したセキュリティスキャンツールです。CIS Benchmark や AWS Security Hub のベストプラクティスに基づくルールセットを内蔵しており、設定ミスによるセキュリティリスクをコードレビュー前に検出できます。

tflint との違い

観点tflinttfsec
主な用途構文・API整合性セキュリティポリシー
検出例無効なインスタンスタイプS3パブリックアクセス
ルール根拠AWS APIドキュメントCIS Benchmark
補完関係lint → security の順で両方実行

検出できる問題の例

  • S3バケットのパブリックアクセス設定漏れ
  • セキュリティグループの 0.0.0.0/0 全開放
  • RDSインスタンスの暗号化未設定
  • CloudTrailのログ暗号化未設定
  • IAMポリシーのワイルドカード * 過剰付与

7-2. tfsec のインストール

macOS

brew install tfsec

tfsec --version
# v1.28.9

Linux(インストールスクリプト)

curl -sSfL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install.sh | bash

tfsec --version

バイナリ直接ダウンロード

TFSEC_VERSION="1.28.9"
curl -sLo tfsec \
  "https://github.com/aquasecurity/tfsec/releases/download/v${TFSEC_VERSION}/tfsec-linux-amd64"
chmod +x tfsec
sudo mv tfsec /usr/local/bin/
tfsec --version

7-3. 基本スキャン実行

# カレントディレクトリをスキャン
tfsec .

# JSON形式で出力(CI連携・差分確認向け)
tfsec . --format json

# SARIF形式(GitHub Code Scanning向け)
tfsec . --format sarif

# 特定の重大度以上のみ報告
tfsec . --minimum-severity HIGH

# 特定ディレクトリをスキャン
tfsec ./environments/prod

重大度は CRITICAL > HIGH > MEDIUM > LOW の4段階です。--minimum-severity HIGH を指定するとMEDIUM/LOWの警告を抑制できます。


7-4. tfsec の出力を読む(検出例)

検出例1: S3バケットのセキュリティ設定漏れ

まず問題のある設定を用意します。

# s3_insecure.tf(意図的なセキュリティ問題)
resource "aws_s3_bucket" "example" {
  bucket = "my-insecure-bucket"
}
# 暗号化・パブリックアクセスブロック設定なし

スキャン結果:

tfsec .
# Result #1 HIGH Bucket does not have encryption enabled
# ────────────────────────────────────────────────────────────────────
#ID aws-s3-enable-bucket-encryption
#  Impact The bucket objects could be read if compromised
# ────────────────────────────────────────────────────────────────────
#Resource: aws_s3_bucket.example
#  File:s3_insecure.tf:1-3
# ────────────────────────────────────────────────────────────────────
#
# Result #2 HIGH Bucket does not have public access block enabled
# ────────────────────────────────────────────────────────────────────
#ID aws-s3-block-public-acls
# ...

修正後のコード:

# s3_secure.tf(修正後)
resource "aws_s3_bucket" "example" {
  bucket = "my-secure-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
  bucket = aws_s3_bucket.example.id

  rule {
 apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
 }
  }
}

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
}

検出例2: セキュリティグループの全開放

# sg_insecure.tf(意図的な問題)
resource "aws_security_group_rule" "http" {
  type  = "ingress"
  from_port= 80
  to_port  = 80
  protocol = "tcp"
  cidr_blocks = ["0.0.0.0/0"]

  security_group_id = aws_security_group.web.id
}
tfsec .
# Result #1 MEDIUM Security group rule allows ingress from public internet.
#ID aws-ec2-no-public-ingress-sgr

ALBからのトラフィックのみ許可する場合は cidr_blocks を削除し、代わりにALBのセキュリティグループを source_security_group_id で指定します。

# sg_secure.tf(修正後)
resource "aws_security_group_rule" "http" {
  type= "ingress"
  from_port = 80
  to_port= 80
  protocol  = "tcp"
  source_security_group_id = aws_security_group.alb.id  # ALBのSGのみ許可

  security_group_id = aws_security_group.web.id
}

7-5. .tfsec/config.yml でルール除外

プロジェクトの要件上、特定のルールを適用しない場合は設定ファイルで除外します。

mkdir -p .tfsec
# .tfsec/config.yml
exclude:
  - aws-s3-enable-bucket-logging # ログ不要なバケットに適用しない

severity_overrides:
  aws-s3-block-public-acls: CRITICAL  # デフォルトより厳しく設定

minimum_severity: LOW# 全件報告(デフォルト)

特定のリソースだけ個別に除外したい場合は、Terraformコード内にコメントで #tfsec:ignore を記述します。

resource "aws_s3_bucket" "public_assets" {
  bucket = "my-public-static-assets"
  #tfsec:ignore:aws-s3-block-public-acls
  #tfsec:ignore:aws-s3-block-public-policy
}

コメント方式はリソース単位の局所的な除外に使い、プロジェクト全体の除外は config.yml で管理するのがベストプラクティスです。


7-6. tfsec を pre-commit に組み込む

# .pre-commit-config.yaml(tfsec 追記分)
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
 rev: v1.88.0
 hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
  args:
 - --args=--init
 - --args=--recursive
- id: terraform_tfsec
  args:
 - --args=--minimum-severity=HIGH  # HIGH以上のみでブロック

動作確認:

pre-commit run terraform_tfsec --all-files
# terraform_tfsec..............Passed

HIGH以上の問題があればコミットがブロックされ、修正を促します。


7-7. GitHub Actions での tfsec 実行

tfsec の公式 GitHub Action を使ったワークフローです。

# .github/workflows/tfsec.yml
name: tfsec

on:
  pull_request:
  push:
 branches:
- main

jobs:
  tfsec:
 runs-on: ubuntu-latest
 permissions:
contents: read
security-events: write# SARIF アップロードに必要

 steps:
- uses: actions/checkout@v4

- name: Run tfsec
  uses: aquasecurity/tfsec-action@v1.0.3
  with:
 minimum_severity: HIGH
 format: sarif
 sarif_file: tfsec.sarif

- name: Upload SARIF file
  uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
 sarif_file: tfsec.sarif

SARIF形式でアップロードするメリット

SARIF(Static Analysis Results Interchange Format)でアップロードすると、GitHubの「Security」タブ→「Code scanning」に結果が集約されます。PRの差分ビューに検出箇所がインライン表示されるため、レビュアーが修正箇所を一目で把握できます。

permissions: security-events: write は SARIF アップロードに必須です。忘れずに設定してください。


7-8. tflint と tfsec を組み合わせた統合ワークフロー

2つのツールを1つのワークフローにまとめると管理が楽になります。

# .github/workflows/terraform-checks.yml
name: Terraform Checks

on:
  pull_request:
  push:
 branches:
- main

jobs:
  lint:
 name: tflint
 runs-on: ubuntu-latest
 permissions:
contents: read

 steps:
- uses: actions/checkout@v4

- uses: terraform-linters/setup-tflint@v4
  with:
 tflint_version: v0.50.3

- name: Init TFLint
  run: tflint --init
  env:
 GITHUB_TOKEN: ${{ github.token }}

- name: Run TFLint
  run: tflint --recursive --format compact

  security:
 name: tfsec
 runs-on: ubuntu-latest
 permissions:
contents: read
security-events: write

 steps:
- uses: actions/checkout@v4

- name: Run tfsec
  uses: aquasecurity/tfsec-action@v1.0.3
  with:
 minimum_severity: HIGH
 format: sarif
 sarif_file: tfsec.sarif

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
 sarif_file: tfsec.sarif

lint ジョブと security ジョブは独立して並列実行されるため、フィードバックが早く返ってきます。両ジョブをPRのブランチ保護ルール(Required status checks)に追加することで、どちらかが失敗した場合もマージをブロックできます。


7-9. ローカルでの統合チェックフロー

# Terraform 品質チェック一式
terraform fmt -recursive# フォーマット
tflint --recursive# 構文・API整合性
tfsec . --minimum-severity HIGH  # セキュリティ
terraform validate# 設定検証
terraform plan # 差分確認

エイリアスで一括実行:

# ~/.zshrc
alias tfqa='terraform fmt -recursive && tflint --recursive && tfsec . --minimum-severity HIGH && terraform validate'

tfqa(Terraform Quality Assurance)を習慣化することで、CI で初めてエラーを検知するのではなく、ローカル開発段階で問題を潰せます。


7-10. まとめ:tflint + tfsec の位置づけ

本セクションで設定したツールチェーンをまとめます。

フェーズツールタイミング
コーディングterraform fmtファイル保存時(エディタ連携)
コミット前tflint + tfsecpre-commit フック
PR作成時tflint + tfsecGitHub Actions
マージ条件両ジョブ greenブランチ保護ルール

次のSection 8では、これらのツールを組み合わせたPRレビュープロセスの全体像と、チームでの運用ルール策定について解説します。

Section 8: 統合フロー + まとめ + 次弾予告

8-1. ローカル開発フローの全体図

本記事で構築したセキュリティチェックが、実際の開発フローにどう組み込まれるかを整理します。

git commit 実行
  ↓
[pre-commit hooks — ローカル自動検証]
  ├─ trailing-whitespace: ✅ 末尾空白を検出・除去
  ├─ end-of-file-fixer:✅ 末尾改行を統一
  ├─ check-merge-conflict: ✅ コンフリクトマーカー検出
  ├─ terraform_fmt:  ✅ HCLフォーマット自動修正
  ├─ terraform_validate:✅ 構文・型チェック
  └─ gitleaks: ✅ シークレット漏洩検出なし
  ↓
[コミット成功]
  ↓
git push → GitHub PR作成
  ↓
[GitHub Actions CI — リモート自動検証]
  ├─ pre-commit(全フック再実行): ✅
  ├─ tflint(ベストプラクティス): ✅
  └─ tfsec(セキュリティスキャン): ✅
  ↓
[PR マージ可能 — レビュー後マージ]

ローカルで pre-commit が通らないとコミット自体が失敗するため、問題のあるコードがリポジトリに入ることを防止できます。CI では同じチェックを再実行することで、ローカル環境の差異に起因する問題も検出できます。

8-2. セキュリティチェックリスト(本記事の総括)

本記事で構築したセキュリティ対策を一覧にまとめます。プロジェクト開始時や定期レビューの際にチェックリストとして活用してください。

【Git管理の基盤】
□ .gitignore — .terraform/ / *.tfstate / *.tfstate.backup を除外
□ .gitignore — *.tfvars / override.tf / crash.log を除外
□ .gitignore — .env / *.pem / *.key / credentials* を除外

【シークレット管理】
□ ハードコード禁止 — AWS_ACCESS_KEY_ID 等を直書きしていない
□ 環境変数 or AWS Secrets Manager で外部化済み
□ terraform.tfvars に機密値がある場合は .gitignore 対象に追加
□ locals {} でのシークレット合成禁止(terraform show で可視)

【漏洩防止スキャン】
□ gitleaks — インストール済み(brew install gitleaks)
□ gitleaks — 全コミット履歴スキャン済み(gitleaks detect --source .)
□ 既存漏洩があれば git-filter-repo で履歴書き換え済み

【pre-commit hooks】
□ pre-commit — インストール済み(pip install pre-commit)
□ .pre-commit-config.yaml — 設定済み
□ pre-commit install — フック有効化済み
□ pre-commit run --all-files — 全ファイルへの適用確認済み

【静的解析】
□ tflint — インストール済み(brew install tflint)
□ .tflint.hcl — 設定ファイル作成済み(aws プラグイン有効化)
□ tflint --init — プラグインインストール済み
□ tflint — 重要度 WARNING 以上の問題なし

【セキュリティスキャン】
□ tfsec — インストール済み(brew install tfsec)
□ tfsec . — 重大度 HIGH / CRITICAL の問題なし
□ 許容する LOW/MEDIUM 警告は .tfsec.yaml で除外設定済み

【CI/CD】
□ GitHub Actions — .github/workflows/terraform-security.yml 設定済み
□ CI — pre-commit / tflint / tfsec の3段階チェックが動作確認済み
□ Secrets — GitHub Repository Secrets に AWS 認証情報を登録済み

8-3. まとめ

本記事では、Terraform コードを安全に Git 管理するためのセキュリティ基盤を段階的に構築しました。各ツールの役割と組み合わせ効果を整理します。

ツール別の役割分担

ツールタイミング役割
.gitignore常時Terraform の生成ファイル・シークレットをリポジトリから除外
環境変数 / Secrets Manager設計シークレットのハードコードを根絶
gitleaksコミット前 / CI既存・新規のシークレット漏洩を検出
pre-commitコミット前フォーマット・バリデーション・漏洩スキャンを自動実行
tflintpre-commit / CIAWS ベストプラクティス違反・非推奨 API を静的検出
tfsecCIIAM権限過多・暗号化不備・パブリック公開設定を検出
GitHub ActionsPR / pushチーム全員に同じ検証基準を強制

組み合わせ効果

各ツールは単独でも有効ですが、組み合わせることで防御の層(Defense in Depth)が生まれます。

開発者ローカル→  pre-commit(フォーマット + gitleaks)で即時フィードバック
リポジトリレベル →  .gitignore + シークレット管理で根本的に漏洩を防止
CI/CD  →  tflint + tfsec + pre-commit で全 PR を自動検証

人為的なチェック漏れに頼らず、ツールが自動的に品質とセキュリティを保証します。これにより、コードレビューは「ロジックの正しさ」に集中できるようになります。

第3弾からの学習の積み上げ

第1弾: Git基礎 → ローカルでコードを管理する
第2弾: GitHub基礎  → チームでコードを共有・レビューする
第3弾: ブランチ戦略 → 開発フローを体系化する
第4弾(本記事) → セキュリティ基盤を構築して安全に運用する
第5弾(次回)→ CI/CDパイプラインで自動デプロイを実現する

「書く → 共有する → 体系化する → 安全にする → 自動化する」という段階を踏むことで、実務レベルの IaC 運用スキルが身につきます。

8-4. 次弾予告(第5弾)

次の記事(最終回) → 第5弾: Terraform実践 — モジュール化・tfstate・OIDC CI/CD


本記事が役に立ったら、シリーズの他の記事もあわせてご覧ください。