Git/GitHub × Terraform 実践シリーズ(全5弾)
- 第1弾: Git入門 — init/commit/branch/merge
- 第2弾: GitHub入門 — repo/PR/Issue/fork
- 第3弾: ブランチ戦略 — GitFlow/GitHub Flow/trunk-based
- 第4弾(本記事): セキュリティ — .gitignore/シークレット/pre-commit/tflint/tfsec
- 第5弾(最終回): Terraform実践 — モジュール化・tfstate・OIDC 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)
必須ツールとバージョン確認
# Git
git --version
# git version 2.39.x 以上
# Terraform
terraform --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.tf
override.tf.json
*_override.tf
*_override.tf.json
EOF
この最小構成でも主要なリスクは防げますが、実際のプロジェクトでは追加の除外が必要です。
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.tf
override.tf.json
*_override.tf
*_override.tf.json
# tfplan バイナリ(terraform plan -out=tfplan で生成)
# バイナリ形式でシークレットが含まれる場合あり
*.tfplan
tfplan
# 生成されたファイル(terraform-docs 等が出力するファイル)
.terraform.tfstate.lock.info
# =============================================
# 認証情報・シークレット(直接配置の防止)
# =============================================
# AWS 認証情報(誤配置防止)
.aws/
credentials
*_credentials
# SSH 秘密鍵
*.pem
*.key
id_rsa
id_ed25519
# 環境変数ファイル
.env
.env.local
.env.*.local
# =============================================
# OS / エディタ固有
# =============================================
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
# Linux
*~
# JetBrains IDE (IntelliJ, GoLand 等)
.idea/
*.iml
# Visual Studio Code
.vscode/
*.code-workspace
# Vim
*.swp
*.swo
# =============================================
# CI/CD ローカル実行ファイル
# =============================================
# act (GitHub Actions ローカル実行)
.actrc
# =============================================
# ログ・一時ファイル
# =============================================
*.log
*.tmp
*.bak
crash.log
crash.*.log
このテンプレートをプロジェクトルートの .gitignore として使用することを推奨します。
2-4. .terraform.lock.hcl の扱い(チーム vs ソロ)
.terraform.lock.hcl はプロバイダのバージョンをロックするファイルです。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.hcl
git commit -m "chore: track terraform lock file for consistent provider versions"
2-5. ハンズオン: .gitignore の動作確認
実際に Terraform プロジェクトを作成し、.gitignore が正しく機能することを確認します。
Step 1: プロジェクトディレクトリを作成
mkdir terraform-security-demo && cd terraform-security-demo
git init
echo "Initialized Git repository: $(pwd)"
Step 2: .gitignore を配置
先ほどの完全版 .gitignore をコピーするか、以下の最小版を使用します。
cat > .gitignore << 'EOF'
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.backup
*.tfvars
*.tfvars.json
override.tf
override.tf.json
*_override.tf
*_override.tf.json
*.tfplan
.env
*.pem
*.key
EOF
Step 3: Terraform 設定ファイルを作成
# providers.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.7"
}
provider "aws" {
region = var.aws_region
}
cat > providers.tf << 'EOF'
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.7"
}
provider "aws" {
region = var.aws_region
}
EOF
cat > variables.tf << 'EOF'
variable "aws_region" {
description = "AWS region"
type = string
default = "ap-northeast-1"
}
EOF
Step 4: 除外対象ファイルを作成
# .tfvars(シークレットを含む変数ファイル)
cat > terraform.tfvars << 'EOF'
aws_region = "ap-northeast-1"
# 本来ここにシークレットが書かれることがある
EOF
# ダミーの .env ファイル
cat > .env << 'EOF'
AWS_ACCESS_KEY_ID=AKIA_DUMMY_ACCESS_KEY_12345
AWS_SECRET_ACCESS_KEY=dummy_secret_key_abcdefghijklmnopqrstuvwxyz
EOF
Step 5: terraform init で .terraform/ を生成
terraform init
init 完了後、ディレクトリ構造を確認します。
ls -la
# .terraform/ ディレクトリと .terraform.lock.hcl が生成されている
Step 6: git status で除外状況を確認
git status
期待される出力(除外対象が表示されないこと):
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
providers.tf
variables.tf
nothing added to commit but untracked files present (use "git add" to track)
.terraform/・.terraform.lock.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.tf
git commit -m "feat: add terraform project with security-focused .gitignore"
git show --stat HEAD
git 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"
EOF
git add terraform.tfvars.example
git commit -m "docs: add tfvars template for local setup"
このパターンにより「どの変数が必要か」をチームで共有しつつ、実際の値は各自のローカル環境に留めることができます。
Section 3: シークレット管理の基本
Section 1・2 で .gitignore の設計と pre-commit による自動チェックを学びました。しかし「そもそもシークレット(認証情報・パスワード・APIキー)をどこに置くか」というアーキテクチャ設計が誤っていると、フックで止める以前の問題になります。Section 3 では Terraform でシークレットを安全に扱う 3 つのアプローチと、tfstate に潜むシークレット問題を解説します。
3-1. やってはいけない例(アンチパターン)
まず、絶対にやってはいけないパターンを確認します。
# ❌ 絶対にやってはいけない例 — provider.tf
provider "aws" {
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
region = "ap-northeast-1"
}
AWS アクセスキーを Terraform コードにハードコードし、git commit した場合、何が起きるのでしょうか。
なぜ危険なのか
① gitログに永久に残る
git commit した時点でオブジェクト(コミット・ツリー・ブロブ)としてリポジトリに刻み込まれます。後からファイルを削除しても、コミット履歴には残り続けます。
# ファイルを削除してコミットしても…
git rm provider.tf
git commit -m "remove credentials"
# git log で過去のコミットを参照すると消えていない
git show abc1234:provider.tf
# → access_key = "AKIAIOSFODNN7EXAMPLE" がまだ見える
② force-push しても消えない
「git push --force でコミット履歴を書き換えれば消える」と思うかもしれませんが、これも不完全です。
- リモートリポジトリ(GitHub/GitLab)の reflog にキャッシュが残る
- 他のメンバーが
git fetch済みならローカルにコピーが存在する - GitHub では削除リクエストを送っても CDN キャッシュにしばらく残る
正しい対処は「キーを即座に無効化し、新しいキーを発行する」ことです。コード上で消すより先にキーを失効させるのが最優先です。
③ パブリックリポジトリでは特に深刻
GitHub にプッシュした瞬間、ボット(悪意あるスクリプト)がスキャンし、数分以内に AWSアカウントが不正利用されることが報告されています。AWS 自身も GitHub をスキャンして危険なキーを検出し、アカウント所有者に通知する仕組みを持っていますが、通知が来るころには手遅れのケースも多くあります。
3-2. アプローチ1: 環境変数を使う(最も簡単)
最もシンプルな方法は、シェルの環境変数に認証情報を設定し、Terraform のコードには一切書かないことです。
環境変数の設定
# ~/.bashrc または ~/.zshrc に追記(bashの場合は ~/.bashrc)
export AWS_ACCESS_KEY_ID="AKIA_DUMMY_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="dummy_secret_access_key_here"
export AWS_DEFAULT_REGION="ap-northeast-1"
設定後、シェルを再起動するか source で反映します。
# 設定を即座に反映
source ~/.zshrc# zsh の場合
source ~/.bashrc # bash の場合
# 正しく設定されているか確認
echo $AWS_ACCESS_KEY_ID
# → AKIA_DUMMY_ACCESS_KEY(実際のキーが表示される)
Terraform コードには認証情報を書かない
# provider.tf — 認証情報を一切書かない
provider "aws" {
region = var.aws_region
# access_key, secret_key は書かない
# → Terraform が環境変数 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY を自動で読む
}
# variables.tf
variable "aws_region" {
description = "AWSリージョン"
type = string
default = "ap-northeast-1"
}
動作確認
# 環境変数から自動で認証情報を読み、planが成功する
terraform plan
# 出力例
# Refreshing Terraform state in-memory prior to plan...
# ...
# Plan: 1 to add, 0 to change, 0 to destroy.
この方法のメリット・注意点
| 項目 | 内容 |
|---|---|
| メリット | 設定が簡単・Terraform コードがシンプル |
| メリット | ~/.zshrc や ~/.bashrc は通常 Git 管理外 |
| 注意点 | シェルを終了すると消える(export は永続しない場合あり) |
| 注意点 | 複数 AWS アカウントを切り替える場合は手間 |
| 注意点 | CI 環境ではシークレットマネージャーや IAM ロールを使うべき |
複数のAWSプロファイルを管理する場合は、AWS_PROFILE 環境変数と ~/.aws/credentials の組み合わせも有効です。
# ~/.aws/credentials(このファイル自体も Git 管理外に置く)
[default]
aws_access_key_id = AKIA_DUMMY_DEFAULT_KEY
aws_secret_access_key = dummy_secret_default
[staging]
aws_access_key_id = AKIA_DUMMY_STAGING_KEY
aws_secret_access_key = dummy_secret_staging
# 環境を切り替える場合
export AWS_PROFILE=staging
terraform plan# staging プロファイルの認証情報を使用
3-3. アプローチ2: terraform.tfvars(.gitignoreで除外必須)
DB パスワードや外部サービスの API キーなど、AWS 認証情報以外のシークレットを Terraform に渡す場合は terraform.tfvars が便利です。ただし .gitignore への登録が必須です。
ディレクトリ構成
terraform-git-handson/
├── main.tf
├── variables.tf
├── provider.tf
├── terraform.tfvars ← ❌ Git 管理外(.gitignore に追加)
├── terraform.tfvars.example ← ✅ Git 管理対象(テンプレート)
└── .gitignore
.gitignore への追記
# .gitignore(Section 2 で作成したものに追記)
# Terraform secrets
terraform.tfvars
*.tfvars
!terraform.tfvars.example # exampleファイルだけは追跡対象
! プレフィックスで .tfvars 全体を除外しつつ、terraform.tfvars.example だけ追跡対象に戻しています。
terraform.tfvars(Git 管理外)
# terraform.tfvars — .gitignore に追記して除外する
db_password = "dummy_db_password_here"
api_secret_key = "dummy_api_secret_here"
slack_webhook= "https://hooks.slack.com/services/dummy/dummy/dummy"
terraform.tfvars.example(Git 管理対象)
チームメンバーが必要な変数を把握できるよう、値を空(またはダミー値)にしたサンプルを用意します。
# terraform.tfvars.example — 実際の値は書かない。Git管理対象
db_password = ""
api_secret_key = ""
slack_webhook= ""
variables.tf — sensitive = true でマスク
# variables.tf
variable "db_password" {
description = "RDS データベースのパスワード"
type = string
sensitive= true # terraform plan / apply の出力でマスク表示される
}
variable "api_secret_key" {
description = "外部APIシークレットキー"
type = string
sensitive= true
}
variable "slack_webhook" {
description = "Slack Webhook URL"
type = string
sensitive= true
}
sensitive = true を設定すると、terraform plan の出力でその変数の値が (sensitive value) と表示され、ログやターミナルに実際の値が露出しません。
動作確認
# terraform.tfvars が存在すれば自動で読み込まれる
terraform plan
# sensitive = true の変数は出力でマスクされる
# Changes to Outputs:
#+ db_endpoint = (sensitive value)
チームへの引き継ぎ方法
新しいメンバーがリポジトリを git clone した後の手順を README に明示します。
# 1. exampleファイルをコピー
cp terraform.tfvars.example terraform.tfvars
# 2. 実際の値をチームの共有パスワードマネージャーから取得して設定
vim terraform.tfvars
# 3. Gitに追加しないことを確認
git status
# → terraform.tfvars が出力されないことを確認(.gitignore に含まれている)
3-4. アプローチ3: AWS Secrets Manager連携
本番環境やチーム開発では、シークレットをファイルに置くのではなく AWS Secrets Manager に集中管理し、Terraform からデータソースとして参照するのがベストプラクティスです。
Secrets Manager へのシークレット登録(AWSコンソール)
- AWSコンソール → AWS Secrets Manager を開く
- 「シークレットを保存」 をクリック
- シークレットの種類: 「その他のシークレットのタイプ」 を選択
- キーと値のペアを入力:
- キー:
password、値:dummy_db_password_here - シークレット名:
prod/myapp/db_password - 自動ローテーション: 今回は無効のまま(本番では有効化を推奨)
- 「保存」 をクリック
Secrets Manager からデータソースとして参照
# secrets.tf
data "aws_secretsmanager_secret" "db_password" {
name = "prod/myapp/db_password"
}
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = data.aws_secretsmanager_secret.db_password.id
}
# main.tf — RDS インスタンスに Secrets Manager の値を渡す
resource "aws_db_instance" "main" {
identifier = "myapp-db"
engine= "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
allocated_storage = 20
db_name = "myappdb"
username = "admin"
password = data.aws_secretsmanager_secret_version.db_password.secret_string
skip_final_snapshot = true
}
JSON形式のシークレットの場合
複数の値をまとめて管理する場合は JSON 形式で保存します。
# Secrets Manager に保存されている JSON 例
# {"username": "admin", "password": "dummy_db_password_here", "host": "myapp-db.cluster-xxxx.ap-northeast-1.rds.amazonaws.com"}
data "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = "prod/myapp/db_credentials"
}
locals {
db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string)
}
resource "aws_db_instance" "main" {
username = local.db_credentials["username"]
password = local.db_credentials["password"]
}
既存シークレットの terraform import
AWS コンソールで作成したシークレットを Terraform の管理下に取り込む場合は terraform import を使います。
# シークレットの ARN を確認
aws secretsmanager describe-secret --secret-id prod/myapp/db_password \
--query 'ARN' --output text
# → arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:prod/myapp/db_password-AbCdEf
# terraform import で管理下に取り込む
terraform import aws_secretsmanager_secret.db_password \
arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:prod/myapp/db_password-AbCdEf
# Secrets Manager シークレット自体を Terraform で管理する場合
resource "aws_secretsmanager_secret" "db_password" {
name = "prod/myapp/db_password"
description = "RDS データベースのパスワード"
recovery_window_in_days = 7 # 削除後7日間は復旧可能
}
IAM ポリシー設定(Terraform 実行ロールへの権限付与)
Terraform が Secrets Manager からシークレットを読み取るには、実行ロールに権限が必要です。
# iam.tf — Terraform 実行ロールに Secrets Manager 読み取り権限を付与
data "aws_iam_policy_document" "secrets_read" {
statement {
effect = "Allow"
actions = [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
]
resources = [
"arn:aws:secretsmanager:ap-northeast-1:*:secret:prod/myapp/*"
]
}
}
resource "aws_iam_policy" "secrets_read" {
name= "terraform-secrets-read"
policy = data.aws_iam_policy_document.secrets_read.json
}
3-5. tfstate のシークレット問題とリモート state
アプローチ1〜3 でコードへのシークレット記載を防いでも、見落とされがちな問題が terraform.tfstate です。
tfstate に何が書かれているのか
terraform apply が完了すると、Terraform は作成・管理したリソースの情報を terraform.tfstate に書き出します。このファイルには適用後のリソースの実際の値が平文で含まれます。
// terraform.tfstate の一部(例)
{
"resources": [
{
"type": "aws_db_instance",
"name": "main",
"instances": [
{
"attributes": {
"password": "dummy_db_password_here",
"username": "admin",
"endpoint": "myapp-db.xxxx.ap-northeast-1.rds.amazonaws.com"
}
}
]
}
]
}
variables.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-1
aws s3api put-bucket-versioning \
--bucket my-tfstate-bucket \
--versioning-configuration Status=Enabled
# DynamoDB テーブルを作成(LockID がパーティションキー)
aws dynamodb create-table \
--table-name terraform-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region ap-northeast-1
バックエンドを S3 に切り替えるには terraform init を再実行します。
# リモートバックエンドに移行
terraform init
# 出力例(ローカル state を S3 に移行するか確認される)
# Do you want to copy existing state to the new backend?
# → yes を入力
S3 バケットの追加セキュリティ設定
# s3_tfstate.tf — tfstate バケットのセキュリティ設定
resource "aws_s3_bucket" "tfstate" {
bucket = "my-tfstate-bucket"
}
# バージョニング(誤った apply からのロールバック用)
resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration {
status = "Enabled"
}
}
# 暗号化(保存時)
resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# パブリックアクセスのブロック(最重要)
resource "aws_s3_bucket_public_access_block" "tfstate" {
bucket= aws_s3_bucket.tfstate.id
block_public_acls = true
block_public_policy = true
ignore_public_acls= true
restrict_public_buckets = true
}
3-6. 比較表: シークレット管理3手法
3つのアプローチを整理して比較します。
| 手法 | 設定難易度 | セキュリティ | チーム共有 | CI/CD対応 | 推奨ケース |
|---|---|---|---|---|---|
| 環境変数 | ⭐ | ★★★ | △(個人設定) | ○(CI で secrets 変数に設定) | 個人・小チーム・AWS認証情報 |
| tfvars(除外) | ⭐⭐ | ★★ | △(共有不可) | △(手動配布が必要) | ローカル開発・DB パスワード |
| Secrets Manager | ⭐⭐⭐ | ★★★★★ | ✅ | ✅(IAM ロールで透過的) | 本番環境・チーム・自動ローテーション |
どれを選ぶべきか
個人開発・学習目的: 環境変数(アプローチ1)から始めるのが最もシンプルです。AWS CLI の aws configure と組み合わせれば認証情報の管理が楽になります。
チーム開発(ステージング以上): Secrets Manager(アプローチ3)を基本とし、AWS 認証情報は IAM ロール(EC2/ECS/Lambda のインスタンスプロファイルや GitHub Actions の OIDC)で解決するのがベストプラクティスです。アクセスキー自体を発行しない設計が理想です。
ローカル開発の DB パスワード等: tfvars(アプローチ2)は手軽ですが、.gitignore への登録を忘れると致命的です。pre-commit フックで detect-secrets または git-secrets を設定し、コミット前に自動検出する仕組みと組み合わせてください(Section 2 参照)。
まとめ: セキュアな Terraform シークレット管理の鉄則
1. コードにシークレットを書かない(ハードコード禁止)
2. tfvars は必ず .gitignore に追加する
3. tfstate は Git 管理しない → S3 リモートバックエンドへ
4. 本番は Secrets Manager + IAM ロール(アクセスキー不要)
5. pre-commit フックで誰かのミスも自動でキャッチする
次の Section 4 では、.gitignore の自動生成・管理ツールと、シークレット検出の自動化ツール群(detect-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 gitleaks
Linuxでは公式リリースページからバイナリを直接取得する:
# 最新バージョンを確認してURLを差し替える
GITLEAKS_VERSION="8.21.2"
curl -sSfL \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz
sudo mv gitleaks /usr/local/bin/
インストール確認:
gitleaks version
基本スキャン
カレントディレクトリのリポジトリ全履歴をスキャン:
gitleaks detect --source .
ステージング済みの変更のみスキャン(コミット前フックで使用):
gitleaks detect --source . --staged
特定のブランチやコミット範囲を指定:
# mainブランチの全履歴
gitleaks detect --source . --log-opts="main"
# 直近10コミット分のみ
gitleaks detect --source . --log-opts="-10"
レポートをJSONで出力(CI/CDでの後処理に有用):
gitleaks detect --source . --report-format json --report-path gitleaks-report.json
4-3. gitleaks の出力を読む
漏洩が検出されると、gitleaksは以下のような出力を表示する:
Finding: AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Secret:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
RuleID:aws-secret-access-key
Entropy: 4.70
File: terraform/variables.tf
Line: 12
Commit:a3f8c2d1b9e4f076a1234567890abcdef1234567
Author:Jane Doe
Email: jane@example.com
Date: 2024-03-15T10:23:45Z
Fingerprint: a3f8c2d1b9e4f076a1234567890abcdef1234567:terraform/variables.tf:aws-secret-access-key:12
各フィールドの意味:
| フィールド | 説明 |
|---|---|
| Finding | 検出されたシークレット(一部マスク表示) |
| RuleID | マッチしたルールの識別子 |
| Entropy | エントロピー値(高いほどランダム文字列の可能性が高い) |
| File | シークレットが含まれるファイルパス |
| Commit | 最初にコミットされたハッシュ |
| Fingerprint | 同一漏洩を追跡するための一意ID |
検出後の対処フロー:
1. AWSコンソールでキーを即座に無効化(IAM → アクセスキー → 無効化)
2. CloudTrailで過去の不正利用がないか確認(直近7日間)
3. git-filter-repo で履歴からシークレットを完全削除
4. force-push で上書き(チーム全員に周知・ローカルリポジトリの再クローンを依頼)
5. 新しいキーを発行し、各環境(CI/CD含む)の環境変数を更新
git filter-repo による履歴削除
# インストール
pip install git-filter-repo
# 特定文字列をREDACTEDに置換(シークレット値を指定)
git filter-repo --replace-text <(echo "AKIAIOSFODNN7EXAMPLE==>REDACTED_ACCESS_KEY_ID")
# ファイルごと削除(.envファイルを完全に除去)
git filter-repo --path .env --invert-paths
履歴削除後のforce-push:
git push origin --force --all
git push origin --force --tags
4-4. .gitleaks.toml でカスタムルール
プロジェクト固有のシークレット形式(社内システムのAPIキーなど)は .gitleaks.toml でカスタムルールを追加できる。
基本構成
# .gitleaks.toml
# デフォルトルールセットを継承
[extend]
useDefault = true
# カスタムルール: 社内APIキー
[[rules]]
id = "internal-api-key"
description = "Internal API Key for Company Systems"
regex = '''(?i)internal[_-]?api[_-]?key\s*=\s*['"]([a-zA-Z0-9]{32,64})['"]'''
tags = ["api", "internal"]
# エントロピーフィルタ(ランダム度が低い値は除外)
[rules.entropy]
min = 3.5
max = 8.0
特定パターンを allowlist に追加
テスト用のダミー値や既知の偽陽性を除外する:
# .gitleaks.toml
[[rules]]
id = "aws-access-key-id"
# デフォルトルールを継承しつつ allowlist を追加
[rules.allowlist]
description = "Ignore test fixtures and documentation examples"
regexes = [
"AKIAIOSFODNN7EXAMPLE", # AWSドキュメントのサンプル値
"AKIAI44QH8DHBEXAMPLE",
]
commits = [
"a3f8c2d1b9e4f076a1234567890abcdef1234567",# 既知のテスト用コミット
]
設定ファイルの検証
gitleaks detect --source . --config .gitleaks.toml --verbose
4-5. pre-commit フックとの統合
gitleaksをpre-commitフックに組み込むことで、シークレットを含むコミットをコミット前にブロックできる。
方法1: Gitネイティブフック
# .git/hooks/pre-commit
#!/bin/bash
set -e
echo "Running gitleaks scan..."
gitleaks detect --source . --staged --no-banner
if [ $? -ne 0 ]; then
echo "ERROR: gitleaks detected secrets. Commit blocked."
exit 1
fi
実行権限を付与:
chmod +x .git/hooks/pre-commit
方法2: pre-commit フレームワーク
.pre-commit-config.yaml に追記:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaks
インストールと有効化:
pip install pre-commit
pre-commit install
これでチームメンバー全員が pre-commit install を実行するだけで同一のフックが適用される。
4-6. GitHub Actions による CI/CD 統合
ローカルフックを抜けてpushされたケースに備え、CI側でもスキャンを実施する。
公式GitHub Action を使用
# .github/workflows/secret-scan.yml
name: Secret Scan
on:
push:
branches: ["**"]
pull_request:
branches: [main, develop]
jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # 全履歴を取得(shallow clone では履歴スキャン不可)
- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 が重要。デフォルトの shallow clone(--depth 1)では直近のコミットしか取得されず、過去の履歴に埋まったシークレットを見逃す。
カスタム設定ファイルを使用する場合
- name: Run gitleaks with custom config
uses: gitleaks/gitleaks-action@v2
with:
config-path: .gitleaks.toml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
スキャン結果をSARIFとしてGitHub Security タブに表示:
- name: Run gitleaks (SARIF output)
uses: gitleaks/gitleaks-action@v2
with:
args: "--report-format sarif --report-path gitleaks.sarif"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: gitleaks.sarif
4-7. git-secrets — AWS特化の代替ツール
git-secretsはAWSが開発したシークレット検出ツールで、AWSクレデンシャルのパターンに特化している。シンプルな用途やAWSシークレットのみ検出したい場合に選択肢となる。
インストール
# macOS
brew install git-secrets
# Linux(手動インストール)
git clone https://github.com/awslabs/git-secrets.git
cd git-secrets
sudo make install
基本セットアップ
# AWSパターンをグローバルに登録
git secrets --register-aws --global
# リポジトリにhookをインストール
cd /path/to/your/repo
git secrets --install
# 既存のリポジトリ全履歴をスキャン
git secrets --scan -r
--register-aws によって以下のパターンが自動登録される:
AWS Access Key ID: AKIA[0-9A-Z]{16}
AWS Secret Access Key: [0-9a-zA-Z/+]{40}
カスタムパターンの追加
# 禁止パターン(検出対象)を追加
git secrets --add 'MY_CUSTOM_SECRET=[A-Za-z0-9]{20}'
# 許可パターン(偽陽性除外)を追加
git secrets --add --allowed 'AKIAIOSFODNN7EXAMPLE'
4-8. gitleaks vs git-secrets 比較
| 項目 | 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-user
CloudTrailで不正利用を確認
# 過去24時間の該当Access Key IDのアクティビティを確認
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
--start-time $(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%SZ') \
--query 'Events[*].{Time:EventTime,Event:EventName,Resource:Resources[0].ResourceName}' \
--output table
4-10. Terraform での Best Practice
Terraformプロジェクトでのシークレット管理はgitleaks導入と合わせて以下を徹底する。
tfvarsファイルのgitignore設定
# .gitignore
*.tfvars
*.tfvars.json
!example.tfvars # サンプルファイルは追跡対象に残す
.terraform/
terraform.tfstate
terraform.tfstate.*
環境変数経由でシークレットを渡す
# シークレットをtfvarsに書かず環境変数で渡す
export TF_VAR_db_password="$(aws secretsmanager get-secret-value \
--secret-id prod/db/password --query SecretString --output text)"
terraform apply
AWS Secrets Manager / Parameter Store との統合
# secrets.tf
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/rds/master-password"
}
resource "aws_db_instance" "main" {
# ...
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
この構成ではシークレット値がTerraformコードに一切登場しないため、gitleaksのスキャンにも引っかからない。
まとめ: シークレット漏洩は「起きてから対処」では遅い。gitleaksのpre-commitフックとCI/CDスキャンを組み合わせて多層防御を構築し、Terraformではtfvarsへの直書きを避けてSecrets Managerと統合することでリスクを根本から排除できる。
Section 5: pre-commit hooks セットアップ
5-1. pre-commit フレームワークとは
Git には「hooks」と呼ばれる仕組みがある。特定のイベント(コミット・プッシュ等)の前後にシェルスクリプトを自動実行できる機能で、.git/hooks/ ディレクトリに配置する。しかし素の Git hooks にはいくつかの課題がある。
| 課題 | 内容 |
|---|---|
| 個人設定 | .git/hooks/ はリポジトリ管理外のため、チーム全員に配布できない |
| 言語依存 | フックをシェルスクリプトで自分で書く必要がある |
| バージョン管理不可 | フックのバージョンを固定・更新する仕組みがない |
pre-commit フレームワークはこれらを解決する。設定ファイル .pre-commit-config.yaml をリポジトリにコミットすることで、チーム全員が同一のフックを利用できる。フックは外部リポジトリから取得するため、バージョン固定と更新管理も容易だ。
リポジトリ
├── .pre-commit-config.yaml ← チーム共有の設定ファイル
├── .git/
│└── hooks/
│ └── pre-commit ← pre-commit install で自動生成
└── terraform/
└── main.tf
pre-commit が担うコミット前の自動チェック項目は多岐にわたる。
- コードフォーマット: Terraform、Python、JavaScript 等の自動整形
- 構文検証: YAML 構文エラー、マージコンフリクトマーカー検出
- セキュリティ: シークレット・認証情報の混入検出(gitleaks)
- ドキュメント: Terraform モジュールの README 自動更新
5-2. インストール
pre-commit は Python で実装されており、macOS・Linux・Windows に対応している。
# macOS(Homebrew)
brew install pre-commit
# pip(Linux / Windows / 仮想環境)
pip install pre-commit
# pipx(グローバルインストールに推奨)
pipx install pre-commit
# バージョン確認
pre-commit --version
# pre-commit 3.7.1
CI 環境(GitHub Actions など)では、アクション側でインストールが行われるため、手動インストールは不要だ。ローカル開発環境での初期セットアップは以下の手順で完了する。
# 1. リポジトリのルートに移動
cd /path/to/your-terraform-repo
# 2. .pre-commit-config.yaml を作成(次のセクションで詳述)
# 3. フックをインストール(.git/hooks/pre-commit が生成される)
pre-commit install
# インストール確認
cat .git/hooks/pre-commit
# #!/usr/bin/env bash
# # File generated by pre-commit: https://pre-commit.com
# ...
pre-commit install を実行すると、以降 git commit のたびに自動的にフックが走る。
5-3. 基本的な .pre-commit-config.yaml
まず汎用フック + Terraform 専用フック + gitleaks を組み合わせた標準構成を示す。
# .pre-commit-config.yaml
repos:
# ── 汎用フック ──────────────────────────────────────
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace # 行末空白を削除
- id: end-of-file-fixer # ファイル末尾の改行を統一
- id: check-yaml # YAML 構文チェック
- id: check-merge-conflict # マージコンフリクトマーカー検出
- id: detect-private-key# 秘密鍵の混入チェック
# ── Terraform 専用フック ────────────────────────────
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.88.0
hooks:
- id: terraform_fmt # terraform fmt 自動実行
- id: terraform_validate# terraform validate 実行
- id: terraform_docs # README.md を自動更新
args:
- --args=--output-file README.md
# ── シークレット検出 ────────────────────────────────
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
重要: rev はコミットハッシュではなくタグで固定する。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..................................................Passed
end-of-file-fixer....................................................Passed
check-yaml...........................................................Passed
check-merge-conflict.................................................Passed
detect-private-key...................................................Passed
terraform_fmt........................................................Passed
terraform_validate...................................................Passed
terraform_docs.......................................................Passed
gitleaks.............................................................Passed
特定フックのみ実行したい場合:
# terraform_fmt だけ実行
pre-commit run terraform_fmt --all-files
# gitleaks だけ実行
pre-commit run gitleaks --all-files
5-5. terraform_fmt フックの動作確認
意図的にフォーマットが崩れた Terraform ファイルを作成し、コミット時の動作を確認する。
# フォーマットが崩れた main.tf を作成
cat > main.tf << 'EOF'
resource "aws_s3_bucket" "example" {
bucket = "my-test-bucket"
tags = {
Name = "example"
}
}
EOF
# コミット試行
git add main.tf
git commit -m "test: フォーマット崩れのテスト"
terraform_fmt フックが失敗し、自動修正が行われる。
terraform_fmt........................................................Failed
- hook id: terraform_fmt
- files were modified by this hook
main.tf
フックは自動修正後にステージングを要求するため、修正済みファイルを再度ステージングしてコミットする。
# 自動修正されたファイルを確認
git diff main.tf
# 再ステージング → コミット成功
git add main.tf
git commit -m "test: フォーマット修正済みのテスト"
terraform_fmt........................................................Passed
terraform_validate...................................................Passed
terraform_docs.......................................................Passed
gitleaks.............................................................Passed
[main 1a2b3c4] test: フォーマット修正済みのテスト
1 file changed, 3 insertions(+), 3 deletions(-)
5-6. gitleaks フックでシークレット検出
gitleaks は AWS 認証情報・API キー・パスワード等のシークレットパターンをコミット前に検出する。
# ダミーのシークレットを含むファイルを作成
echo 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' >> test.env
git add test.env
git commit -m "test: シークレット混入のテスト"
gitleaks がシークレットを検出してコミットをブロックする。
gitleaks.............................................................Failed
- hook id: gitleaks
- exit code: 1
○
│╲
│ ○
○ ░
░ gitleaks
Finding: AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Secret:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
RuleID:aws-secret-access-key
Entropy: 4.234
File: test.env
Line: 1
Fingerprint: test.env:aws-secret-access-key:1
対処方法: シークレットを環境変数や AWS Secrets Manager に移し、ファイルからは削除する。
# test.env からシークレットを削除
rm test.env
# .gitignore に追加して誤コミットを防ぐ
echo "*.env" >> .gitignore
git add .gitignore
git commit -m "chore: .env ファイルを .gitignore に追加"
テスト・ダミー値を意図的に含める場合
テストコードやドキュメント中にダミー値が必要な場合は .gitleaksignore で除外できる。
# .gitleaksignore を作成
cat > .gitleaksignore << 'EOF'
# テスト用ダミー値のフィンガープリントを除外
test/fixtures/dummy_secrets.tf:aws-secret-access-key:5
docs/examples/terraform.tfvars:aws-access-token:12
EOF
フィンガープリントは gitleaks の出力に含まれる Fingerprint: の値をそのまま使用する。
5-7. terraform_docs フックで README を自動更新
terraform_docs フックを利用すると、Terraform モジュールの変数・出力・プロバイダー情報を README.md に自動反映できる。
まず terraform-docs ツールのインストールが必要だ。
# macOS
brew install terraform-docs
# バージョン確認
terraform-docs --version
# terraform-docs version v0.17.0
モジュールディレクトリに README.md のマーカーを追加する。
<!-- terraform-docs の出力先マーカー(README.md に追記) -->
<!-- BEGIN_TF_DOCS -->
<!-- END_TF_DOCS -->
コミット時に terraform_docs フックが <!-- BEGIN_TF_DOCS --> と <!-- END_TF_DOCS --> の間を自動更新する。
# variables.tf を変更してコミット試行
git add terraform/modules/s3/variables.tf
git commit -m "feat: S3 モジュールに新しい変数を追加"
terraform_docs.......................................................Failed
- hook id: terraform_docs
- files were modified by this hook
terraform/modules/s3/README.md
README.md が自動更新されるため、再ステージングして再コミットする。
git add terraform/modules/s3/README.md
git commit -m "feat: S3 モジュールに新しい変数を追加"
# terraform_docs.......................................................Passed
5-8. pre-commit の CI/CD 統合(GitHub Actions)
ローカルでの pre-commit はあくまで開発者の補助だ。コードレビューや CI 段階でも同一チェックを走らせることで、フックのスキップや未インストールを防ぐ。
# .github/workflows/pre-commit.yml
name: pre-commit
on:
pull_request:
push:
branches: [main]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # gitleaks の全履歴スキャンに必要
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- uses: pre-commit/action@v3.0.1
pre-commit/action は .pre-commit-config.yaml を自動検出し、変更ファイルのみにフックを適用する。PR のたびに差分ファイルだけチェックするため実行時間が短い。
gitleaks でリポジトリ全履歴をスキャンしたい場合は fetch-depth: 0 が必要だ。デフォルトの shallow clone では過去のコミットが含まれず、スキャン範囲が限定される。
5-9. フックの一時スキップと運用ルール
緊急デプロイ等でフックをスキップする必要がある場合は SKIP 環境変数を使う。--no-verify は全フックをスキップするため、特定フックのみの SKIP が推奨だ。
# 特定フックのみスキップ(カンマ区切りで複数指定可)
SKIP=terraform_docs git commit -m "docs: ドキュメント更新は後回し"
# 複数フックをスキップ
SKIP=terraform_docs,terraform_validate git commit -m "wip: 一時的なスキップ"
--no-verify の使用について: セキュリティフック(gitleaks 等)もすべてスキップされるため、チームポリシーとして禁止することを強く推奨する。CI でも同じフックが走るため、--no-verify はローカルでのみ一時回避できるにすぎず、最終的には CI で検出される。
フックのバージョン管理
# 全フックのバージョンを最新に更新
pre-commit autoupdate
# 更新後の .pre-commit-config.yaml の差分確認
git diff .pre-commit-config.yaml
# キャッシュクリア(フック動作がおかしい場合)
pre-commit clean
pre-commit autoupdate は各リポジトリの最新タグに rev を更新する。月次での実行を推奨する(CI での自動 PR 作成も有効だ)。
チーム向け運用ルール
| ルール | 内容 |
|---|---|
--no-verify 禁止 | CI でも同じチェックが走るため意味がない。シークレット混入リスクを高める |
rev はタグ固定 | latest / main は禁止。再現性確保のため必ずバージョンタグを指定 |
| 月次 autoupdate | セキュリティフックの定義更新を逃さないため月1回 autoupdate を実行 |
初回 --all-files | 新規参加者が pre-commit install 後に --all-files で全チェックを確認 |
5-10. まとめ
本セクションでは pre-commit フレームワークを使ったコミット前自動検証の仕組みを構築した。
| フック | 目的 | 特記事項 |
|---|---|---|
trailing-whitespace | 行末空白削除 | 自動修正あり |
end-of-file-fixer | ファイル末尾改行統一 | 自動修正あり |
check-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.3
Linux(公式インストールスクリプト)
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
tflint --version
バイナリ直接ダウンロード(バージョン固定したい場合)
TFLINT_VERSION="0.50.3"
curl -sLo tflint.zip \
"https://github.com/terraform-linters/tflint/releases/download/v${TFLINT_VERSION}/tflint_linux_amd64.zip"
unzip tflint.zip -d /usr/local/bin/
chmod +x /usr/local/bin/tflint
tflint --version
6-3. .tflint.hcl 設定ファイル
tflint の動作はリポジトリルートに置く .tflint.hcl で制御します。プロジェクトルートで以下のファイルを作成してください。
# .tflint.hcl
plugin "aws" {
enabled = true
version = "0.29.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_required_version" {
enabled = true
}
rule "terraform_required_providers" {
enabled = true
}
rule "terraform_naming_convention" {
enabled = true
resource {
format = "snake_case"
}
variable {
format = "snake_case"
}
}
rule "aws_instance_invalid_type" {
enabled = true
}
rule "aws_s3_bucket_name" {
enabled = false # バケット名のカスタム規則がある場合は無効化
}
主要ルールの説明
| ルール名 | 内容 |
|---|---|
terraform_required_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.......................Passed
6-7. GitHub Actions での tflint 実行
PR時とmainブランチへのプッシュ時に自動でlintを実行するワークフローを作成します。
# .github/workflows/tflint.yml
name: tflint
on:
pull_request:
push:
branches:
- main
jobs:
tflint:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup TFLint
uses: terraform-linters/setup-tflint@v4
with:
tflint_version: v0.50.3
- name: Show version
run: tflint --version
- name: Init TFLint
run: tflint --init
env:
GITHUB_TOKEN: ${{ github.token }} # プラグインDL時のrate limit回避
- name: Run TFLint
run: tflint --recursive --format compact
GITHUB_TOKEN をセットすることで、GitHubからプラグインをダウンロードする際のAPI rate limit(未認証: 60req/h)を回避できます。
ワークフロー追加後の動作確認
git add .github/workflows/tflint.yml .tflint.hcl
git commit -m "ci: add tflint workflow"
git push origin feature/add-security-checks
PRを開くと tflint チェックがCI一覧に表示され、違反があればマージがブロックされます。
6-8. tflint をローカルで日常的に使う
CI だけでなく、コーディング中にも随時実行する習慣をつけると手戻りが減ります。
# plan前の標準チェックフロー
terraform fmt -recursive
tflint --recursive
terraform validate
terraform plan
エイリアスを設定しておくと便利です。
# ~/.zshrc または ~/.bashrc
alias tfcheck='terraform fmt -recursive && tflint --recursive && terraform validate'
これで tfcheck と入力するだけで fmt・lint・validate を一括実行できます。
Section 7: tfsec セットアップ・基本スキャン・実行例
7-1. tfsec とは
tfsec は Terraform コードに特化したセキュリティスキャンツールです。CIS Benchmark や AWS Security Hub のベストプラクティスに基づくルールセットを内蔵しており、設定ミスによるセキュリティリスクをコードレビュー前に検出できます。
tflint との違い
| 観点 | 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 tfsec
tfsec --version
# v1.28.9
Linux(インストールスクリプト)
curl -sSfL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install.sh | bash
tfsec --version
バイナリ直接ダウンロード
TFSEC_VERSION="1.28.9"
curl -sLo tfsec \
"https://github.com/aquasecurity/tfsec/releases/download/v${TFSEC_VERSION}/tfsec-linux-amd64"
chmod +x tfsec
sudo mv tfsec /usr/local/bin/
tfsec --version
7-3. 基本スキャン実行
# カレントディレクトリをスキャン
tfsec .
# JSON形式で出力(CI連携・差分確認向け)
tfsec . --format json
# SARIF形式(GitHub Code Scanning向け)
tfsec . --format sarif
# 特定の重大度以上のみ報告
tfsec . --minimum-severity HIGH
# 特定ディレクトリをスキャン
tfsec ./environments/prod
重大度は CRITICAL > HIGH > MEDIUM > LOW の4段階です。--minimum-severity HIGH を指定するとMEDIUM/LOWの警告を抑制できます。
7-4. tfsec の出力を読む(検出例)
検出例1: S3バケットのセキュリティ設定漏れ
まず問題のある設定を用意します。
# s3_insecure.tf(意図的なセキュリティ問題)
resource "aws_s3_bucket" "example" {
bucket = "my-insecure-bucket"
}
# 暗号化・パブリックアクセスブロック設定なし
スキャン結果:
tfsec .
# Result #1 HIGH Bucket does not have encryption enabled
# ────────────────────────────────────────────────────────────────────
#ID aws-s3-enable-bucket-encryption
# Impact The bucket objects could be read if compromised
# ────────────────────────────────────────────────────────────────────
#Resource: aws_s3_bucket.example
# File:s3_insecure.tf:1-3
# ────────────────────────────────────────────────────────────────────
#
# Result #2 HIGH Bucket does not have public access block enabled
# ────────────────────────────────────────────────────────────────────
#ID aws-s3-block-public-acls
# ...
修正後のコード:
# s3_secure.tf(修正後)
resource "aws_s3_bucket" "example" {
bucket = "my-secure-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
bucket = aws_s3_bucket.example.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls= true
restrict_public_buckets = true
}
検出例2: セキュリティグループの全開放
# sg_insecure.tf(意図的な問題)
resource "aws_security_group_rule" "http" {
type = "ingress"
from_port= 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.web.id
}
tfsec .
# Result #1 MEDIUM Security group rule allows ingress from public internet.
#ID aws-ec2-no-public-ingress-sgr
ALBからのトラフィックのみ許可する場合は cidr_blocks を削除し、代わりにALBのセキュリティグループを source_security_group_id で指定します。
# sg_secure.tf(修正後)
resource "aws_security_group_rule" "http" {
type= "ingress"
from_port = 80
to_port= 80
protocol = "tcp"
source_security_group_id = aws_security_group.alb.id # ALBのSGのみ許可
security_group_id = aws_security_group.web.id
}
7-5. .tfsec/config.yml でルール除外
プロジェクトの要件上、特定のルールを適用しない場合は設定ファイルで除外します。
mkdir -p .tfsec
# .tfsec/config.yml
exclude:
- aws-s3-enable-bucket-logging # ログ不要なバケットに適用しない
severity_overrides:
aws-s3-block-public-acls: CRITICAL # デフォルトより厳しく設定
minimum_severity: LOW# 全件報告(デフォルト)
特定のリソースだけ個別に除外したい場合は、Terraformコード内にコメントで #tfsec:ignore を記述します。
resource "aws_s3_bucket" "public_assets" {
bucket = "my-public-static-assets"
#tfsec:ignore:aws-s3-block-public-acls
#tfsec:ignore:aws-s3-block-public-policy
}
コメント方式はリソース単位の局所的な除外に使い、プロジェクト全体の除外は config.yml で管理するのがベストプラクティスです。
7-6. tfsec を pre-commit に組み込む
# .pre-commit-config.yaml(tfsec 追記分)
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.88.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
args:
- --args=--init
- --args=--recursive
- id: terraform_tfsec
args:
- --args=--minimum-severity=HIGH # HIGH以上のみでブロック
動作確認:
pre-commit run terraform_tfsec --all-files
# terraform_tfsec..............Passed
HIGH以上の問題があればコミットがブロックされ、修正を促します。
7-7. GitHub Actions での tfsec 実行
tfsec の公式 GitHub Action を使ったワークフローです。
# .github/workflows/tfsec.yml
name: tfsec
on:
pull_request:
push:
branches:
- main
jobs:
tfsec:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write# SARIF アップロードに必要
steps:
- uses: actions/checkout@v4
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.3
with:
minimum_severity: HIGH
format: sarif
sarif_file: tfsec.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: tfsec.sarif
SARIF形式でアップロードするメリット
SARIF(Static Analysis Results Interchange Format)でアップロードすると、GitHubの「Security」タブ→「Code scanning」に結果が集約されます。PRの差分ビューに検出箇所がインライン表示されるため、レビュアーが修正箇所を一目で把握できます。
permissions: security-events: write は SARIF アップロードに必須です。忘れずに設定してください。
7-8. tflint と tfsec を組み合わせた統合ワークフロー
2つのツールを1つのワークフローにまとめると管理が楽になります。
# .github/workflows/terraform-checks.yml
name: Terraform Checks
on:
pull_request:
push:
branches:
- main
jobs:
lint:
name: tflint
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: terraform-linters/setup-tflint@v4
with:
tflint_version: v0.50.3
- name: Init TFLint
run: tflint --init
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Run TFLint
run: tflint --recursive --format compact
security:
name: tfsec
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.3
with:
minimum_severity: HIGH
format: sarif
sarif_file: tfsec.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: tfsec.sarif
lint ジョブと security ジョブは独立して並列実行されるため、フィードバックが早く返ってきます。両ジョブをPRのブランチ保護ルール(Required status checks)に追加することで、どちらかが失敗した場合もマージをブロックできます。
7-9. ローカルでの統合チェックフロー
# Terraform 品質チェック一式
terraform fmt -recursive# フォーマット
tflint --recursive# 構文・API整合性
tfsec . --minimum-severity HIGH # セキュリティ
terraform validate# 設定検証
terraform plan # 差分確認
エイリアスで一括実行:
# ~/.zshrc
alias tfqa='terraform fmt -recursive && tflint --recursive && tfsec . --minimum-severity HIGH && terraform validate'
tfqa(Terraform Quality Assurance)を習慣化することで、CI で初めてエラーを検知するのではなく、ローカル開発段階で問題を潰せます。
7-10. まとめ:tflint + tfsec の位置づけ
本セクションで設定したツールチェーンをまとめます。
| フェーズ | ツール | タイミング |
|---|---|---|
| コーディング | terraform fmt | ファイル保存時(エディタ連携) |
| コミット前 | tflint + 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弾)
次の記事(最終回) → 第5弾: Terraform実践 — モジュール化・tfstate・OIDC CI/CD
本記事が役に立ったら、シリーズの他の記事もあわせてご覧ください。