マルチプルなＳＳＨポートフォワーディングの設定

# マルチプルなＳＳＨポートフォワーディングの設定
世はまさにＶＰＮトンネリング時代[^備考１]、であるが、マジメにＶＰＮ張るのは大変だったり、ＳＳＨでトンネリングするのもちょっと面倒だったり、ＶＰＮ張るまでも無く…といったケースは多々ある。実際ＶＰＮ張るのはめんどくさい[^備考２]。

[^備考１]: ２０２０年０９月現在。２０２０年初頭のＳＡＲＳコロナウィルス２禍によるリモートワーク推奨にともなう。
[^備考２]: ルーティングをどう設定するかの問題が…。頭空っぽに設定するなら、そう面倒では無い。

いわゆるプロキシーサーバーを立てるのも一つではあるが、ＨＴＴＰプロキシーでは汎用性に心許なく、ＳＯＣＫＳ５プロキシーについてはサポートしているかどうか悩ましく、プロキシーサーバーまでの通信が保護されてるか不明だし、アプリの設定が大味[^備考３]だったりすることもあって悩ましいところ。

[^備考３]: プロキシーサーバーを指定しての「オン」か「オフ」かしかなく[^備考４]、オン／オフするのもメニューの深いところにあったりするなど。
[^備考４]: 接続先に応じて複数のプロキシーサーバーを指定する、または指定しないなどのファインチューニングする機能が無かったりする。

そこで今回、ＳＳＨはＳＳＨでもポートフォワーディングを駆使することで手軽にトンネリングしようという話である。

この手の話題は調べればいくらでも出てくるのであるが、ユースケースをはっきりさせた上で、最新のＳＳＨクライアント事情に合わせて解説する。

