serveo.netを使ってローカルsshサーバを外部公開

# 目的

プライベートネットワークに接続されたLinuxサーバ（例：自宅のPCなど）に対して，外部ネットワークからsshができるようにする．

# 良い点・悪い点

* 👍 serveo.net独自のソフトウェアを追加でインストールする必要が無く，sshだけで完結する．
  * セキュリティ的にもリスクがわかりやすい．
* 👍 [フレッツ光のIPv6プラス環境にあるサーバは通常の22番ポートで外部公開することができないが](/nbhr/items/2850ccaa3456a8553bbf)，serveo.netを使えば22番で待ち受けることができる．
* 👎 serveo.net経由で接続を確立した後に，直接通信に切り替えることができない．つまりNAT Traversalとか難しいことをしてくれるわけではない．
* 👎 ポート443番で公開するためには自前のサーバが必要（らしい）．

# お断り

* この記事の内容は有用であると願って記述してありますが**全くの無保証**です．
* 外部からアクセスできるということは，必然的に**セキュリティリスクが高まります**．

# 用語

説明上，以下のように用語を定義する．

* **ローカルサーバ**：外部からssh接続を受け付けたいLinuxサーバ（例：自宅のPC）
* **リモートPC**：ローカルサーバにssh接続を行うPC（例：外出時のノートPC）


# 基本的な使い方の確認 / 一時的に使う場合

