- ✅ 前提①: Terraform 基礎 — init/plan/apply の理解
- ✅ 前提②: Terraform 実践 — モジュール・State・CI/CD
- ✅ 第1弾: Git入門 — ローカルで init/commit/branch/merge を習得
- ✅ 第2弾: GitHub入門 — push/pull/PR/Issue/Fork でチーム協業
- ✅ 第3弾: ブランチ戦略 — GitFlow / GitHub Flow / trunk-based と Terraform モジュール管理
- ✅ 第4弾(本記事): セキュリティ — .gitignore/シークレット/pre-commit/tflint/tfsec
- 📌 第5弾(近日公開): GHA × Terraform OIDC — CI/CDパイプライン構築
各記事は独立して読めますが、第1弾から順に進めると「Git基礎 → チーム協業 → ブランチ戦略 → セキュリティ → CI/CD」という一本の学習パスとして体系的に習得できます。
- 1 Section 1: はじめに
- 2 Section 2: Terraform向け .gitignore 完全版
- 3 Section 3: シークレット管理の基本
- 4 Section 4: シークレット漏洩防止ツール
- 4.1 4-1. なぜスキャンツールが必要か
- 4.2 4-2. gitleaks — インストールと基本スキャン
- 4.3 4-3. gitleaks の出力を読む
- 4.4 4-4. .gitleaks.toml でカスタムルール
- 4.5 4-5. pre-commit フックとの統合
- 4.6 4-6. GitHub Actions による CI/CD 統合
- 4.7 4-7. git-secrets — AWS特化の代替ツール
- 4.8 4-8. gitleaks vs git-secrets 比較
- 4.9 4-9. 漏洩した場合の緊急対応フロー
- 4.10 4-10. Terraform での Best Practice
- 5 Section 5: pre-commit hooks セットアップ
- 6 Section 6: tflint セットアップ・基本ルール・実行例
- 7 Section 7: tfsec セットアップ・基本スキャン・実行例
- 8 Section 8: 統合フロー + まとめ + 次弾予告
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 2 | Terraform向け .gitignore 完全版 | 20分 |
| Section 3 | シークレット管理3選 | 30分 |
| Section 4 | gitleaks による漏洩スキャン | 20分 |
| Section 5 | pre-commit hooks 自動化 | 25分 |
| Section 6-7 | tflint / 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 以上| ツール | 最低バージョン | インストール方法 |
|---|---|---|
| Git | 2.39 以上 | brew install git |
| Terraform | 1.7 以上 | brew tap hashicorp/tap && brew install hashicorp/tap/terraform |
| Python | 3.10 以上 | brew install python |
| pre-commit | 3.x 以上 | pip3 install pre-commit |
| gitleaks | 8.x 以上 | brew install gitleaks |
| tflint | 0.50 以上 | brew install tflint |
| tfsec | 1.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つあります。
- 容量: プロバイダバイナリは数百MBに達し、Git リポジトリを肥大化させる
- 再生成可能:
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 で再生成可能 |
*.tfstate | AWSリソースIDとシークレットが平文で含まれる | リモートバックエンド(S3等)を使うこと |
*.tfstate.backup | tfstate の前バージョン(同様のリスク) | — |
*.tfvars | 環境別シークレット・設定値が含まれることが多い | テンプレート(*.tfvars.example)はコミット可 |
*.tfvars.json | JSON 形式の変数ファイル(同様のリスク) | — |
.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 はプロバイダのバージョンをロックするファイルです。npm の package-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*.keyEOFStep 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"}EOFStep 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_abcdefghijklmnopqrstuvwxyzEOFStep 5: terraform init で .terraform/ を生成
terraform initinit 完了後、ディレクトリ構造を確認します。
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.hcl・terraform.tfvars・.env が Untracked 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 HEADgit show --stat の出力に .terraform/・terraform.tfvars・.env が含まれていないことを確認します。
- [ ] .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コンソール)
- AWSコンソール → AWS Secrets Manager を開く
- 「シークレットを保存」 をクリック
- シークレットの種類: 「その他のシークレットのタイプ」 を選択
- キーと値のペアを入力:
- キー:
password、値:dummy_db_password_here - シークレット名:
prod/myapp/db_password - 自動ローテーション: 今回は無効のまま(本番では有効化を推奨)
- 「保存」 をクリック
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.tf で sensitive = 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-secrets・gitleaks・tfsec)を組み合わせたワークフローを解説します。
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 gitleaksLinuxでは公式リリースページからバイナリを直接取得する:
# 最新バージョンを確認して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.json4-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 --tags4-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 --verbose4-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.sarif4-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 比較
| 項目 | gitleaks | git-secrets |
|---|---|---|
| 対応シークレット | 汎用(100+パターン) | AWS特化 |
| インストール | バイナリ1つ / brew | brew / make install |
| カスタムルール | .gitleaks.toml(柔軟) | --addコマンド(簡易) |
| CI/CD統合 | GitHub Actions公式サポート | 手動設定が必要 |
| 出力形式 | JSON / SARIF / CSV | テキストのみ |
| 履歴スキャン | gitleaks detect | git 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-userCloudTrailで不正利用を確認
# 過去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 table4-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 applyAWS 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.tfpre-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.1CI 環境(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 はコミットハッシュではなくタグで固定する。latest や main は禁止だ。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-files5-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.mdREADME.md が自動更新されるため、再ステージングして再コミットする。
git add terraform/modules/s3/README.mdgit commit -m "feat: S3 モジュールに新しい変数を追加"# terraform_docs.......................................................Passed5-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.1pre-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 cleanpre-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-yaml | YAML 構文検証 | エラー時はコミットブロック |
check-merge-conflict | マージコンフリクトマーカー検出 | エラー時はコミットブロック |
terraform_fmt | Terraform 自動フォーマット | 自動修正後に再ステージング必要 |
terraform_validate | Terraform 構文・整合性検証 | init 済み環境が必要 |
terraform_docs | README 自動生成 | 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 validate | HCL構文・型チェック | 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.3Linux(公式インストールスクリプト)
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 --version6-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_version | terraform {} ブロックの required_version 記述を必須化 |
terraform_required_providers | required_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.......................Passed6-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 compactGITHUB_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-checksPRを開くと 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 との違い
| 観点 | tflint | tfsec |
|---|---|---|
| 主な用途 | 構文・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.9Linux(インストールスクリプト)
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 --version7-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-sgrALBからのトラフィックのみ許可する場合は 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..............PassedHIGH以上の問題があればコミットがブロックされ、修正を促します。
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.sarifSARIF形式でアップロードするメリット
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.sariflint ジョブと 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 + tfsec | pre-commit フック |
| PR作成時 | tflint + tfsec | GitHub 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 | コミット前 | フォーマット・バリデーション・漏洩スキャンを自動実行 |
tflint | pre-commit / CI | AWS ベストプラクティス違反・非推奨 API を静的検出 |
tfsec | CI | IAM権限過多・暗号化不備・パブリック公開設定を検出 |
GitHub Actions | PR / 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弾)
- GitHub Actions OIDC — AWS キー不要の認証フロー
- terraform plan / apply を PR と push で自動実行
- plan 結果を PR コメントに自動投稿
- 環境別デプロイ(dev / stg / prod)のブランチ戦略と連携
本記事が役に立ったら、シリーズの他の記事もあわせてご覧ください。