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

📚 Git/GitHub × Terraform実践シリーズ — 全体ロードマップ

各記事は独立して読めますが、第1弾から順に進めると「Git基礎 → チーム協業 → ブランチ戦略 → セキュリティ → CI/CD」という一本の学習パスとして体系的に習得できます。


目次

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)

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

# Gitgit --version# git version 2.39.x 以上# Terraformterraform --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.tfoverride.tf.json*_override.tf*_override.tf.jsonEOF

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


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.tfoverride.tf.json*_override.tf*_override.tf.json# tfplan バイナリ(terraform plan -out=tfplan で生成)# バイナリ形式でシークレットが含まれる場合あり*.tfplantfplan# 生成されたファイル(terraform-docs 等が出力するファイル).terraform.tfstate.lock.info# =============================================# 認証情報・シークレット(直接配置の防止)# =============================================# AWS 認証情報(誤配置防止).aws/credentials*_credentials# SSH 秘密鍵*.pem*.keyid_rsaid_ed25519# 環境変数ファイル.env.env.local.env.*.local# =============================================# OS / エディタ固有# =============================================# macOS.DS_Store.AppleDouble.LSOverride# WindowsThumbs.dbehthumbs.dbDesktop.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*.bakcrash.logcrash.*.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.hclgit 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-demogit initecho "Initialized Git repository: $(pwd)"

Step 2: .gitignore を配置

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

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

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

# providers.tfterraform {  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}EOFcat > 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_12345AWS_SECRET_ACCESS_KEY=dummy_secret_key_abcdefghijklmnopqrstuvwxyzEOF

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

terraform init

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

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

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

git status

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

On branch mainNo commits yetUntracked files:  (use "git add <file>..." to include in what will be committed)  .gitignore  providers.tf  variables.tfnothing 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.tfgit 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"EOFgit add terraform.tfvars.examplegit 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.tfprovider "aws" {  access_key = "AKIAIOSFODNN7EXAMPLE"  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"  region  = "ap-northeast-1"}

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

なぜ危険なのか

① gitログに永久に残る

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

# ファイルを削除してコミットしても…git rm provider.tfgit 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.tfvariable "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_KEYaws_secret_access_key = dummy_secret_default[staging]aws_access_key_id  = AKIA_DUMMY_STAGING_KEYaws_secret_access_key = dummy_secret_staging# 環境を切り替える場合export AWS_PROFILE=stagingterraform 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 secretsterraform.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.tfvariable "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.tfdata "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-1aws 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 -xzsudo 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/bPxRfiCYEXAMPLEKEYSecret:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYRuleID:aws-secret-access-keyEntropy:  4.70File:  terraform/variables.tfLine:  12Commit:a3f8c2d1b9e4f076a1234567890abcdef1234567Author:Jane DoeEmail: jane@example.comDate:  2024-03-15T10:23:45ZFingerprint: 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 --allgit 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.5max = 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/bashset -eecho "Running gitleaks scan..."gitleaks detect --source . --staged --no-bannerif [ $? -ne 0 ]; then  echo "ERROR: gitleaks detected secrets. Commit blocked."  exit 1fi

実行権限を付与:

chmod +x .git/hooks/pre-commit

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

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

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

インストールと有効化:

pip install pre-commitpre-commit install

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


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

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

公式GitHub Action を使用

# .github/workflows/secret-scan.ymlname: Secret Scanon:  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シークレットのみ検出したい場合に選択肢となる。

インストール

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

基本セットアップ

# AWSパターンをグローバルに登録git secrets --register-aws --global# リポジトリにhookをインストールcd /path/to/your/repogit 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.tfstateterraform.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.tfdata "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.yamlrepos:  # ── 汎用フック ──────────────────────────────────────  - 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..................................................Passedend-of-file-fixer....................................................Passedcheck-yaml...........................................................Passedcheck-merge-conflict.................................................Passeddetect-private-key...................................................Passedterraform_fmt........................................................Passedterraform_validate...................................................Passedterraform_docs.......................................................Passedgitleaks.............................................................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.tfgit commit -m "test: フォーマット崩れのテスト"

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

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

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

# 自動修正されたファイルを確認git diff main.tf# 再ステージング → コミット成功git add main.tfgit commit -m "test: フォーマット修正済みのテスト"
terraform_fmt........................................................Passedterraform_validate...................................................Passedterraform_docs.......................................................Passedgitleaks.............................................................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.envgit add test.envgit commit -m "test: シークレット混入のテスト"

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

gitleaks.............................................................Failed- hook id: gitleaks- exit code: 1 ○ │╲ │ ○ ○ ░ ░ gitleaksFinding:  AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYSecret:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYRuleID:aws-secret-access-keyEntropy:  4.234File:  test.envLine:  1Fingerprint: test.env:aws-secret-access-key:1

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

# test.env からシークレットを削除rm test.env# .gitignore に追加して誤コミットを防ぐecho "*.env" >> .gitignoregit add .gitignoregit commit -m "chore: .env ファイルを .gitignore に追加"

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

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

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

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

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

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

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

# macOSbrew 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.tfgit commit -m "feat: S3 モジュールに新しい変数を追加"
terraform_docs.......................................................Failed- hook id: terraform_docs- files were modified by this hookterraform/modules/s3/README.md

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

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

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

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

# .github/workflows/pre-commit.ymlname: pre-commiton:  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 | bashtflint --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/tflinttflint --version

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

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

# .tflint.hclplugin "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.ymlname: tflinton:  pull_request:  push: branches:- mainjobs:  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.hclgit commit -m "ci: add tflint workflow"git push origin feature/add-security-checks

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


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

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

# plan前の標準チェックフローterraform fmt -recursivetflint --recursiveterraform validateterraform plan

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

# ~/.zshrc または ~/.bashrcalias 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 tfsectfsec --version# v1.28.9

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

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

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

TFSEC_VERSION="1.28.9"curl -sLo tfsec \  "https://github.com/aquasecurity/tfsec/releases/download/v${TFSEC_VERSION}/tfsec-linux-amd64"chmod +x tfsecsudo 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.ymlexclude:  - 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.ymlname: tfsecon:  pull_request:  push: branches:- mainjobs:  tfsec: runs-on: ubuntu-latest permissions:contents: readsecurity-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.ymlname: Terraform Checkson:  pull_request:  push: branches:- mainjobs:  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: readsecurity-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 # 差分確認

エイリアスで一括実行:

# ~/.zshrcalias 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弾 — GitHub Actions × Terraform OIDC 完全ガイド本記事で構築したセキュリティ基盤の上に、GHA CI/CD パイプラインを構築します。OIDC を使って AWS キーを一切使わないセキュアなデプロイフローを実現。

  • GitHub Actions OIDC — AWS キー不要の認証フロー
  • terraform plan / apply を PR と push で自動実行
  • plan 結果を PR コメントに自動投稿
  • 環境別デプロイ(dev / stg / prod)のブランチ戦略と連携

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