ローカルに立てたWebサーバーを、VPSのSSHサーバーを経由して外部公開する（リバースSSHポートフォワーディング）

# 記事の内容

ローカルで立ち上げたWebサーバーをインターネットに公開します。
ローカル環境は、今回は私の自宅PCです。
普通の光回線契約で、固定IPアドレスではありません。

本記事では既に、VPSにSSHサーバーを立ててあります。
ローカルからVPSにSSH接続し、リバースSSHポートフォワーディングの機能を利用して、外部からのWebアクセスをローカルWebサーバーで受けます。

図にすると以下のような感じです（等幅フォントで見てね）。


┏━━━━━━━┓　　　┏━━━━━━━━━━━━━━┓　　　┏━━━━━━━━━┓
┃　外部利用者　┃　　　┃　　　　　　VPS 　　　　　　┃　　　┃　　ローカル　　　┃
┃　　　　　　　┃　　　┃　　　　　　　　　　　　　　┃　　　┃　　　　　　　　　┃
┃　　　　　　　┃　　　┃　　　　　　　　　　　　　┏┻━┓　┃　　　　　　　　　┃
┃　　　　　　　┃　　　┃　　　　　　　　　　　　　┃　　┃ポートフォワード　　　┃
┃　　　　　　　┃　　　┃　　　　　　　　┌────┨　　┠─╂────┐　　　　┃
┃　　　　　　　┃　　　┃　　　　　　　　│　　　　┃SSH ┃　┃　　　　│　　　　┃
┃　　　　　　　┃　　　┃　　　　　　　　↓　　　　┃　　┃　┃　　　　↓　　　　┃
┃┌────┐　┃　┏━┻━┓　　┌──────┐　┃　　┃　┃　┌──────┐┃
┃│　　　　│　┃　┃　　　┃　　│　　　　　　│　┃　　┃　┃　│　　　　　　│┃
┃│ブラウザ│←╂─┨HTTP/S┃←→│SSHサーバー │←┨　　┠─╂─┤Webサーバー │┃
┃│　　　　├─╂→┃　　　┃　　│　　　　　　├─┨　　┠─╂→│　　　　　　│┃
┃│　　　　│　┃　┃　　　┃　　│　　　　　　│　┃　　┃　┃　│　　　　　　│┃
┃└────┘　┃　┗━┳━┛　　└──────┘　┗┳━┛　┃　└──────┘┃
┃　　　　　　　┃　　　┃　　　　　　　　　　　　　　┃　　　┃　　　　　　　　　┃
┃　　　　　　　┃　　　┃　　　　　　　　　　　　　　┃　　　┃　　　　　　　　　┃
┗━━━━━━━┛　　　┗━━━━━━━━━━━━━━┛　　　┗━━━━━━━━━┛

## 参考ページ（感謝します）

[ngrokやserveoなしでローカルで開発しているサイトに外部からアクセスする方法](https://blog.champierre.com/1157)
[caddyで簡単にhttpsサイト](https://qiita.com/mmotoi/items/1af28e2e62a4e344eb6c)

# 環境

## VPS環境

ドメイン名：example.com （本当は違う）
OS：CentOS 8.2
ユーザー名：usrname
sshポート：2222
Ed25519鍵認証

以下のように、ファイアウォールの設定でhttpとhttpsを開けてあります。

```

# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: ens3
  sources: 
  services: dhcpv6-client http https
  ports: 2222/tcp
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
```

## ローカル環境

OS：Ubuntu 20.04
Python 3.8.5

# 外部から http でアクセスする

## VPS側：SSHの設定

VPS側で、VPSからローカル環境へポートフォワードできるように、SSHのコンフィグを設定します。

```

# editor /etc/ssh/sshd_config
```

変更点は以下の通り。

```/etc/ssh/sshd_config

- GatewayPorts no
+ GatewayPorts yes
```
<font color="Red">
(2023/02/20追記)<br>
GatewayPortsをyesにするのは古いやり方で良くないそうです。<br>
まだ試せていませんが、GatewayPorts clientspecified を検討するのが良いようです。<br><br>
</font>

保存したら、sshdサービスを再起動します。

```

systemctl restart sshd.service
```

## ローカル側：動作確認用Webサーバーを立てる

以下のように、動作確認用のディレクトリ（名前は何でも良いです）を作成し、Webで公開するファイルも適当に用意し（本記事ではa.txt）、Webサーバーを立てます。
本記事では、ローカルhttpのポートは3001にしました。

```

mkdir ~/test
cd ~/test
echo "Hello world!" > a.txt
python3 -m http.server 3001
```

ローカル環境でブラウザで「 http://localhost:3001 」にWebアクセスしたら、以下のように表示されました。

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/e6b7e254-7fc5-b024-6179-39ead145d0b6.png)

## ローカルからVPSへssh接続：コマンドライン編

ローカル側の端末から以下を実行して、VPSにSSH接続します。
SSH接続がタイムアウトして自動切断されないよう、あらかじめKeep Aliveを有効にしておくと良いでしょう（その手順は省略します）。

本記事では、外部公開するhttpのポートは13001にしました。

```

ssh -o ExitOnForwardFailure=yes -p 2222 -R 13001:localhost:3001 usrname@example.com
```

## http動作確認：コマンドライン編

ブラウザで「 http://example.com:13001 」にWebアクセスしたら、以下のように表示され、外部からローカルWebサーバーにアクセスできました。

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/b5c5ea1e-c0fa-dc64-4607-dcd559417272.png)