## 想定環境
- クライアントＯＳ： Ｗｉｎｄｏｗｓ１０ バージョン ２００４[^備考５]
- ＳＳＨクライアント： ＯｐｅｎＳＳＨ（７．７ｐ１）・[ＰｕＴＴＹ](https://ice.hotmint.com/putty/)
- クライアントアプリ： Ｆｉｒｅｆｏｘ（８０．０．１で検証）・リモートデスクトップ

[^備考５]: 原理的にはＷｉｎｄｏｗｓ７から実績あり。Ｗｉｎｄｏｗｓ７には標準でＯｐｅｎＳＳＨが無いので話を割り引く必要がある。

※ＯｐｅｎＳＳＨの有効化およびＰｕＴＴＹのインストール方法についてはここでは取り扱わない。
Ｗｉｎｄｏｗｓ上で動く他のＳＳＨクライアントであれば、設定の詳細はともかく、おおよその設定については参考にはなると思う。

![ssh-port-forwarding.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321527/dfc1951a-b48e-d246-6bf1-77632a124091.png)

# まずはＯｐｅｎＳＳＨでポートフォワーディングしてみる
動作原理を確認するため、下記コマンドを実行してみる。

```shell
ssh -L 127.1.1.1:3389:リモデサーバー:3389 -D 127.1.1.1:1080 -l アカウント名 踏み台サーバー
```

- これで【クライアント】から【踏み台サーバー】までの間はＳＳＨにより通信保護される。
- どのような通信が保護されるかというと、以下の通りである。
  - `-L`オプション： `127.1.1.1:3389` に接続したリモートデスクトップクライアント
  - `-D`オプション： `127.1.1.1:1080` をＳＯＣＫＳ５プロキシーに指定したブラウザ（アプリ）

## リモートデスクトップでつないでみる
ここでリモートデスクトップを立ち上げて「コンピュータ」に `127.1.1.1` を指定すれば、踏み台サーバーから「リモデサーバー」に接続（ポートフォワード）してくれる。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321527/826c0e9f-e454-8268-0550-e594e4ee9546.png" alt="screenshot01.png" width="65%">
「踏み台サーバー」を基準に「リモデサーバー」につなぐので以下の点がメリットとなる。

- 踏み台サーバーへの認証はＳＳＨ認証[^備考６]で担保。
- 踏み台サーバーを起点とする内部ネットワーク（イントラ等）への接続が可能。
- アクセス元ＩＰが踏み台サーバーに絞れる。
- 「リモデサーバー」の接続先ポートが標準（３３８９）外のポート番号でもクライアント側で吸収できる。

[^備考６]: 鍵認証またはパスワード認証等。鍵認証のみに制限することを勧める。

デメリットはＳＳＨのオプションが煩雑であることであるが、これについては[後述](#ｏｐｅｎｓｓｈのポートフォワーディング設定)する。

## ブラウザでつないでみる
さてブラウザ（Ｆｉｒｅｆｏｘ）の場合、メニューバーから「ツール」→「オプション」を選んで「一般」の画面の一番下にある「ネットワーク設定」の「接続設定」ボタンを押す。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321527/40499137-0687-107b-2f2a-4236e6f9dc36.png" alt="screenshot02.png" width="65%">

ここで下記の設定を行うと、踏み台サーバーから任意のサイトに対し任意のプロトコルでアクセスするようになる。

| インターネット接続に使用するプロキシーの設定 | ＳＯＣＫＳホスト | ポート |
|:--------------------------------------------:|:----------------:|:------:|
| `●手動でプロキシーを設定する`               | `127.1.1.1`      | `1080` |

- `ＳＯＣＫＳ ｖ５`にチェックが入ってること[^備考７]。
- `ＳＯＣＫＳ ｖ５を使用するときはＤＮＳもプロキシーを使用する`のチェック状態はあまり問わない。
- プロキシーを通さずに接続したいサイトがあれば `プロキシーなしで接続` に当該ドメインを記述すること。

[^備考７]: ＳＯＣＫＳ５の時点で前世紀のプロトコルなのにＳＯＣＫＳ４は無いわ～。

## 設定の要点１
今回例として `127.1.1.1` を指定したが、このＩＰは `127.0.0.1` 以外の `127.0.0.0/8` の範疇で指定が可能で、３オクテット分の自由度がある。
この３オクテットを使って接続先サーバーの識別が可能である。
これは特にＨｏｓｔｓファイル[^備考８]に記載したときに効果的に利用できる。
なお `127.0.0.1` の指定は可能ではあるがお勧めしない。使用したいポートがいつ、どこで他者が使われるか分からないためである。
ある時は問題なかった操作が、別のタイミングで問題を起こす（動作しない等）ことがあるので、`127.0.0.1` の使用はお勧めしない。
また当然 `127.0.0.0` も `127.255.255.255` も使用できない[^備考９]。

[^備考８]: 具体的には `C:\Windows\System32\drivers\etc\hosts` というファイル。
[^備考９]: ネットワークアドレスとブロードキャストアドレス。

## 設定の要点２
`-L` および `-D` オプションは複数指定が可能なので、単一の踏み台サーバーから接続できる、複数の接続先サーバーへの設定が行える。
つまり複数台の接続先設定を一回の設定でまかなえることを意味する。

逆に複数の接続先サーバーに対して、それぞれ踏み台サーバーが異なる場合は、それぞれの踏み台サーバーに接続する必要があるため、一回の設定ではまかなえない。

## オプションを最適化してみる
というのも、先の手順だと普通にサーバーにログインしてしまうので、シェルオペレーションできないようにしたい。そういう時は `-N` オプションを指定する。

- `-N`： リモートコマンドを開始しない（ポート転送のみ運用向け）、`-T` オプションを暗黙に指定している
- `-T`： 疑似端末を確保しない（バッチ処理向け）

`exit` では終了できなくなるので、`CTRL-C` [^備考10]で停止させることになる。`exit` で終了させたい向きもあるかもしれないが、間違ってコピペしてしまったり、別のターミナルのつもりでコマンドを実行してしまう等の事故を考慮すると、このオプションの指定を推奨するものである。

[^備考10]: コントロールキーを押しながら「`c`」を押す。「`C`」でもかまわないが、大文字の `C` であることが必須の場合 `CTRL-SHIFT-C` という表現になる（とは言え、一般的な表現ではない）。

またこの辺りをまとめてバッチファイルにしてしまうのがよろしいと思われ。

# ＰｕＴＴＹでポートフォワーディングしてみる
こっちは実践と設定がほぼイコールなので、その前提で[先の例](#まずはｏｐｅｎｓｓｈでポートフォワーディングしてみる)を踏襲して説明する。
[ＰｕＴＴＹ](https://ice.hotmint.com/putty/)を立ち上げると、設定画面が表示される。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321527/35ed7825-bc6d-0a76-6eb0-0fa41b49ec64.png" alt="screenshot03.png" width="65%">

【接続】→【ＳＳＨ】→【トンネル】より、一行一行、下記の設定を「追加」する。

| 源ポート         | 送り先ポート          | 下記にチェックを入れる  |
|:----------------:|:---------------------:|:-----------------------:|
| `127.1.1.1:1080` | （記述しない）        | ダイナミックポート      |
| `127.1.1.1:3389` | `リモデサーバー:3389` | ローカル                |

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321527/d0508fec-d82e-87f5-d6ce-209dc978afac.png" alt="screenshot04.png" width="65%">

今回、以下の２つの設定は無関係なのでスルーしてよい。

- 「ローカルポートは他のホストからの情報を受け入れる」
- 「リモートポートも同様（SSH-2のみ）」

この後【セッション】に戻って、「ホスト名」を入力、接続タイプに「ＳＳＨ」にチェックを入れて「開く」ボタンを押せば、ポートフォワーディング開始となる。

[接続](#リモートデスクトップでつないでみる)[例](#ブラウザでつないでみる)については、先の内容を確認して欲しい。

## オプションの最適化と設定の保存
[先の説明](#オプションを最適化してみる)と同じく、ＰｕＴＴＹでもいくつかの設定すべき／した方がよいオプションがある。

- 【接続】
  - 【データ】
        - 「自動ログインのユーザー名」： ＯｐｅｎＳＳＨではローカルパート[^備考11]のないしは `-l` オプションでの指定に相当する。。
  - 【ＳＳＨ】
        - 「シェルやコマンドを開始しない」： ＯｐｅｎＳＳＨでは `-N` オプションに相当する設定である（`-T` オプション相当とは独立）。
        - 【ＴＴＹ】
            - 「疑似端末を確保しない」： ＯｐｅｎＳＳＨでは `-T` オプションに相当する設定である。
- 【ウィンドウ】 ※画面の隅に置いておくサイズで
  - 「桁」： 個人的には「`80`」辺りに設定するの推奨
  - 「業」： 個人的には「`4`」辺りに設定するのを推奨
  - 「スクロールバックの行数」： 個人的には「`0`」辺りに設定するのを推奨
  - 【外観】
        - 「フォントの設定」→「変更」ボタンを押す： サイズを８～１０ポイント程度の小さめのフォントを選択する。

他にも設定した方がよい設定があるが、運用（ＳＳＨ鍵認証等）と密接に絡むパラメータなので、ここでは取り上げない。

上記設定がなされた後、【セッション】にて「セッション一覧」の下のボックス（上側）にセッション名（たとえば `ssh-port-forward` など）を入れて「保存」ボタンを押す。

# ＯｐｅｎＳＳＨのポートフォワーディング設定
ＯｐｅｎＳＳＨの場合、オプションを保存しておこうと思ったら、バッチファイルを作成することになるが、一応設定を保存しておくことが可能である。

コマンドプロンプトを立ち上げた際のホームディレクトリ（`C:\Users\アカウント名`）の下に `.ssh` ディレクトリを作成（事前に作成済みの場合もある）、`config` という名前のテキストファイル（拡張子無し）を作成する。取り急ぎ[先の例](#まずはｏｐｅｎｓｓｈでポートフォワーディングしてみる)にならい、下記の設定を行う。

```
Host ssh-port-forwarding
    HostName 踏み台サーバー
    User アカウント名
    DynamicForward 127.1.1.1:1080
    LocalForward   127.1.1.1:3389 リモデサーバー:3389
    ExitOnForwardFailure yes
```

これで `ssh -N ssh-port-forwarding` と実行するだけで、ポートフォワードしてくれるようになる。また終了は `CTRL-C` [^備考10]で停止することになる。

- `HostName`、`User`： 踏み台サーバーのサーバー名とアカウントの指定
- `DynamicForward`： `-D` オプション相当
- `LocalForward`： `-L` オプション相当
- `ExitOnForwardFailure`： 指定された `-D`、`-L`、`-R`、`-w` オプションのどれかでセットアップに失敗すると終了する（起動しない）

なお `-N` オプションは特別で、コマンドラインでしか指定できない。
`-T` オプション相当であるところの `RequestTTY no` 設定は `-N` オプションの指定があるので無意味である[^備考12]

# よくある質問とその答え

## Ｑ．多段ポートフォワードすれば一個の設定で行けるじゃないのか！
Ａ．行けないことは無いですが、まずは基本を抑えておきましょう。そのうちここでも解説するかもしれない。
あと必ず前段の踏み台サーバーを経由することが必須なので、その分の帯域とレイテンシーにペナルティがかかります。
場合によっては、そういう深い構成のネットワークを相手にする場合もあるので、多段せざるをえない場合もあります。
言い直すと、直列多段のユースケースはありますが、並列多段を直列化するのはお勧めしません。

## Ｑ．第三者サーバーｔｏ第三者サーバーの設定方法は？
Ａ．基本的なロジックは同じなのですが、本件での位置付けとしては別物なので、ここでは取り扱いません。

## Ｑ．`-R` オプションあるいは `RemoteForward` って何だ？
Ａ．`-L` オプションあるいは `LocalForward` の逆向きの設定です。何が逆向きかというと、下図のようにリモート側の何かクライアントが、ローカル側の何かサービスへアクセスする、といった向きです。
ＳＳＨの接続する方向性は変わりありませんので実にやっかいです。
ユースケースが限られる、というかこれを使いたい状況というのが説明しにくいのと、実用する場合には、更に注意点（`GatewayPorts` 設定）があるので、ここでは取り扱いません。
いずれにせよ下記のようなユースケースでは欲しいと思う状況は一般に発生しないです。ゼロとは言わないのでこのオプションが存在するわけですが、「そうせざるを得ない」という意味で極めて特殊です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158501/388bf8c2-2d23-7eb5-12c8-1141c86ed152.png" alt="ssh-port-forwarding-remote.png" width="65%">

## Ｑ．ＨＴＴＰプロキシーとＳＯＣＫ５プロキシーで何が違うんだ？
Ａ．今時なんでも（ＶＰＮですら）ＨＴＴＰ（ＨＴＴＰＳ）で通信するのでどっちでも良いと言えばどっちも違いは無いように見えないこともないですが、プロトコル汎用プロキシーとして設計されているＳＯＣＫＳ５の場合、ＵＤＰすらプロキシーできたりするので、そこの部分だけ違いがあると言えば違いがあるかもしれません。

## Ｑ．`-L` オプションと `-D` オプションの使い分けは？
アプリの対応状況によって使い分けます。
ＳＳＨ自体にはＨＴＴＰプロキシー機能が無いので、ＳＯＣＫ５プロキシーが使えるアプリでは `-D` オプションを使用（指定）します。宛先を良きに計らってくれるため、使い勝手が良いです。
接続先の指定しかできないアプリでは、`-L` オプションにより擬似的（`127.x.x.x:yyyy`）に宛先を乗っ取ってやる必要があります。

# 参考文献
- [ssh(1)](https://man.openbsd.org/ssh)
- [ssh_config(5)](https://man.openbsd.org/ssh_config)

[^備考11]: 「ローカルパート`@`ホスト名」と指定した場合の `@` より前の文字列のこと
[^備考12]: 設定しても問題は無い。`RequestTTY yes` といった矛盾した設定の挙動は調べてない。調べる気も無い。