SSH Agent Forwarding: Dockerで痕跡を残さずプライベートリポジトリをクローンする方法

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

実践的な課題:Dockerビルド時のSSHキー流出という悪夢

マイクロサービスプロジェクトのDockerイメージをビルドしている場面を想像してみてください。アプリケーションは、GitHubやGitLab上のプライベートリポジトリにあるいくつかの内部ライブラリからコードをプルする必要があります。

ここでの難問は、安全性を確保しながら、どうやってそれらのコードをイメージ内に git clone するかということです。

多くの人が、Dockerfileで COPY コマンドを使って SSHキーファイル(id_rsa)を直接コピーするという「近道」を選びがちです。もし Buildah のようなツールを使わずに標準のビルドを行う場合、プロジェクトはすぐに動きますが、リスクは非常に大きいです。このキーファイルはイメージのレイヤーに永続的に残ります。イメージをプルする権限を持ち、dive のようなツールを使える人なら、60秒もかからずにあなたのプライベートキーを抽出できてしまいます。これは、会社全体のソースコードが流出しかねない重大なセキュリティ脆弱性です。

なぜ従来の手法は極めて危険なのか?

1. SSHキーをイメージにコピーする

# 致命的な間違い
COPY ~/.ssh/id_rsa /root/.ssh/id_rsa
RUN git clone [email protected]:company/private-core.git
RUN rm /root/.ssh/id_rsa

rm コマンドでファイルを削除すれば十分だと勘違いしている人が多いです。実際には、Dockerはすべての変更をレイヤーごとに保存します。最終レイヤーにファイルが見えなくても、その前の COPY レイヤーにはストレージドライバ内にそのまま残っています。

2. ビルド引数(ARG)を使用する

# これでも安全ではありません
ARG SSH_PRIVATE_KEY
RUN echo "$SSH_PRIVATE_KEY" > /root/.ssh/id_rsa

ARG の値はイメージのメタデータに記録されます。docker inspect コマンドを打てば、誰でも渡したキーの内容をすべて読み取ることができます。これでは「頭隠して尻隠さず」です。

3. マルチステージビルド

この方法は、最初のビルドステージにキーを残せるため、比較的マシです。しかし、中間イメージ(intermediate images)はCI/CDサーバー上に残ります。もしサーバーが攻撃を受ければ、キーは依然として危険な状態にあります。特に Docker-in-Docker(DinD)環境 では、イメージの管理に細心の注意が必要です。

標準的な解決策:SSH Agent Forwarding với BuildKit

Docker 18.09から、Dockerは --mount=type=ssh 機能を備えた BuildKit を導入しました。これが現在、最もプロフェッショナルな方法です。これは Laravelを「本番仕様」でDocker化する ような高度な設定においても、安全性を確保するための必須知識と言えます。

この仕組みは、あるサーバーから別のサーバーへジャンプするためにSSH Agent Forwardingを使用する方法と似ています。Dockerは一時的なUnixソケットを作成し、コンテナがホストマシンのSSH Agentを「借りる」ことができるようにします。

実用的なメリット:

  • 絶対的なセキュリティ: SSHキーのデータは1バイトもイメージレイヤーに書き込まれません。
  • 利便性: ファイルをコピーする必要がなく、Dockerfile内で複雑なパスフレーズ管理も不要です。
  • 制御: ホストマシンやCIランナー側で、いつでもアクセス権を取り消すことができます。

詳細な実装ガイド

以下は、企業の実際のプロジェクトで私がよく設定する3つのステップです。

ステップ1:ホストマシンでSSH Agentを準備する

まず、SSH Agentが起動しており、必要なキーがロードされていることを確認してください。

# エージェントを起動
eval $(ssh-agent -s)

# キーを追加(例:GitHub用のキー)
ssh-add ~/.ssh/id_rsa_github

# キーの準備ができているか確認
ssh-add -l

ステップ2:BuildKit標準のDockerfileを書く

このステップでは、コードのクローンを実行する RUN コマンドで SSHマウントタイプを宣言する必要があります。ビルドプロセスがフィンガープリントの確認要求で中断されないよう、github.comknown_hosts に追加することを忘れないでください。

# syntax=docker/dockerfile:1
FROM node:18-slim

# gitとopensshをインストール
RUN apt-get update && apt-get install -y git openssh-client && rm -rf /var/lib/apt/lists/*

# "Host key verification failed" エラーを避けるためにフィンガープリントをスキャン
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts

WORKDIR /app

# キーを保存せずにリポジトリをクローンするためにsshマウントを使用
RUN --mount=type=ssh git clone [email protected]:your-org/private-lib.git .

RUN npm install

ヒント: ファイル冒頭の # syntax=docker/dockerfile:1 行は必須です。これがないと、DockerはBuildKitの高度な機能を理解できません。

ステップ3:ビルドの実行

ビルドコマンドを実行する際、--ssh default フラグを追加するだけです。Dockerは自動的にあなたのマシンのソケットをコンテナに接続します。

# BuildKitを有効化(古いDockerを使用している場合)
export DOCKER_BUILDKIT=1

# イメージを安全にビルド
docker build --ssh default -t my-secure-app .

実践的な経験とトラブルシューティング

大規模なCI/CDシステムを運用する中で、いくつか重要な注意点に気づきました:

複数のSSHキーを同時に使用する

プロジェクトで内部のGitLabとGitHubの両方からコードをプルする必要がある場合は、IDで分類できます:

# Dockerfile内
RUN --mount=type=ssh,id=gitlab git clone [email protected]:internal/core.git

ビルド時に、対応する各キーファイルをマッピングします:docker build --ssh gitlab=~/.ssh/id_rsa_gitlab ...

GitHub Actionsでの展開

CI環境では、webfactory/ssh-agent アクションを使用することをお勧めします。これによりソケットが自動的に管理され、BuildKitがキーをよりスムーズに認識できるようになります。実際、GitHub Actions Runnerをセルフホスト している場合、この方法によりパイプラインの設定時間を20%削減できることが分かっています。

よくあるエラー

もし「Permission denied」エラーが発生した場合は、以下を再確認してください:

  1. ssh-add を実行しましたか?
  2. ホストマシンの環境変数 $SSH_AUTH_SOCK は存在しますか?
  3. Dockerfileの冒頭に # syntax 行はありますか?

結論

COPYキーからSSH Agent Forwardingへの移行は、単に数行のコマンドを変更することではありません。それは、プロフェッショナルで安全なDevOpsの考え方への転換です。この方法により、システムの機密情報が漏洩することを恐れずに、Docker Hubやその他のレジストリに安心してイメージをプッシュできるようになります。さらに、Docker Scoutでイメージの脆弱性をスキャン する習慣をつければ、セキュリティはより盤石なものとなるでしょう。

皆さんの成功を祈っています。もし異なるCI/CDシステムでの設定で困難に直面した場合は、遠慮なく下のコメント欄で質問してください!

Share: