Edited at

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


目的

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


良い点・悪い点


  • 👍 serveo.net独自のソフトウェアを追加でインストールする必要が無く,sshだけで完結する.


    • セキュリティ的にもリスクがわかりやすい.



  • 👍 フレッツ光のIPv6プラス環境にあるサーバは通常の22番ポートで外部公開することができないが,serveo.netを使えば22番で待ち受けることができる.

  • 👎 serveo.net経由で接続を確立した後に,直接通信に切り替えることができない.つまりNAT Traversalとか難しいことをしてくれるわけではない.

  • 👎 ポート443番で公開するためには自前のサーバが必要(らしい).


お断り


  • この記事の内容は有用であると願って記述してありますが全くの無保証です.

  • 外部からアクセスできるということは,必然的にセキュリティリスクが高まります


用語

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



  • ローカルサーバ:外部からssh接続を受け付けたいLinuxサーバ(例:自宅のPC)


  • リモートPC:ローカルサーバにssh接続を行うPC(例:外出時のノートPC)


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

基本的な考え方はssh over ssh(公式ドキュメントへのリンク).つまり


ローカルサーバから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のようなクラウドストレージサービスへの通信のほうが高速であれば,skickarcloneを使うことで高速にファイルをコピーすることができる.


そのほか



  • sslhを用いてSSL通信のように見せることで,外部ネットワーク(例えば公衆WiFi)がsshポートの通信を許さない,あるいは単純に443番でsshを待ち受けてもSSL通信か否かをチェックして弾いてしまう,などの問題に対応できる.


    • この場合は自前のserveoサーバを立てる必要があるようだ(公式blogの「Remote Internet of Things Device Management」の項).



  • ssh接続へのアタックが多い場合,sshguardをインストールすれば多少は数を減らすことができる.


関連するソフトウェア,サービス



  • ngrok


    • TCP通信を外部公開




  • tmate


    • tmuxのセッションを外部からssh接続可能にする