基本的な考え方はssh over ssh（[公式ドキュメントへのリンク](http://serveo.net/blog/remote-access-to-ssh-servers-using-serveo)）．つまり

> ローカルサーバから[serveo.net](https://serveo.net)にsshを行い，sshのリモートフォーワード機能を使ってserveo.netの22番（何番でもよい）をローカルサーバの22番（ローカルサーバのsshがlistenしているなら何番でもよい）に転送する＝sshトンネルを作る．このトンネルを通して，リモートPCがローカルサーバにssh接続を行う

したがってもっとも単純には

```
$ ssh -R 10022:localhost:22 serveo.net
```

とすると

```
Forwarding TCP connections from serveo.net:10022
Press g to start a GUI session and ctrl-c to quit.
```

と出力されたままになるので，リモートPCから

```
$ ssh serveo.net -p 10022
```

とすればsshログインに進むことができる．なお最初にserveo.netにsshする時点で，sshキーファイルを持っている（かつssh-agentが動いていない）場合はパスフレーズの入力が促されるが，sshキーは必要ない．

# 固定アドレス化

## `serveo.net`のサブドメイン

まずsshで指定するリモートアドレスにホスト名（以下の例では`hogehoge.serveo.net`）を入れて接続を張る．

```
ssh -R hogehoge.serveo.net:22:localhost:22 serveo.net
Forwarding SSH traffic from alias "hogehoge.serveo.net"
Press g to start a GUI session and ctrl-c to quit.
```

続けてリモートPCから

```
$ ssh -J serveo.net hogehoge.serveo.net
```

のようにjump hostとして`serveo.net`を指定して接続すると転送される．

## カスタムドメイン

自分で管理しているドメイン（あるいは適当なdynamic dnsサービス）がある場合，AレコードとTXTレコードを設定することでカスタムドメインを使うことができる．

* Aレコード：`159.89.214.31`
* TXTレコード：authkeyfp=`fingerprint`

sshキーのfingerprintは，`ssh-keygen -l`で表示することができる．たとえば

```
$ ssh-keygen -lf ~/.ssh/id_ed25519
256 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ed25519-key-???????? (ED25519)
```

と表示されたならば`SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`の部分のことなので，TXTレコードに

```
authkeyfp=SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

という文字列を設定する．あとは先ほどと同様に

```
$ ssh -R 設定したホスト名:22:localhost:22 serveo.net
```

としておいたうえで，jump hostを使って

```
$ ssh -J serveo.net 設定したホスト名
```

とすれば外部から接続できる．ただし外部からの接続時には上記のsshキーが必ず必要になる．


# 長期的に使う場合

## SSHクライアントの設定

jump hostの設定をコマンドラインで毎回指定することが煩わしい場合，`~/.ssh/config`に

```
Host 設定したホスト名
    ProxyJump serveo.net
    IdentityFile ~/.ssh/id_ed25519
```

のようにホスト名に紐づけられたオプションを指定しておくことができる．さらに

```
Host serveo.net
    PubkeyAuthentication no
```

も追加しておくと，最初にserveo.netに接続する際に意味なく`Enter passphrase for key ...`と尋ねられることなくserveo.netへの接続が完了する．したがってこれら両方を設定しておくと，

1. まずJumpホストである serveo.net にパスワード無しで接続が確立する
2. 続いてローカルサーバには指定した公開鍵で接続する＝ssh-agentで自動ログインできる

とできるため，（必要であれば）パスワード入力なしでログインすることができる．

## SSHサーバの設定

外部からssh接続される際にパスワード認証を無効化し，必ず公開鍵認証を行うことにしておけば多少はセキュリティがましになる．

もっとも簡単には，すべてのssh接続で公開鍵を必須にすればよい．すなわち`/etc/ssh/sshd_config`で

```
ChallengeResponseAuthentication no
GSSAPIAuthentication no
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
```

とする．ローカル接続には公開鍵を求めない場合，**末尾に**

```
Match Address 192.168.0.0/24
        PasswordAuthentication yes
```

のように`Match`ディレクティブを書くことで対応できる（が，すべて公開鍵だけにしておくほうが良い）．

あるいはデフォルトではパスワード認証を許可して，serveoからの通信だけを公開鍵必須にするには，通常の22番に加えて2222番など適当なアドレスもbindしておき，serveoからはこの2222番に転送することにして，

```
PasswordAuthentication yes

Match LocalPort !22
        PasswordAuthentication no
```

などとすることもできる（が，すべて公開鍵だけにしておくほうが良い）．なおもともと

```
$ ssh -R 設定したホスト名:22:localhost:22 serveo.net
```

のようにserveoに張ったsshトンネルが`localhost:22`に接続されているなら，sshサーバにとってはlocalhostからのssh接続に見えるはず．したがって

```
Match Host localhost
        ....
```

という設定でserveo経由か否かをチェックすることもできる（気がする．未確認）．

## 確認

ここまでで一度はリモートPCからserveo.net経由でのsshを試みて，わざと公開鍵のパスフレーズを入れなかった場合にssh接続が拒否されることを必ず確認すること．パスワードをキーボード入力するように求めてきた場合は，上記の設定ができておらず，だれでも総当たりでパスワード入力を試みることができること，を意味している．

またserveo.net経由で接続する場合，必ずホスト鍵のfingerprintが正しくローカルサーバのものであることを確認すること．これが正しい限り，sshパケットがserveo.netを経由したとしても中身が漏洩するわけではないことが保証される．


## Systemd + AutoSSHの設定

以下の内容で`/etc/systemd/system/autossh-serveo.service`というファイルを作成する．

```
[Unit]
Description=AutoSSH serveo
After=network.target

[Service]
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -M 0 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o PubkeyAuthentication=no -N -n -R 設定したホスト名:22:localhost:22 serveo.net

[Install]
WantedBy=multi-user.target
```

ここで

* `ServerAliveInterval=30`は暗号化されたssh通信路を使って30秒ごとに死活監視パケットを送る
* `PubkeyAuthentication=no`はserveo.netへの接続では公開鍵を使わない（不要）であることを明示する

という意味．

```
$ sudo systemctl start autossh-serveo
$ sudo systemctl status autossh-serveo
```

で問題が無いか確認し，

```
$ sudo systemctl enable autossh-serveo
```

で自動起動するように設定すればssh接続が維持される．起動に問題がある場合は`ExecStart`に指定したコマンドをそのままrootユーザで実行してみるとエラーが分かりやすい．

# 考察

**この部分は全くの推測です．こうなるだろうなと考えて書いてはいますが，一切検証していません．**

## 安全性

まず最初にserveo.netに行うssh通信，つまり上記の例では`serveo.net:10022`からローカルサーバまでのリモートフォーワードトンネルは安全性が保障されない．なぜならばserveo.net自身が中間者であり，このsshトンネル上を流れるすべてのパケットを読むことができるから．

しかしこのsshトンネル上でリモートPCからローカルサーバへの別のssh接続を行った場合は，このssh接続そのものが通信の安全性を担保してくれる．つまりトンネル上を流れる別のsshパケットを読むことができたとしても，その内容をうかがい知ることはできない．

## 高速化

serveo.net経由でのssh-over-sshとなるため，scpやrsyncで大きなファイルを転送する際にはあまり回線速度が期待できない．この問題を回避するには

* 直接接続に切り替える
* クラウドを介したコピーを行う

などの方法が考えられる．

### 直接接続への切り替え

一旦serveo.net経由でローカルサーバにsshできたとする．ここでローカルサーバからリモートPCへと直接sshを行えば，素直にscp / rsyncを行うことができる．ただしこの方法はリモートPCが外部からssh接続を受け付けることができることを仮定している．

また端末としての操作はserveo.net経由となるためラグが生じる場合が残る．これをどうしても回避するには，ローカルサーバからリモートPCに直接sshする際にリモートフォーワードを設定し，このsshトンネルを使って改めてリモートPCからローカルサーバへとssh-over-sshすればserveo.netを介さないので多少は高速になると期待できる．つまりリモートPCがserveo.netの役割も兼ねるようにする．

### クラウドを介したコピー

もしserveo.net経由の通信よりもローカルサーバ，リモートPCそれぞれからGoogle Driveのようなクラウドストレージサービスへの通信のほうが高速であれば，[skicka](https://github.com/google/skicka)や[rclone](https://rclone.org/)を使うことで高速にファイルをコピーすることができる．

# そのほか

* [sslh](https://github.com/yrutschle/sslh)を用いてSSL通信のように見せることで，外部ネットワーク（例えば公衆WiFi）がsshポートの通信を許さない，あるいは単純に443番でsshを待ち受けてもSSL通信か否かをチェックして弾いてしまう，などの問題に対応できる．
  * この場合は自前のserveoサーバを立てる必要があるようだ（[公式blog](http://serveo.net/blog/remote-access-to-ssh-servers-using-serveo)の「Remote Internet of Things Device Management」の項）．
* ssh接続へのアタックが多い場合，`sshguard`をインストールすれば多少は数を減らすことができる．

# 関連するソフトウェア，サービス
* [ngrok](https://ngrok.com/)
  * TCP通信を外部公開
* [tmate](https://tmate.io/)
  * tmuxのセッションを外部からssh接続可能にする