reverse ssh ポートフォワーディングでローカル PC のサービスを外部公開するメモ

## 背景

* ローカルのつよつよ PC で StyleGan とかで画像処理するサービスのデモを構築し提供したい(ベータサービス公開みたいな)
* Solana validator などのブロックチェーン関連のサーバーを建てたい(これもつよつよ CPU + GPU がいる)
* ローカル環境(e.g. 自宅, オフィス)ではグローバル IP は無い
* ネットワーク帯域はそれほど消費しない & 速度(レイテンシ)も重要ではない
* AWS とかでつよつよ構成のインスタンス立てると高すぎる & 常時使うわけではないので無駄が多い

外部から `http://my-awsome-ml.com:8088` にアクセスがあったら, それをローカルの PC に転送し, 処理をして結果を返すのをやりたい.

reverse ssh 接続でポートフォワーディング(トンネリング)で実現します.

ルータに変更などは不要ですが, ゲートウェイ(踏み台)として global IP(or dynamic DNS)のある VPS などは必要になります.

## 環境

* local PC
* VPS など global IP(or dynamic dns) があるサーバ. ネットワークを転送するだけなので, 月数百円とかのレベルのでよい


## 手順

* firewall のポートを開ける
* reverse SSH でローカル PC からポートフォワーディングの指定をする
* ただし reverse SSH 接続だけでは外部からはアクセスできないので, sshd config を編集して外部からもアクセスできるようにする 

### 詳細

#### ポート開放

まず, ゲートウェイ(踏み台)サーバでポートを開放します.

Centos7 でたとえば 8088~8098 まで開放したい場合,

```bash
# server: my-awesome-ml.com
> sudo firewall-cmd --zone=public --add-port=8088-8098/tcp --permanent
> sudo firewall-cmd --reload
```

として開放します.

#### reverse SSH

How does reverse SSH tunneling work?
https://unix.stackexchange.com/questions/46235/how-does-reverse-ssh-tunneling-work

VPNを使わずに、逆SSHポートフォワードを使って自宅のマシンをRDPで外から操作する
https://qiita.com/mikuta0407/items/b1a3b94b356e97eafb8e

ありがとうございます.

ローカル PC から,

```bash
# [local]
> ssh -R my-awesome-ml.com:8088:localhost:8088 -N my-awsome-ml.com
```

のようにしてコネクションを張ります. 
(@ttdoda さまからご指摘いただきました. ありがとうございます)

特にエラーがなければ成功で, 何も表示されずそのまま動いています.

##### sshd の変更

ただ, この状態ですとサーバーの `my-awesome-ml.com` 内部から localhost:8088 にアクセスすればローカル PC に転送されますが, 外部(インターネット)から `my-awesome-ml.com:8088` でアクセスはできません.

sshd の設定で ~~`GatewayPorts yes` にすることで, 外部からの `my-awesome-ml.com:8088` のアクセスが可能になります.~~
`GatewayPorts clientspecified` にする必要がありました. @ttdoda さまご指摘ありがございます :pray:

#### socat を使う

sshd に変更をいれるのを躊躇われる場合は, socat でフォワーディング(リレー)することでもいけました.

サーバー側で

```bash
# server: my-awesome-ml.com
$ socat TCP4-LISTEN:8088,fork TCP4:localhost:8089
```

のようにします. `reuseaddr` をお好みで添えて.

同じポート番号にしてしまうと ssh reverse portforwarding のプロセスとポートが衝突して Address already in use になってしまうので注意です.
上記のように socat でリレーする場合は reverse ssh で接続するポートも 8089 に変えておきます.



#### ローカル PC でサービスを立てる

あとはローカル PC でサービスを立てるだけです!

たとえば http サービスを建てて確認してみましょう.

```bash
$ python -m http.server 8088
```

これで `http://my-awesome-ml.com:8088` にアクセスするとローカル PC でうごいている http サーバに転送されて処理されるはずです!

python http.server はそのままだとカレントディレクトリの中身を見せるので, ディレクトリの中身全世界に公開されてしまうので注意してくださいね.

### Advanced な内容

#### https にする

いつものアプリケーションサーバに対して HTTPS でアクセス可能にする気軽な方法
https://qiita.com/cuzic/items/672c66e8ccdfba067166

socat で簡易プロキシサーバーを立てよう
http://racchai.hatenablog.com/?page=1460687400

ありがとうございます. socat との組み合わせでいけますね.

#### 安定運用したい

Reverse ssh tunnel を安定運用する
https://qiita.com/syoyo/items/d31e9db6851dfee3ef82

#### 複数ポートを転送したい

Solana の validator のように, 複数ポートを使うようなサービスも扱えるようにしたい.

ssh ポートフォワーディングで複数ポートを転送したいメモ
https://qiita.com/syoyo/items/5792de1404bf6fac5843

ワイルドカード指定とかはできないので, ポート数ぶん指定して reverse ssh 張る必要がありました.

socat のほうもポートごとにプロセスを立てる必要があります.

#### セキュリティはどうか

少なくともローカル PC で動かすサービスはある程度 Sandbox/Contaier 実行にしたほうがよいでしょう(e.g. c Docker とか LXC とか). 画像処理サービスの場合, 悪意あるユーザーが変に巨大な画像をアップロードしたり, JPG/PNG に悪意あるデータ(デコードすると buffer-overrun や out-of-memory を引き起こすなど)を忍ばせる可能性があります.

プログラムが CPU だけの場合は wasm-sandboxing でプログラム全体をセキュアにするという手もあります https://github.com/shravanrn/wasm-sandboxing

GPU を使う場合はでも完全にセキュアというわけにはいかないでしょう(Docker だと GPU device は privileged 権限で動かさないといけなかったような).

vGPU なりで HW できちんと仮想化されたのが理想ですが, その場合はサービスを本番運用したときに考えればよいでしょうか.

Solana validator を運用する場合は悩まなしいところですね.
(悪意ある validator が GPU をハングさせるような命令を忍ばせてくる可能性も無きにしも非ず)
 

#### VPN?

OpenVPN とかでローカル PC と踏み台サーバーをつないで通常の順方向 port forwarding(or proxy)という手もありますが, VPN だとネットワーク環境全体を設定することになるので, セキュリティ的にちょっと気になります(侵入とかがあったら環境丸見え).  

### TODO

* [ ] proxy server(e.g. http server の proxy 機能)を使ったほうがいいか?
* [ ] socat 以外で, ポートフォワーディングだけ処理してくれるなんかいい感じに処理してくれそうなツールがありそうなので探してみる
* [ ] ポートフォワーディングだけ処理してくれるサービスがありそうだが...(悪用されやすいのか, 需要がないのか, ぱぱっとは見つからなかった)  
* [ ] ローカル PC から socat で SSL + reverse proxy する方法を試す(サーバ側で socat 実行が不要にできるように).  
* [ ] reverse ssh ポートフォワーディングのセキュリティについて考える(数万円くらいでオンデマンドセキュリティチェックしてくれるサービスほしい)