## ローカルからVPSへssh接続：Python編

先程は端末からssh接続しましたが、同じことをPythonプログラムから行うこともできます。
端末からssh接続してあったら、それをログアウトしてからお試しください。

ローカル側で、pip 等で paramiko ライブラリをインストールします。

```

pip3 install paramiko
```

適当なディレクトリで以下を実行し、paramikoライブラリのコードを取得します。

```

git clone https://github.com/paramiko/paramiko
```

取得したコードのうち、「paramiko/demos/rforward.py」を使用します。
以下のように複製してから編集します。

```

cp paramiko/demos/rforward.py .
editor rforward.py
```

```rforward.py

（前略）

- SSH_PORT = 22
+ SSH_PORT = 2222

（中略）

    try:
        client.connect(
            server[0],
            server[1],
            username=options.user,
            key_filename=options.keyfile,
            look_for_keys=options.look_for_keys,
            password=password,
        )
+         client.get_transport().set_keepalive(60)
    except Exception as e:

（後略）
```

本記事ではVPSのSSHポートは2222ですので、ソースコードもそのように書き換えました。
また、ssh接続がタイムアウトで切れないように、keepaliveの処理を加えました。

書き換えた rforward.py を動かして、ローカル側からVPSにssh接続します。

```

python3 rforward.py example.com -p 13001 -r localhost:3001 --user usrname --key /home/usrname/.ssh/id_ed25519 --password
```

## http動作確認：Python編

ブラウザで「 http://example.com:13001 」にWebアクセスしたら、以下のように表示され、外部からローカルWebサーバーにアクセスできました。

![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/92dd7750-8e19-41a5-f7fe-a5297778d373.png)

# 外部から https でアクセスする

先程までは外部からの http をリバースポートフォワードしました。
引き続き、https でも行うことにします。
今までの手順に加えて、以下を実行します。

## VPS側で Caddy を導入する

Caddyについては、[caddyで簡単にhttpsサイト](https://qiita.com/mmotoi/items/1af28e2e62a4e344eb6c) を参考にしました（感謝）。
本記事では、Caddyのバイナリをダウンロードして使用します。

以下のページを見て、ダウンロードするバージョンを決め、ターボールのURLを調べます。
https://github.com/caddyserver/caddy/releases/

以下を実行して、ターボールをダウンロードし、適当なディレクトリて展開します。

```

cd ~/Downloads
wget https://github.com/caddyserver/caddy/releases/download/v2.2.1/caddy_2.2.1_linux_amd64.tar.gz
cd
mkdir caddy
cd caddy
tar xf ~/Downloads/caddy_2.2.1_linux_amd64.tar.gz
```

Caddyfileという名前のファイルを新規作成します。

```

editor Caddyfile
```

内容は以下の通り。

```Caddyfile

example.com

reverse_proxy 127.0.0.1:13001
```

Caddyを起動します。

```

sudo ./caddy run --config Caddyfile
```

## https動作確認

ブラウザで「 https://example.com 」にWebアクセスし、a.txtのリンクをクリックしたら、以下のように表示され、外部からhttpsでローカルWebサーバーにアクセスできました。

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/6456881a-daac-86bf-f4b6-d18efb45f667.png)

本記事の手順では、最終的に外部からhttp（ポート13001）とhttps（ポート443）の両方にアクセスできることにご注意ください。
httpへのアクセスを塞ぐ何らかの対策が必要か．．（方法は調べていません）

以上となります。