ログ、ビルド成果物、Git履歴からシークレットが漏洩する仕組み

Vaultから安全にシークレットを取得するCI/CDパイプラインを設定し終えたとします。パイプラインは正常に動作し、アプリケーションはデプロイされ、すべてがグリーンに見えます。ところが1週間後、チームの誰かが3日前のパイプラインログにデータベースパスワードが出力されているのを発見します。誰がそれを見たのか、コピーされたのかは誰も知りません。パスワードは事実上公開されたも同然です。

このシナリオは、チームが想定するよりも頻繁に発生します。Vault自体は安全で、パイプラインとの統合も機能しています。しかし、シークレットは必ずしもストレージシステムから漏洩するわけではありません。ログ、ビルド成果物、Git履歴といった、意図せずシークレットが紛れ込む場所から漏洩するのです。

パイプラインログが漏洩ポイントになる理由

パイプラインログは、シークレットが最初に漏洩する場所です。開発中やデバッグ中に、チームは何が起こっているかを確認するために環境変数を出力することがよくあります。アプリケーションがデータベースに接続できない場合、誰かが接続文字列全体(パスワードを含む)をダンプする簡単なprint文を追加します。そのログエントリはCI/CDサーバーに保存され、ログアクセス権を持つ誰でも参照できるようになります。

問題は、ほとんどの環境でログが自動的にローテーションされたりクリーンアップされたりしないことです。今日ログに現れたシークレットは、無期限にそこに残り続けます。後からチームに加わったメンバー、パイプラインを監査する人、ログの読み取り権限を持つ人なら誰でもそのシークレットを見ることができます。一度シークレットがログに入り込むと、誰がそれを見るかを制御できなくなります。

例えば、デバッグ用に環境変数を出力するデプロイスクリプトを考えてみましょう。

#!/bin/bash
# デバッグ: 接続情報を出力
echo "データベースに接続中..."
echo "DB_PASSWORD=$DB_PASSWORD"  # 誤ってシークレットを出力
# 実際の接続コマンド
psql "host=$DB_HOST user=$DB_USER password=$DB_PASSWORD dbname=$DB_NAME"

パイプラインログには次のように表示されます。

データベースに接続中...
DB_PASSWORD=supersecret123

この1行がCI/CDサーバーのログストレージに保存され、ログアクセス権を持つ誰でも参照できるようになります。

ログは共有されることもあります。開発者が助けを求めてログのスニペットをチャットチャンネルに貼り付けると、そのスニペットにパスワードが含まれている場合があります。するとシークレットはチャット履歴にも残ります。メッセージを削除しても、キャッシュされたコピーが残っている可能性があります。

ビルド成果物は警告なしにシークレットを運ぶ

ビルド成果物はあまり目立たない漏洩ポイントですが、同様に危険です。JAR、Dockerイメージ、ZIPファイルをビルドするとき、ビルドプロセスはソースディレクトリからファイルを成果物にコピーします。シークレットを含む設定ファイルが、誰にも気づかれずに成果物の中に紛れ込む可能性があります。

よくある例は、ローカル開発で使用される.envファイルです。このファイルはプロジェクトディレクトリにあり、ビルドスクリプトがすべてを出力フォルダにコピーします。.envファイルがDockerイメージやJARの中に含まれてしまいます。成果物はレジストリにプッシュされます。そのイメージをプルしたり、その成果物をダウンロードした人は誰でも、設定ファイルを抽出してシークレットを読むことができます。

危険なのは、ソースファイルを修正しても成果物は修正されないことです。ソースから.envファイルを削除してリビルドすれば、新しい成果物はクリーンになります。しかし、レジストリ内の古い成果物には依然としてシークレットが含まれています。古い成果物を明示的に削除しない限り、タグやダイジェストを知っている人なら誰でもシークレットにアクセスできます。

Git履歴はほぼ完全に消去不可能

Git履歴は最も危険な漏洩ポイントです。なぜなら、永続性を前提に設計されているからです。シークレットを含むファイルをコミットすると、そのシークレットはコミットに記録されます。後続のコミットでファイルを削除しても、シークレットはコミット履歴に残り続けます。完全な履歴を持つリポジトリをクローンした人は誰でも、古いコミットをチェックアウトしてシークレットを読むことができます。

多くのチームは、数ヶ月後または数年後にこれを発見します。誰かがコードベース内の設定ファイルを検索し、本番データベースの認証情報を含む古いコミットを見つけます。シークレットは数ヶ月間露出していました。チームはその間に誰がリポジトリをクローンしたのか、シークレットが抽出されたのかどうかを知る術がありません。

フォースプッシュで履歴を書き換えればリモートリポジトリからシークレットを削除できますが、既存のクローンには効果がありません。すでにリポジトリをクローンした人は、ローカル履歴にシークレットが残っています。ローカルコピーを削除するよう強制することはできません。唯一安全な対応は、シークレットを即座にローテーションすることです。

