環境変数とファイルマウント、何が違うのか
このラボは本物のVM上で動きます
この箱はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが
別に動き、systemdが実際にサービスを管理し、dockerは偽物ではなく
本物のDockerエンジンです。docker runで起動したコンテナは、実際にプロセスに
なり、docker execもdocker logsもそのまま動作します。
以前は、このラボはPodの中で動いていました。カーネルの権限をすべて下ろした箱だったため、 コンテナを起動するステップが塞がれており、そのためイメージのアーカイブを自分で展開して みるという回り道で学んでいました。もう回り道は必要ありません。
知っておくべきことが2つあります。
- 最初の起動に1分ほどかかります。VMが起動してDockerをインストールするためです。Podのラボ(通常は40秒)より遅くなります。
- ブラウザープレビューはありません。VMに入ってくる接続は、採点ポート
1つだけが開いています。Webサーバーを起動したなら、VMの中で
curlで確認してください。
目標
同じシークレットを、環境変数とファイルのマウントの2つの方式で渡してみて、docker inspectへの
露出の有無と、再起動なしでのローテーションの可否を自分で測り、比較表にまとめます。
最後に、簡単なシークレット検知のスクリプトを書きます。
なぜ重要なのか
環境変数は、保管場所ではなく受け渡しの方式です。/proc/<pid>/environは、プロセスが
生きている間は読み取れ続け、コンテナの環境変数は、デーモンにアクセスできる誰にでも
docker inspectでそのまま見えます。そのため、「.envを読み込んで削除したから大丈夫」という
安心には、根拠がありません。さらに重要なのは、ローテーションです。環境変数は、プロセスの開始
時点で固定されるため、値を変えるには必ず再起動が必要で、ファイルのマウントは、同じ
inodeを上書きすれば、再起動なしで新しい値が見えます。この違いが、インシデント対応の時間を
分けます。認証情報が公開されたと仮定したとき、取り消して交換し、サービスを正常化する
のに何分かかるかが、そのチームのシークレット管理のレベルそのものです。
ステップ
/root/sec3ディレクトリを作成し、/root/sec3/app.envにAPI_KEY=labhub-env-key-v1の1行を書いたあと、ファイルの権限を600に変更します。alpine:3.20でsec3-envコンテナを、--env-file /root/sec3/app.envを使ってバックグラウンド実行します。コンテナのAPI_KEYが、ファイルの値と同じである必要があります。docker inspect sec3-envで読み取ったAPI_KEYの値を、そのまま/root/sec3/leak-proof.txtに保存します。/root/sec3/secret.txtにlabhub-file-key-v1を書き、そのファイルをsec3-fileコンテナの/run/secrets/api_keyのパスに読み取り専用でバインドマウントして、バックグラウンド実行します。マウント元は、正確に/root/sec3/secret.txtである必要があります。sec3-fileコンテナの環境変数に、API_KEYも、シークレットの値そのものも、入っていない必要があります。- コンテナを再起動しないまま、
/root/sec3/secret.txtの内容をlabhub-rotated-v2で上書きします。sec3-fileは新しい値を見ることになり、sec3-envの環境変数の値は、古い値のままである必要があります。そして/root/sec3/rotate.mdに、どちらの方式が再起動を必要とするかの結論を書きます(재시작またはrestartという語が入っている必要があります。前者は、韓国語で「再起動」を意味する語です)。 - 実行権限のある
/root/sec3/find-secrets.shを作成します。第1引数で受け取ったディレクトリを調べて、AKIAで始まるアクセスキーやPRIVATE KEYブロックがあれば、0以外の終了コードで終了しつつ、どのファイルで見つけたかパスを出力し、何もなければ、終了コード0で終了する必要があります。 /root/sec3/secrets.mdに、環境変数方式とファイルのマウント方式を比較する内容を書き、次の4行を正確にこの形式で含めます。env_visible_in_inspect=yesmount_visible_in_inspect=noenv_needs_restart=yesmount_needs_restart=no
参考
- ファイルの権限の変更は
chmod 600 <파일>、確認はstat -c %a <파일>です(プレースホルダーはファイルです)。 - ファイル1つを読み取り専用で付けるには、
-v /호스트/경로:/컨테이너/경로:roの形式を使います(プレースホルダーはホストのパスとコンテナのパスです)。 - コンテナの環境変数の確認は、
docker inspect <이름> | jq -r '.[0].Config.Env[]'です(プレースホルダーはコンテナ名です)。 - ステップ6でファイルを上書きするときは、
printf '%s' labhub-rotated-v2 > /root/sec3/secret.txtのように、その場での上書きを使ってください。 - よくあるミス1: ステップ6で
sed -iや、ファイルを削除してから作り直す方式を使うと、inodeが変わって、コンテナが古い値を見続けます。 - よくあるミス2: ステップ4で
sec3-fileに-e API_KEY=...をあわせて指定すると、ステップ5が失敗します。ファイルのマウントに変えたなら、環境変数は外してください。 - よくあるミス3: ステップ6で
sec3-envを作り直したり、app.envをあわせて修正したりすると、対照群がなくなって失敗します。sec3-envは、そのままにしてください。
シークレットのファイルと権限600
/root/sec3ディレクトリを作成し、/root/sec3/app.envにAPI_KEY=labhub-env-key-v1の1行を書いたあと、ファイルの権限を600に変更してください。
値そのものより、ファイルの権限が採点の対象です。所有者だけが読み書きでき、グループとその他のユーザーには、何の権限もない必要があります。ファイルを作成してから権限を変更する順序で行ってください。
env-fileで注入する
alpine:3.20でsec3-envコンテナを、--env-file /root/sec3/app.envを使ってバックグラウンド実行してください。コンテナのAPI_KEYが、ファイルの値と同じである必要があります。
値をコマンドラインに直接書くと、シェルの履歴とプロセスの一覧に残ります。ファイルから読み込んで注入するオプションを使ってください。採点がコンテナの環境変数の値とファイルの値を照合するので、コンテナは起動している必要があります。
環境変数はinspectにそのまま見える
docker inspect sec3-envで読み取ったAPI_KEYの値を、そのまま/root/sec3/leak-proof.txtに保存してください。
コンテナを作成した人でなくても、デーモンにアクセスできれば、この値を読めます。推測で書かず、実際にdocker inspectで取り出した値を、そのままファイルに残してください。
ファイルとしてマウントする
/root/sec3/secret.txtにlabhub-file-key-v1を書き、そのファイルをsec3-fileコンテナの/run/secrets/api_keyのパスに読み取り専用でバインドマウントして、バックグラウンド実行してください。マウント元は、正確に/root/sec3/secret.txtである必要があります。
ホストのファイル1つを、コンテナの特定のパスにそのまま付ける方式です。マウント元のパスと対象のパスが採点の対象なので、正確に合わせる必要があり、シークレットなので読み取り専用で付ける必要があります。
環境変数には残さない
sec3-fileコンテナの環境変数に、API_KEYも、シークレットの値そのものも、入っていない必要があります。
ファイルのマウントに変えたなら、同じ値を環境変数でも渡す理由はありません。両方与えると、ファイルのマウントの利点がなくなります。inspectにまた見えるからです。ステップ4のコンテナに、環境変数が混ざっていないかを確認してください。
再起動せずにローテーションする
コンテナを再起動しないまま、/root/sec3/secret.txtの内容をlabhub-rotated-v2で上書きしてください。sec3-fileは新しい値を見ることになり、sec3-envの環境変数の値は、古い値のままである必要があります。そして/root/sec3/rotate.mdに、どちらの方式が再起動を必要とするかの結論を書きます(재시작またはrestartという語が入っている必要があります。前者は、韓国語で「再起動」を意味する語です)。
バインドマウントはinodeを追いかけます。ファイルをその場で上書きすれば(リダイレクト)、コンテナが新しい値をすぐに見ますが、ファイルを削除して作り直す方式(一部のエディターやsed -i)は、inodeが変わって、マウントが古いファイルを指し続けます。そして、環境変数の側は、絶対に変わってはいけません。それが、このステップの対照群です。
シークレット検知のスクリプト
実行権限のある/root/sec3/find-secrets.shを作成してください。第1引数で受け取ったディレクトリを調べて、AKIAで始まるアクセスキーやPRIVATE KEYブロックがあれば、0以外の終了コードで終了しつつ、どのファイルで見つけたかパスを出力し、何もなければ、終了コード0で終了する必要があります。
ディレクトリを再帰的に走査して、2つの形を探します。AKIAで始まるアクセスキーと、PRIVATE KEYブロックのヘッダーです。きれいなら0、見つければ0以外の値で終了する必要があり、どのファイルかパスを出力する必要があります。このようなルールが、形がはっきりした値にしかうまく効かないという限界も、あわせて考えてみてください。
受け渡し方式の比較表
/root/sec3/secrets.mdに、環境変数方式とファイルのマウント方式を比較する内容を書き、次の4行を正確にこの形式で含めてください。
env_visible_in_inspect=yesmount_visible_in_inspect=noenv_needs_restart=yesmount_needs_restart=no
4行の値は、前のステップで自分で確認した結果と一致している必要があります。ステップ3で何が見えたか、ステップ5で何が見えなかったか、ステップ6でどちらが再起動を要求したかを振り返って書いてください。そして、本文には、環境変数方式とマウント方式の両方が言及されている必要があります。