Git管理された.envを安全な構成へ戻す手順
.env を .gitignore に追加しても、すでに追跡されたファイルはGit管理から外れない。さらに、現在のファイルを消しても過去のコミットに値が残る。
Go製APIサンプルで .env がコミットされていたため、追跡対象、設定例、Docker Compose、README、回帰テストを一つの契約として修正した。
この記事で解決すること
対象は、.env を誤ってコミットし、今後の再混入も防ぎたい人だ。次の順序で対応する。
- 漏えいした可能性のある値を無効化する
.envを追跡対象から外す- 実値を持たない
.env.exampleを作る - アプリとDocker Composeの参照方法を揃える
- テストで再混入を検出する
最初に値をローテーションする
公開履歴へ入った秘密情報は、削除後も取得済みと考える。JWT署名鍵、DBパスワード、APIキーなどは、Git操作より先に利用先で無効化・再発行する。
履歴修正は露出を減らす処置であり、値を再び安全にする処置ではない。この二つを同じ作業として扱わないことが重要だ。
実装手順
追跡状態を確認してから、安全な設定例へ置き換える。
まず、Gitが現在どの設定ファイルを追跡しているか確認する。
git ls-files .env .env.example.env が表示される場合、.gitignore の追加だけでは不十分だ。作業用ファイルは手元に残し、インデックスから外す。
git rm --cached .envそのうえで .gitignore に .env を追加する。リポジトリにはキー名だけを示す .env.example を置き、値は明確なプレースホルダーにする。
POSTGRES_USER=change-me
POSTGRES_PASSWORD=change-me
SECRET=replace-with-a-long-random-value
GO_ENV=devyour-secret-key のようなありがちな固定値もサンプルに残さない。コピー後に変更されず、本番へ流用される可能性があるためだ。
設定の参照元を揃える
アプリだけを直しても、docker-compose.yml に値が直書きされていれば目的を達成できない。PostgreSQLのユーザー名、パスワード、DB名も環境変数を参照する形へ揃えた。
利用者向けのREADMEには、次の初期化手順と、変更が必須な値を明記する。
cp .env.example .env
docker compose up -d
go test ./...設定例、実行環境、説明が同じキー名を使うことで、秘密情報を除去した結果として開発環境が起動不能になる事故を防げる。
検証結果
人の注意だけでは再発するため、GoのテストからGitの追跡状態を検査するようにした。
func TestEnvironmentSecretsAreNotTracked(t *testing.T) {
cmd := exec.Command("git", "ls-files", ".env", ".env.example")
out, err := cmd.Output()
if err != nil {
t.Fatalf("git ls-files failed: %v", err)
}
for _, path := range strings.Fields(string(out)) {
if path == ".env" {
t.Fatal(".env must not be tracked; commit .env.example instead")
}
}
}別のテストでは .env.example に必須キーがあることと、既知の危険なサンプル値がないことを確認する。今回の再検証では全パッケージが成功した。
go test ./...
# ok go-sample-api
# controller、db、modelなども失敗なしこのテストは「Gitに追跡された .env」を防ぐ。未追跡ファイルの中身や、別名で保存された鍵までは検出しないため、秘密情報スキャナとの併用が必要だ。
履歴を調査する
現在のツリーを直した後、過去のコミットに .env が存在した範囲を確認する。
git log --all -- .env公開リポジトリから履歴を除去する場合は、共同作業者への影響を確認してから git filter-repo などを使う。履歴を書き換えると既存cloneとの整合が崩れるため、無断でforce pushしてはいけない。
採用理由と代替案
.env.example を採用したのは、必要なキーをコード外で一覧化しつつ、実値を配布しないためだ。ただし本番の秘密情報管理にはならない。本番ではホスティング基盤のSecret機能や専用のSecret Managerを使う。
回帰テストには既存の go test を使った。追加ツールなしで必ず実行できる利点がある。一方、値のパターン検出や履歴全体の走査は得意ではないため、CIではgitleaksなどを追加する余地がある。
完了条件
- 漏えいした可能性のある値をローテーションした
git ls-files .envが何も返さない.env.exampleはキー名と安全なプレースホルダーだけを含む- アプリ、Docker Compose、READMEでキー名が一致する
go test ./...と秘密情報スキャンが成功する- 履歴修正の要否と共同作業者への影響を確認した
ファイルを消すだけでは、秘密情報対応は完了しない。値の失効、追跡解除、開発手順の再構築、機械的な再発防止までを一つの修正として扱う必要がある。