TT Lab
はじめる
学ぶ 学習パス コース

CI/CDパイプライン

秘密情報はログとレイヤーに残る

TT Labで続きを見る

一言でいうと

パイプラインで秘密情報が漏れる場所は、ほぼ決まっています。ビルドログ、イメージのレイヤーと設定の履歴、リポジトリのコミット履歴、アーティファクトの4か所です。そして、一度漏れた秘密情報への対応は、消すことではなく、取り消して新しく発行することです。

なぜ必要なのか

秘密情報の扱いでの失敗は、たいてい悪意ではなく、便宜から生まれます。デバッグのために環境変数をまるごと出力し、ビルド引数でトークンを渡すのが最も簡単で、設定ファイルに値を入れたままコミットしました。どれも、1人が数分を節約するためにしたことなのに、結果は、その値を読める人の範囲がまるごと広がることです。

4つの場所を1つずつ見ていきます。

マスキングは対策ではなく、最後の防衛線

CIプラットフォームは、ログに含まれる既知の秘密情報の値を隠してくれます。助けにはなりますが、対策としてはいけません。理由は単純です。マスキングが隠せるのは、知っている文字列とまったく同じものだけだからです。値が少しでも変形されると、そのまま見えます。

そのため、順序は次のとおりです。まず出力しないようにし、マスキングは、ミスを減らしてくれる最後の安全網として置きます。GitHubは、自身のシークレットではない機密性のある値も::add-mask::で登録するよう勧めていますが、これも同じ限界をそのまま抱えています。そして、フォークしたリポジトリから起動されたワークフローには、GITHUB_TOKENを除いて、シークレットが渡されません。これはマスキングよりもはるかに強い保護であり、境界を分けるほうが、隠すほうよりもよいという原則を、そのまま示しています。

ビルド引数とランタイムのシークレット

Docker公式ドキュメントの文章が、結論をそのまま書いています。「ビルド引数と環境変数は、ビルドにシークレットを渡すのに適切ではありません。最終イメージに残るからです。」値を書いて削除しても、イメージが含んでいるビルドの記録に残ります。

代わりに使うのが、ビルド時のシークレットマウントです。

# 값은 그 RUN 명령이 도는 동안에만 존재하고, 레이어에 남지 않는다
RUN --mount=type=secret,id=npmtoken \
    NPM_TOKEN="$(cat /run/secrets/npmtoken)" npm ci

既定のマウント先は/run/secrets/<id>(プレースホルダーはidです)で、そのコマンドが終わると消え、イメージのレイヤーに保存されません。

ランタイムのシークレットは、また別の話です。実行時に必要な値は、イメージではなく、実行環境が渡します。環境変数で渡すと便利ですが、プロセス一覧、子プロセス、エラーレポート、ダンプに一緒に載ってしまいます。ファイルで渡すほうがたいてい適切で、そのときは2つを決めます。権限(そのプロセスだけが読めるように絞り、ディレクトリの権限まで確認する)と、有効期間(いつ消えるのか、ローテーションしたら再び読み込むのか)です。

有効期間が短いほうが、構造的によい

有効期間が長い認証情報は、「いつ漏れたのかわからない」という問題を抱えています。数か月前にログへ漏れたトークンが、今も有効です。有効期間が短い認証情報は、漏れた値の使い道に有効期限をかけます。漏洩を防げない代わりに、被害が及ぶ期間を数分に縮めます。

同じ理由で、権限の範囲も狭く与えます。パイプラインが読み取りだけを必要とするなら、読み取りだけを与えます。これは漏洩を防ぐ措置ではなく、漏洩したときの被害を決める措置です。

漏洩のあとの対応は、取り消しと再発行

ここが最もよく間違えられます。値がコミットに入ってしまったとき、人は履歴を書き換えて消そうとします。履歴の修正は、すでにクローンされたコピー、フォーク、キャッシュ、ログ、バックアップには何の影響も与えません。その間にその値を読んだ人がいたかどうかがわからないことが核心です。

そのため、順序はいつも同じです。まず、その認証情報を取り消します。新しい値を発行します。使っている場所を切り替えます。そのあとではじめて、履歴の整理と再発防止の話をします。履歴から消す作業は最初のステップではなく、たいてい最も重要度の低いステップです。

現場での姿

参考

次のラボですること

秘密情報が漏れる4つの場所を、シェルとgit、skopeoで1つずつ再現して防ぎます。まず、環境変数を出力するステップを入れて、ログに値が残ることを確認し、マスキングを模したフィルターを付けたうえで、base64に変換して出力し、そのフィルターをそのまま通り抜ける反例を作ります。続いて、/opt/imagesのoci-archiveをskopeo inspect --rawで開いて、イメージがファイルシステムだけでなく、設定と履歴も一緒に含んでいることを目で確認します。コミット履歴については、値をコミットして削除したあと、古いコミットからそのまま取り出してみることで扱います。最後に、秘密情報のファイルの権限と有効期間を検査するスクリプトと、漏洩への対応の手順を取り消しから書いたチェックリストを作ります。