Windows SOCKS Proxy SSH環境で Git Bash と VSCode Remote Development の ~/.ssh/config を共通にする

# Motivation

使用頻度の高いGit BashからのsshとVSCode Remote Developmentでssh_configの設定を共通化したい。

# 最初にまとめ

* Git BashとWindows標準のsshで ~/.ssh/config でのパスの書き方が違う。
* VSCodeのRemote Developmentでは標準でWindowsのsshが使われる。
* ~/.ssh/config を以下のように書くと、Git BashとWindows標準どちらのsshからもtargetに接続できる。

```ssh:~/.ssh/config
Host target
  Hostname target.example.com
  User ******
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand C:/PROGRA~1/Git/mingw64/bin/connect.exe -S socks.example.com:1080 %h %p
```

connect.exe のパスにスペースが入らないように、 `Program Files` の短いファイル名 `PROGRA~1` を使うこと、ディレクトリ区切りに`\`ではなく`/`を使うことがポイント

構成:

![windows_socks_proxy_gitbash_vscode_ssh_config3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183533/2f5f8842-d190-70ae-6ba9-24e4959271fd.png)


# 環境

* Windows 10 64bit (1909)
* Git for Windows 64bit (2.22.0)
* Visual Studio Code 1.60.1

# 経緯と詳細


* Windows で SOKCKサーバー経由のSSH接続を行うにはGit for Windows付属の connect.exe を使う。
  * Git for Windows ならみんなもうインストール済み
  * ssh.exe のパスは `C:\Program Files\Git\usr\bin\ssh.exe`
  * connect.exe のパスは `C:\Program Files\Git\mingw64\bin\connect.exe` ここはPATHの通っていないディレクトリなので、connect.exeを使うにはフルパスで指定が必要
* 最近のWindowsにはssh.exeが付属してきており、そのパスは `C:\Windows\System32\OpenSSH\ssh.exe`
  * VSCode の Remote Development においては標準でこちらのssh.exeが使われる

## Git BashからProxy経由でsshのssh_config


```ssh:~/.ssh/config
Host target
  Hostname target.example.com
  User ******
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand "C:\Program Files\Git\mingw64\bin\connect.exe" -S socks.example.com:1080 %h %p
```

`C:\Program Files\Git\mingw64\bin\connect.exe` を一つの実行ファイルと認識させるためにこのパスをダブルクォーテーションで囲んでいる。

## PowerShellからProxy経由でsshのssh_config

```ssh:~/.ssh/config
Host target
  Hostname target.example.com
  User ******
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand C:\Program Files\Git\mingw64\bin\connect.exe -S socks.example.com:1080 %h %p
```

PowerShellのCLI、cmd.exe (コマンドプロンプト)、 Visual Studio Code などWindowsネイティブでのパスの指定方法はこちら。

`C:\Program Files\` のように途中にスペースがあってもなぜか `C:\Program Files\Git\mingw64\bin\connect.exe` までが最初の実行ファイルの指定だとわかり、connect.exe を起動して残りをconnect.exeへのパラメータとして扱ってくれる。

## Git BashとPowerShellからの~/.ssh/config設定を共通にしたい

違いは ProxyCommand の connect.exe のパスの書き方だけなのでなんとか共通化したい。


`ProxyCommand C:\Program Files\Git\mingw64\bin\connect.exe` でGit Bashからsshすると、エラー

```bash
$ ssh target
/usr/bin/bash: line 0: exec: C:Program: not found
kex_exchange_identification: Connection closed by remote host
```
`\` がエスケープ文字と見なされて消えているのと、 `Program Files` のスペースで区切られてしまい、 Program という実行ファイルを探してしまっている。
エスケープ
`ProxyCommand C:\\Program\ Files\\Git\\mingw64\\bin\\connect.exe` でGit Bashからsshすると、つながる。

```bash
$ ssh target
Last login: Sun Sep 19 09:37:09 2021 from xxx.xxx.xxx.xxx
```

この状態でPowerShellからsshするとエラー。

```
PS C:\Users\umorigu> ssh target
CreateProcessW failed error:2
posix_spawn: No such file or directory
```

逆にGit Bashで繋がっていた `ProxyCommand "C:\Program Files\Git\mingw64\bin\connect.exe"` でPowerShellからsshすると、これもエラー。

```
PS C:\Users\umorigu> ssh target
CreateProcessW failed error:87
posix_spawn: Unknown error
```
シングルクォートで囲んでみても `ProxyCommand 'C:\Program Files\Git\mingw64\bin\connect.exe'` メッセージが変わるが引き続きエラーになる。

```
PS C:\Users\umorigu> ssh target
CreateProcessW failed error:2
posix_spawn: No such file or directory
ssh_exchange_identification: Connection closed by remote host
```

Windows native のsshは、パス前後に引用符 `"`, `'` が付いたり、パスの途中に `\` が入ったりするともうつながらなくなってしまう。
ただし複数の `\` は一つの `\` と同一視される。(Windows一般ルール)

引用符を使わずにパス途中のスペースに対応する方法は？ と考えて、短いファイル名を思い出した。

```
> dir /x C:\
...
2021/09/07  14:45    <DIR>          PROGRA~1     Program Files
2021/06/29  10:49    <DIR>          PROGRA~2     Program Files (x86)
...
```

`ProxyCommand C:\PROGRA~1\Git\mingw64\bin\connect.exe` を試すと
PowerShellではつながる、Git Bashでエラー

```bash
$ ssh target
/usr/bin/bash: line 0: exec: C:PROGRA~1Gitmingw64binconnect.exe: not found
kex_exchange_identification: Connection closed by remote host
```
`\` がエスケープ文字と解釈されて消えてしまっている。

`\`をエスケープするために`\\`に変えた `ProxyCommand C:\\PROGRA~1\\Git\\mingw64\\bin\\connect.exe` を試すと
PowerShellではつながる。Git Bashでもつながる！

次にバックスラッシュ2つを`/`に変えた `ProxyCommand C:/PROGRA~1/Git/mingw64/bin/connect.exe` を試すと、これも
PoewrShellではつながる。Git Bashでもつながる！

以上により、 ProxyCommand の記述として正しいのは次の2パターン。

```
ProxyCommand C:\\PROGRA~1\\Git\\mingw64\\bin\\connect.exe -S socks.example.com:1080 %h %p
```

```
ProxyCommand C:/PROGRA~1/Git/mingw64/bin/connect.exe -S socks.example.com:1080 %h %p
```

どちらでも動作するがLinuxでのssh_configや `IdentityFile ~/.ssh/id_rsa` のような典型的な記述を意識して今回は `/` を採用する。

# 確定したパスを使ってconnect.exeのバージョンを確認する

```
C:/PROGRA~1/Git/mingw64/bin/connect.exe -V
```

を実行すると connect.exe のバージョンを確認できる。 (Git Bash, PowerShell, コマンドプロンプトすべて同じでOK)

```bash
$ C:/PROGRA~1/Git/mingw64/bin/connect.exe -V
connect --- simple relaying command via proxy.
Version 1.105
```

```
PS C:\Users\umorigu> C:/PROGRA~1/Git/mingw64/bin/connect.exe -V
connect --- simple relaying command via proxy.
Version 1.105
```

## 得られた知見

* Git BashとWindows標準のsshで実行バイナリが異なり、 ~/.ssh/config でのパスの書き方が違う。
* VSCodeのRemote Developmentでは標準でWindowsのsshが使われる。
* Windows標準sshは、パスを`"`や`'`で囲むと動かなくなる
* Git Bash付属sshはShell的な解釈を行い、`"`や`'`で囲まれていないパスの途中にスペースがあると動かない。
* 短いファイル名 ( `C:\Program Files` に対して `C:\PROGRA~1` ) を使うことでスペースも引用符もなしにGit BashとWindows標準のsshに共通のパス設定(実行ファイル指定)を行うことができる。
* パス区切り文字列は `\\` とするか `/` とするか、どちらでも有効。