シークレット漏洩を自動的に防ぐ方法

シークレット漏洩を防ぐには、複数の防御層が必要です。単一のツールやプラクティスですべてを捕捉できるわけではありません。目標は、シークレットがログ、成果物、Git履歴に到達する前に、早期に捕捉することです。

以下の図は、各漏洩経路とその主要な防止策、および最終的な対応をマッピングしたものです。

flowchart TD A[ソースコード内のシークレット] --> B{漏洩経路} B --> C[パイプラインログ] B --> D[ビルド成果物] B --> E[Git履歴] C --> F[パイプラインスキャン] D --> G[無視ファイル + パイプラインスキャン] E --> H[プリコミットスキャン] F --> I[即座にローテーション] G --> I H --> I I[シークレットをローテーションして古いものを無効化]

プリコミットスキャン

プリコミットフックとしてシークレットスキャナーをインストールします。git-secrets、truffleHog、Gitleaksなどのツールは、ステージングされたファイルをスキャンして、APIキー、トークン、パスワードなどのパターンを検出します。スキャナーが疑わしいパターンを検出すると、コミットはブロックされ、開発者には何が見つかったかを説明するメッセージが表示されます。

プリコミットスキャンは、シークレットがGit履歴に入り込む前に捕捉します。これは漏洩を止める最も効果的なポイントです。なぜなら、シークレットがリポジトリに到達しないからです。開発者はシークレットを削除し、ファイルを.gitignoreに追加して、再度コミットできます。

パイプラインスキャン

プリコミットフックはバイパスされる可能性があります。開発者はフックをスキップしたり、正しくインストールしなかったり、フックが設定されていないマシンで作業したりすることがあります。そのため、パイプラインに第二のスキャン層が必要です。

多くのCI/CDプラットフォームは、ログ出力やビルド成果物からシークレットをチェックする組み込みのスキャン機能やプラグインを提供しています。GitHub Actionsにはシークレットスキャンがあります。GitLab CIはSASTツールにシークレット検出を含んでいます。Jenkinsには認証情報スキャン用のプラグインがあります。スキャナーがログ行や成果物ファイル内でシークレットを検出すると、パイプラインを失敗させるか、警告を送信できます。

パイプラインスキャンは、プリコミットフックをすり抜けたシークレットを捕捉します。また、ビルドステップ中に誤って出力された環境変数など、他の手段でパイプラインに入り込んだシークレットも捕捉します。

無視ファイルを厳格に使用する

.gitignore.dockerignoreはシンプルですが効果的なツールです。シークレットを含む設定ファイルは、両方のファイルにリストアップして、GitリポジトリやDockerビルドコンテキストに決して入らないようにします。ただし、無視ファイルは完全な解決策ではありません。開発者が更新を忘れたり、強制追加で誤って上書きしたりする可能性があります。

無視ファイルは基本防御として扱い、主要な防御とはしないでください。自動スキャンと組み合わせて、無視ファイルが機能しなかった場合を捕捉します。

漏洩が検出されたら即座にローテーションする

シークレットがGit履歴に漏洩した場合、履歴をクリーンアップしようとしないでください。コミットを削除したり履歴を書き換えたりしても、既存のクローンにはシークレットが残っているため不十分です。唯一安全なアクションは、シークレットを即座にローテーションすることです。

新しいパスワード、トークン、またはキーを生成します。古いシークレットを使用しているすべてのシステムを更新します。古いシークレットを無効化して、認証に使用できなくします。そして、新しいシークレットがVaultに保存され、パイプラインを通じてアクセスされ、どこにもハードコードされていないことを確認します。

シークレット漏洩防止のための実践的チェックリスト

  • すべてのリポジトリにプリコミットフックとしてシークレットスキャナーをインストールする。
  • CI/CDパイプラインでログと成果物に対するシークレットスキャンを有効にする。
  • シークレットを含む設定ファイルを.gitignore.dockerignoreに追加する。
  • パイプラインログを定期的にレビューして、偶発的なシークレット露出がないか確認する。
  • 露出したシークレットは、たとえ軽微な露出だと思ってもローテーションする。

まとめ

シークレット管理は、Vaultをパイプラインに統合した時点で終わりではありません。Vaultは保存中のシークレットを安全に保ちますが、パイプラインはログ、成果物、Git履歴を通じてシークレットを漏洩させる可能性があります。漏洩を防ぐ唯一の方法は、コミット前、ビルド中、デプロイ後のすべての段階でスキャンすることです。そして漏洩が発生した場合、証拠を消そうとしないでください。シークレットをローテーションしてください。それが実際に問題を修正する唯一のアクションです。