🐳

WSL2 + VSCode + DevcontainerでGitHubにSSHでpushする際にちょっとハマった

2023/09/24に公開

はじめに

久々に開発環境を構築しなおした際に、VS CodeDevcontainerを使ってWSL2上で開発をしているのですが、GitHubSSHpushする際にハマったのでメモしておきます。

環境

  • OS: Windows 11 Pro
  • WSL2 + Ubuntu 22.04
  • VS Code 1.82.2
  • Docker Desktop 4.23.0 (120376)

結論

とりあえず同じ問題にぶつかった人のために、先に結論を書いておきますね!

  1. WSL2上でkeychainを導入する

  2. .bashrcに以下のコードを追記する

# SSHの秘密鍵をロードする(※~/.ssh/id_rsaの場合は下記の通り)
/usr/bin/keychain -q --nogui $HOME/.ssh/id_rsa
source $HOME/.keychain/$(hostname)-sh

経緯

元々、WSL2上で開発をしていたのですが、devcontainer内でGitHubSSHpushするために、元々設定されていた下記のような設定を何も考えずに引き継いで使っていました。

.devcontainer/compose.yml

volumes:
  node_modules:

services:
  node:
    build:
      context: ..
      dockerfile: .devcontainer/node/Dockerfile
    volumes:
      - ..:/home/node/project:cached
      - node_modules:/home/node/project/node_modules:delegated
      - ~/.ssh:/home/node/.ssh:cached # ここでSSHの秘密鍵を指定する
      - ~/.gitconfig:/home/node/.gitconfig:cached # ここでGitのグローバル設定を共有する
    command: /bin/sh -c "while sleep 1000; do :; done"
    user: node

ところが、この設定だと、devcontainer内とWSL2上で別ユーザーを使いたい場合に、SSHの秘密鍵が読み込めないという問題が発生してしまいました。

衝撃の事実: そもそも公式で推奨されている方法と違っていた

拡張機能の公式ドキュメントの解説箇所を見てみると、このように書かれてます。

The Dev Containers extension provides out of the box support for using local Git credentials from inside a container. In this section, we'll walk through the two supported options.

If you do not have your user name or email address set up locally, you may be prompted to do so. You can do this on your local machine by running the following commands:

git config --global user.name "Your Name"
git config --global user.email "your.email@address"

The extension will automatically copy your local .gitconfig file into the container on startup so you should not need to do this in the container itself.

つまり、devcontainer内で~/.gitconfigを共有する必要はなく、devcontainer内でgit configを実行することで、~/.gitconfigが自動的にコピーされるようです。

この時点で、以下の行が完全に意味がないということがわかりました。

- ~/.gitconfig:/home/node/.gitconfig:cached # ここでGitのグローバル設定を共有する

さらに衝撃の事実: SSHの秘密鍵も自動的に共有される

さらにドキュメントを読み進めると、SSHの秘密鍵を共有する方法も書かれていました。

There are some cases when you may be cloning your repository using SSH keys instead of a credential helper. To enable this scenario, the extension will automatically forward your local SSH agent if one is running.
You can add your local SSH keys to the agent if it is running by using the ssh-add command. For example, run this from a terminal or PowerShell:

ssh-add $HOME/.ssh/github_rsa

On Windows and Linux, you may get an error because the agent is not running (macOS typically has it running by default). Follow these steps to resolve the problem:

なんと、ホストOSでssh-agentが起動していて、SSHの秘密鍵が登録されていれば、devcontainer内でも同じSSHの秘密鍵が使えるようです。
至れり尽くせりかよ!ここまで書いてくれてるのに何で誰も気づかなかったんだろう…。

意気揚々と試してみる

さっそく、WSL2上でssh-agentを起動して、SSHの秘密鍵を登録してみました。
(プライバシーに関わる部分は伏せてます)

まず、ssh-agentを使えるようにopenssh-clientsocatをインストールして…

$ sudo apt install openssh-client socat
$ eval `ssh-agent`
Agent pid 12345
$ ssh-add ~/.ssh/id_rsa
Identity added: /home/nayu/.ssh/id_rsa (nayu@DESKTOP-XXXXXXX)

ssh-agentが起動して、SSHの秘密鍵が登録できました。

この後、実際にdevcontainerを起動して、git pushをしてみると…

$ git pull
Already up to date.

無事に接続できてる感じだ!!素晴らしい!

起動するたびにssh-addするのが面倒なので自動化しようとした

しかし、いろいろ調べているうちに、このままだとssh-agentを起動するたびに、ssh-addをする必要があることが判明しました。

そこで、下記のようなコードを.bashrcに追記して、ssh-agentを起動すると同時にssh-addをするようにしました。

# 登録したいSSHキーのフルパスを配列で設定
# ユーザーのホームディレクトリは$HOMEで指定
SSH_KEY_PATHS=(
  "$HOME/.ssh/github-first.pem"
  "$HOME/.ssh/github-second.pem"
)

# SSHエージェントの環境変数が設定されていない場合
if [ -z "$SSH_AUTH_SOCK" ]; then
   # 現在実行中のSSHエージェントがあるか確認
   RUNNING_AGENT=$(ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]')

   # SSHエージェントが実行中でない場合、新しいエージェントを起動
   if [ "$RUNNING_AGENT" = "0" ]; then
        eval $(ssh-agent -s)
   fi

   # SSH_AUTH_SOCKが設定されているか確認
   if [ -n "$SSH_AUTH_SOCK" ]; then
       # すでに登録されているキーを取得
       REGISTERED_KEYS=$(ssh-add -l)

       # 各SSHキーに対して処理
       for KEY_PATH in "${SSH_KEY_PATHS[@]}"; do
           # SSHキーの名前(ファイル名)を抽出
           KEY_NAME=$(basename $KEY_PATH)
           # 該当のキーがすでに追加されているか確認
           KEY_EXIST=$(echo "$REGISTERED_KEYS" | grep "$KEY_NAME" | wc -l)
           # キーが追加されていない場合、ssh-addで追加
           if [ "$KEY_EXIST" = "0" ]; then
               ssh-add $KEY_PATH
           fi
       done
   else
       echo "SSH_AUTH_SOCK is not set. Cannot proceed with ssh-add."
   fi
fi

ふっ…また便利なスクリプトを書いてしまった…。
と、思ったのですが、シェルを起動した瞬間、私は凍り付きました。

SSH_AUTH_SOCK is not set. Cannot proceed with ssh-add.

$nayu@DESKTOP-XXXXXXX:~$

私:「待って。SSH_AUTH_SOCKが設定されていないってことは、ssh-agentが起動してないってことだよね??
いや、ssh-agentは起動してるはずなんだけど…。」

自分で書いたエラーハンドリングなのに、自分で混乱しています。

じゃあ、ssh-agentが起動しているか確認してみましょう。

$nayu@DESKTOP-XXXXXXX:~$ ps -ax | grep 'ssh-agent -s' | grep -v grep | wc -l | tr -d '[:space:]'
1

私:「え??ssh-agentは起動してるのに、SSH_AUTH_SOCKが設定されてないってことは、ssh-agentが起動してないってことだよね??」

もうこのへんで、私は完全に頭がおかしくなっています。

私:「じゃあ…手動でキーを追加してみるか…」

$nayu@DESKTOP-XXXXXXX:~$ ssh-add ~/.ssh/github-first.pem
Could not open a connection to your authentication agent.

私:「…。(失神)」

WSL2のssh-agentは一味違うようだ

そうこうしているうちに、以下の記事を見つけました。

https://zenn.dev/kaityo256/articles/ssh_agent_on_wsl

以下、引用いたします。

このあとssh-addするとパスフレーズが記憶されます。しかし、一度ターミナルを抜けてもう一度開くと、ssh-agentプロセスは生きているのに、ssh-addができません。

そういうことだったのか…。

keychain導入ですべて解決

上記の記事でも紹介されているように、keychainを導入することで、ssh-agentを起動するたびにssh-addをする必要がなくなります。

sudo apt-get install keychain

keychainを導入したら、~/.bashrc | ~/.zshrcに以下のコードを追記します。

# SSHの秘密鍵をロードする
/usr/bin/keychain -q --nogui $HOME/.ssh/id_rsa # ここでSSHの秘密鍵を指定する
source $HOME/.keychain/$(hostname)-sh

これで、WSL2上でssh-agentを起動するたびに、ssh-addをする必要がなくなりました。
エラーも出なくなりました。

補足

同一ディレクトリに公開鍵がない場合、以下のような警告が出るみたいです。

 * Warning: Cannot find separate public key for {秘密鍵のパス}

その場合は、ssh-keygenで公開鍵を作成できます。

ssh-keygen -y -f {秘密鍵のパス} > {公開鍵のパス}.pub

まとめ

今回は、WSL2上でVS CodeDevcontainerを使って開発をしている際に、GitHubSSHpushする際にちょっとだけハマった話を書きました。
他にも解決方法はありそうですが、keychainを使った方法はかなりシンプルだったので、今後はkeychainを使うことにしました。

最初に紹介した古いdeccontainer用のcompose.ymlもきれいになりました。

volumes:
  node_modules:

services:
  node:
    build:
      context: ..
      dockerfile: .devcontainer/node/Dockerfile
    volumes:
      - ..:/home/node/project:cached
      - node_modules:/home/node/project/node_modules:delegated
    command: /bin/sh -c "while sleep 1000; do :; done"
    user: node

それでは良きDevcontainerライフを!

参考

https://zenn.dev/kaityo256/articles/ssh_agent_on_wsl

https://esc.sh/blog/ssh-agent-windows10-wsl2/

Discussion