# 別解

* Git for Windows を Program Files でなくスペースの入らないディレクトリにインストールする。
  * これでもOK。ただ、そんなことをする人はほとんどいない。
* VSCode の Remote.SSH:Path に `C:\Program Files\Git\usr\bin\ssh.exe` を設定する。
  * これでもOK。ただ、できるだけVSCode側のデフォルト設定は変えずにおきたい。

# 応用

## 踏み台サーバー経由で別のサーバーにログインする

踏み台サーバー(bastion)を経由してtarget-internalにログインしたい場合の設定は以下のようになる。

構成:

![windows_socks_proxy_gitbash_vscode_ssh_bastion_config3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183533/1d7e2219-7bd2-c374-e25e-4f6d3b67985c.png)


```ssh:~/.ssh/config
Host bastion
  Hostname bastion.example.com
  User ******
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand C:/PROGRA~1/Git/mingw64/bin/connect.exe -S socks.example.com:1080 %h %p

Host target-internal
  Hostname target-internal.example.com
  User ******
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand ssh.exe -CW %h:%p bastion
```

こうしておくと

```bash
$ ssh target-internal
```

でSSHログインできる。

## HTTP Proxy経由でSSH接続

HTTP Proxyの場合は、 connect.exe に指定するオプションを `-S`(SOCKS) から `-H` に変える。

```ssh:~/.ssh/config
Host target
  Hostname target.example.com
  User ******
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand C:/PROGRA~1/Git/mingw64/bin/connect.exe -H proxy.example.com:10080 %h %p
```


# 感想

2021年になってショートファイル名 PROGRA~1 を使うことになるとは思わなかった。