秘密情報を Git に入れない

GitBeginner
オンラインで実践に進む

はじめに

自分のマシン上では役立つファイルでも、ソースコードのコミットに含めるのは適切でないことがあります。この新しい DeskLite リポジトリには、小さな業務変更と、架空の認証情報を含む非公開設定があります。非公開ファイルは誤ってステージングされていますが、一度もコミットされていません。

このラボでは、Git の作業領域、ステージング領域、コミットのスナップショットを学び、非公開設定と提供用の設定例を分離して、用意されている認証情報スキャナーを使います。Git のアカウント、リモートリポジトリ、Git の事前知識は必要ありません。

作業領域とステージング領域を確認する

このステップでは、Git がどのファイルを保存しようとしているかを確認します。

Git は、選んだバージョンのファイルをリポジトリに記録します。作業ツリーには、編集するファイルが含まれます。ステージング領域には、次にコミットする予定のスナップショットが置かれます。コミットは、そのステージング済みのスナップショットをローカルの履歴に保存します。自動的に何かをアップロードするわけではありません。

cd /home/labex/project/desklite-git

git status は、これらの領域を比較します。

git status

用意されているターミナルではファイルの区分に色が付く場合がありますが、重要なのは表示される文言です。2 つの異なる区分を探してください。private.env はコミットされる変更の下に表示され、その内容はすでにステージングされています。service/app.py はコミットに向けてステージングされていない変更の下に表示され、業務変更はまだ作業ツリーだけにあります。ブランチ名は異なる場合がありますが、この課題には影響しません。

git ls-files は、最初のコミットに向けてステージングした新規ファイルも含め、追跡対象のパスを一覧表示します。

git ls-files

非公開ファイルは、まだコミットされていなくても表示されます。漏えいを防ぐべきタイミングは今です。初期のコミット済み履歴には秘密情報がありません。まだコミットせず、まず何が含まれるかを確認してください。

2 種類の変更を比較する

このステップでは、業務変更とステージング済みの非公開設定を比較します。

git --no-pager diff は、ステージングされていない変更を表示します。グローバルオプション --no-pager により出力が直接ターミナルに表示されるため、別のビューアーを閉じる必要がありません。パスを指定して、業務ファイルだけに絞ります。

git --no-pager diff service/app.py

- で始まる行は古い内容、+ で始まる行は新しい内容を示します。新しいプログラムは DeskLite tickets ready を出力します。この意図した業務変更は維持してください。

一方、git --no-pager diff --cached はステージング済みの内容を表示します。--cached オプションはステージング領域を選択します。

git --no-pager diff --cached

private.env と、その中の SERVICE_NAME および API_TOKEN キーが表示されるはずです。トークンは架空のもので、このコース以外では使えません。それでも、非公開に保つべき値の一例です。

ステージングされたスナップショットは、現在ディスク上にあるファイルと異なる場合があります。そのため、作業ツリーだけを確認すると、すでにコミット用に準備された内容を見落とすことがあります。コミット前には、両方の変更を確認してください。

このリポジトリには、ローカル用のサンプルの作成者情報がすでに設定されています。個人情報の入力や、Git と外部サービスの接続は行いません。

マスクされた認証情報の検出結果を読み取る

このステップでは、用意されているスキャナーを非公開ファイルに対して実行し、検出結果を解釈します。

Gitleaks は、認証情報に関連するテキストパターンを検出します。コース用ランチャー course-secrets は、合成の labex_ トークン形式を対象とする 1 つの固定ローカルルールで、Gitleaks 8.24.2 を実行します。一致した値はマスクされ、ネットワークリクエストは行いません。この限定的なコース用ルールは、本番環境の包括的な秘密情報検出方針ではありません。

ランチャーにファイルまたはディレクトリを指定して、現在の内容をスキャンします。

course-secrets private.env

desklite-practice-token という識別子、ファイル名と行番号、マスクされた秘密情報を含む検出結果が表示されるはずです。認証情報が見つかったため、このコマンドは意図的にステータス 1 で終了します。検出があったことと、ツールが実行できなかったことは異なります。

このコマンドは、Git の追跡対象かどうかにかかわらず、現在のファイルをスキャンします。後で紹介する履歴スキャンは、コミット済みの変更を調べます。履歴に問題がなくても、この未コミットのファイルを安全にステージングできることにはなりません。

ここでは、非公開の値はステージング領域と作業ツリーの内容にしか存在しません。実際の認証情報がすでに漏えいしていた場合、新しいコミットから除くだけでは失効せず、古いコピーも消えません。その所有者による失効や置き換えが必要になります。この演習では、履歴の書き換えは扱いません。

非公開設定と設定例を分離する

このステップでは、非公開設定を Git の追跡対象から外し、対象を限定した無視ルールを追加して、実用的な公開用設定例を作成します。

git rm --cached は、ローカルファイルを残したまま、ステージング領域と追跡対象からパスを取り除きます。ここでは、新しい非公開ファイルを誤ってステージングした操作を取り消します。

git rm --cached private.env

追跡されていないパスのうち何を除外するかを Git に伝える .gitignore を開きます。

nano .gitignore

次の 1 つのルールを入力し、Ctrl+O、Enter、Ctrl+X で保存して終了します。

/private.env

先頭の / は、このリポジトリのルートを基準にルールを適用します。service/ は提供すべきファイルを含むため、無視しないでください。無視ルールは、すでに追跡されているファイルや古いコミットを自動的に取り除きません。そのため、先ほどのコマンドが必要でした。

同じ設定キーを使って公開用設定例を作成します。

nano service/config.example.env

次の内容を保存します。

SERVICE_NAME=DeskLite
API_TOKEN=REPLACE_ME

この設定例は、使える認証情報を渡すことなく、今後の運用担当者に必要な設定を伝えます。元の非公開ファイルはローカルに残してください。

ls private.env
git status

非公開パスは、ステージング済みとしても未追跡としても表示されなくなるはずです。.gitignore と公開用設定例は未追跡になり、意図的にステージングする準備ができています。既存の業務変更は、まだステージングされていません。

提供用ファイルだけをステージングする

このステップでは、次のコミットに含めるファイルを正確に準備します。

git add は、指定したパスの現在の内容をステージング領域にコピーします。提供する各ファイルを明示的に指定します。

git add service/app.py service/config.example.env .gitignore

ファイルの区分と、ステージング済みの内容の両方を確認します。

git status
git --no-pager diff --cached

意図した業務変更、対象を限定した無視ルール、安全な設定例だけが含まれるはずです。非公開のトークンがあってはいけません。git add の後にファイルを再編集した場合は、コミット前に更新後の内容をステージングする必要があります。Git は、以前ステージングしたスナップショットを自動的には置き換えません。

追跡対象のパスをもう一度一覧表示します。

git ls-files

3 つの提供対象パスが表示され、private.env は表示されないはずです。この確認で分かることは ls とは異なります。ローカルの非公開ファイルは存在していますが、Git の追跡対象からは外れています。

次のステップでは、提供用ディレクトリをスキャンします。意図的に限定した提供範囲をスキャンしても、マシン上の他のすべてのファイルに秘密情報がないことまでは確認できません。

提供内容をスキャンする

このステップでは、非公開のローカル設定を残しながら、提供するソースディレクトリを確認します。

同じ固定ルールのスキャナーを、service ディレクトリに対して実行します。

course-secrets service

no leaks found と表示され、正常に終了するはずです。スキャナーは実際のソースと設定例の内容を処理します。手作業で編集したレポートには依存しません。

非公開設定は、この提供用ディレクトリの外にあるリポジトリのルートに置かれています。その非公開ファイルを再びスキャンすれば、引き続きトークンが報告されます。それは想定どおりです。閲覧者を分離したのであり、機密データが存在しなくなったと主張しているわけではありません。

ステージング済みのスナップショットをもう一度確認します。

git --no-pager diff --cached

ディレクトリのスキャンは現在のファイルを確認し、diff は Git が保存する内容を確認します。どちらも意図した提供内容と一致する必要があります。検出結果がないスキャンで確認できるのは、設定したルールと対象範囲に関することだけです。あらゆる秘密情報やセキュリティ上の欠陥が存在しないことまでは証明できません。

これで、確認済みのステージング済みスナップショットができました。次のステップで、ローカルコミットとして保存します。

ローカルコミットを保存する

このステップでは、確認済みのステージング済みスナップショットをローカル履歴に保存します。

git commit はステージング領域を記録します。-m オプションは、変更を説明する短いメッセージを指定します。引用符で囲むことで、複数の単語をまとめています。

git commit -m "Deliver ticket message and safe configuration example"

変更された 3 つのパスを含むコミットの概要が表示されるはずです。コミット ID やファイルの統計情報は異なる場合があるので、含まれた内容に注目してください。

git status

作業ツリーに変更が残っていないはずです。無視されている非公開ファイルは、git status に表示されなくても存在しています。

コミットはローカルの操作です。リモートサービスへのプッシュ、アップロード、共有は何も行われていません。個人用アカウントがなくても、安全に引き渡す準備を練習するにはこれで十分です。

コミットするものがないと Git に表示された場合は、git status、git ls-files、ステージング済みの diff を前のステップと比較してください。想定外の状態を解消するために、リポジトリや既存の履歴を削除しないでください。

履歴と業務変更を確認する

このステップでは、コミット済みの履歴をスキャンし、維持した小さなプログラムを実行します。

ランチャーの --history オプションは、Gitleaks による Git 履歴スキャンを選択します。最後の . は、このリポジトリを意味します。

course-secrets --history .

no leaks found と表示されるはずです。安全な初期コミットと、新しい提供用コミットの両方が固定ルールに合格する必要があります。先ほどのファイルスキャンに成功しただけでは、この履歴の結果は証明できません。

python3 は、提供されている Python プログラムを実行します。このラボでは、Python の構文を編集したり理解したりする必要はありません。

python3 service/app.py

次のように表示されるはずです。

DeskLite tickets ready

ローカルの非公開ファイルが、Git の追跡対象から外れたままであることを確認します。

ls private.env
git ls-files

実用的な提供用ファイル、安全な設定例、練習用認証情報を含まないローカルコミットができました。非公開ファイルは削除されておらず、業務変更も引き続き機能しています。スキャンレポートのコピーや調査ファイルは不要です。

まとめ

ローカルの非公開設定とソースの提供物を分離し、コミット前にステージング内容を確認しました。また、検出値をマスクする固定ルールの認証情報スキャンで、現在の提供用ファイルとローカルの Git 履歴を確認しました。業務上の変更と便利な公開用設定例を残しながら、非公開設定をコミットの外に保ちました。