SSHトンネルでLAN内のAndroid TVに外部からアクセス

Inside of A Colorful Tunnelいずれは他人様のお宅に設置される予定のAndroid TV準拠ストリーミング・メディア・プレーヤAmigo 7xJPを外部からアクセスできるようにする必要がある。eo光をご使用中なので,eo光多機能ルーター eo-RT100下のLAN内に設置されることになる。既にTeamViewerを介して画面共有・操作できるようにし不便ながらも遠隔で文字入力できるようにしてあるが,これはいわば最終手段。

すっかり忘れていたが,ほぼ6年前にeo-RT100について少し調べていた。まだ現役とは…。もっとも10ギガ対応のeo-RT150(N) / eo-RT200(N)も今は用意されているようだが。

SSHアクセスだけについては,eo-RT100のポートマッピング機能で特定ポートからAmigo 7xJPで走るsshdのポート(8022)に転送することも考えてあり,それができるためにTermuxからBashスクリプトでダイナミックDNSレコードを自動的に更新するように設定した。残りは現地に赴いて作業する必要がある。現時点ではルータの設定を遠隔に変更するすべがないので。

しかしSSHアクセスだけでは不十分で,Amigo 7xJPにおいてTermuxでインストールした,MQTTブローカやNode-Redのサービスにも外部からアクセスできるようになることが必要。それはSSHトンネリングで実現することにする。ちなみに,私ももちろんアクセスする予定だが,他人様が(私が設定した)自身のスマホでアクセスすることも前提。

基本的な考え方は,インターネット上に自分がいじれるSSHサーバがあればそれを利用してトンネルが張れる,ということ。Node-REDで遊ぶためにOracleの無料VPSサービスを既にセットアップしてあり,そこにSshdもインストールしてあるのでこれを利用する。以下簡単のためこれを「SSHサーバ」と呼ぶことにする。

クライアント上でのsshコマンド

How to Use SSH Tunneling to Access Restricted Servers and Browse Securely,” “SSH/OpenSSH/PortForwarding – Community Help Wiki” などに説明がある3種のSSHのトンネリングのうち,Remote Port Forwardingのケース(Dynamic Port Forwardingはよくわかってない)。で,今の例に即して言うと,基本的にはAmigo 7xJP上で

ssh -R <SSHサーバのポート>:localhost:<localhostのポート> <ユーザ名>@<SSHサーバ>

とするだけ。正確に言うと,<SSHサーバのポート>とはこのトンネルで使用する,SSHサーバが走るホストのポート。<localhostのポート>も同様で,今トンネルで接続しようとしている,localhostで走るサービスで使用しているポート。

-R オプションは必要なだけ単一のsshコマンドに追加できる…と思うのだが未確認。-L オプションではできる。

また,ルータの設定画面自身を遠隔でアクセスできるようにしたいが,そのためには以下の-Rオプションを上と同じsshコマンドに追加すればよいはず。

-R <SSHサーバのポート>:<ルータのアドレス>:<ルータのWeb UIポート(たぶん80)>

SSHサーバーの走るホスト側の設定

SSHサーバの設定

SSHサーバ(この場合OracleのVPS)側の設定も必要。 “A Guide to SSH Port Forwarding/Tunnelling – Boolean World,” “SSH/OpenSSH/Configuring – Community Help Wiki” などを参照。

/etc/ssh/sshd_conf 中で,コメントアウトされていた以下を有効化。

AllowTcpForwarding yes

以下はコメントアウトされていた上に値がnoになっていたと思うがこれもyesにした上で有効化。

GatewayPorts yes

SSHトンネリングに使うアカウントの作成と設定

linux – How to restrict an SSH user to only allow SSH-tunneling? – Unix & Linux Stack Exchange” によると上だけではセキュリティーの観点からは不十分でSSHトンネリングに使う専用アカウントを作成ししかるべく設定したほうがいいようだ。

sudo useradd sshtunnel -m -d /home/sshtunnel -s /bin/true

でsshtunnelユーザを作成しSSHトンネリングはこれを使う。シェルとして/bin/trueを指定するのは意味のあるシェルが起動できないようにするため。が, /etc/passwd 見てみると /usr/sbin/nologin が指定されているアカウントが多くある。こちらの方がより目的が明示的かもしれない。

この場合クライアントからsshを起動する際は -N オプションを付けてそもそもシェルを起動しようとしないようにする必要がある。/bin/trueが起動するとすぐ終了してしまう。さらに -f オプションでバックグラウンド実行

Amigo 7xJPからSSHサーバの走るホストで,トンネリングのために新規作成したユーザでのSSHでのログインがパスワードレスでできるように

このsshtunnelユーザのアカウントでSSHでリモートからログインできるようにするには “Adding Users on an Instance” を参照する。VPSにはもう初期設定したマシンからしかSSHログインできないようになっておりパスワードによるログインはできないので,Amigo 7xJPからパスワードレスでログインできるために必要な公開鍵は,一旦ubuntuユーザとして受け取ってsshtunnelユーザのために設定してやることになる。

ポートのマッピング関係

Registered/user portsは1024–49151なのでこの中から選ぶ。

  • ルータのWeb UI IPアドレス: 80? ⇦ XXXX0
  • ssh 8022 ⇦ XXXX1
  • MQTT 1883 ⇦ XXXX2
  • Node-RED 1880 ⇦ XXXX3

SSHサーバが走るホストのポートで接続を受け付けるようにする

ここで引っかかった。 “Free Tier: Install Apache and PHP on an Ubuntu Instance” にあるように,3. Enable Internet Access でingressルールを,4. Set up Apache and PHPでiptablesを編集する,の2つの双方ともしなければならない,というのが引っかかりやすいところかと。どちらもポートはレンジ指定が可。Iptables であれば --dport port_beginning:port_end のフォーマット。Iptablesの操作にしくじったら, “How To List and Delete Iptables Firewall Rules | DigitalOcean” を参照して修正。

Oracle Cloudが永遠に無料らしいので開発環境を作ってみたかった (ネットワーク編) – Qiita” に丁寧な説明があるんだがそれすらよくわかってない。

ちなみに,HTTP用ポート80が開いてるのはいけないのじゃないかと思ったが,Igress Rulesで “HTTP port for HTTPS cert” というコメントが付いているし,HTTPSの通信に入る前に証明書の送受信するのにHTTPが使われてるってことか。また,iptables でポート10000を指定してみると webmin と表示されるので不審に思ったが,Webminデフォルトポートが10000だからのようだ。

タイムアウト等でトンネルが壊れてしまうことの対策

SSHサーバのセッションのタイムアウト設定はトンネルにも適用されるようで,一定時間後トンネルを介した接続は何の反応もしなくなってしまった。/etc/ssh/sshd_configでClientAliveCountMaxの値を0に設定することでタイムアウトしないようにした。この判断が特にこの後導入したautosshの使用を前提にしたときに正解なのかはよくわからない。

Oracle CloudのVPSで基本SSHサーバは常時起動しているはずであるが,稀有な偶然でたまたま起動していないときに,クライアント側でSSHトンネルを作成しようとしても失敗する。現時点では自動的にリトライを試みるなどのしかけは用意していないので,一度失敗すれば失敗したきり。その場合にはAmigo 7xJPを再起動するのが簡単な解決策になる。

しかし “linux – How to reliably keep an SSH tunnel open? – Super User” でautosshREADME.txt; マニュアル)を知った。幸いにしてTermuxでもaptパッケージとして用意されているので簡単にインストールして利用できる。

AutosshのREADME.txtでは-Mオプションについて以下の記載がある。

-M 0 will turn the monitoring off, and autossh will only restart ssh on ssh exit.

For example, if you are using a recent version of OpenSSH, you may wish to explore using the ServerAliveInterval and ServerAliveCountMax options to have the SSH client exit if it finds itself no longer connected to the server. In many ways this may be a better solution than the monitoring port.

デフォルトでautosshは-Mオプションで指定されたポートとそれより1大きい数字のポートを利用してサーバとメッセージのやり取りをして接続状態をモニターする。しかし上の記載にあるようにsshクライアント自身の同様な目的の機能を使うほうがよさそうだ。”SSH tunnelling for fun and profit: Autossh” にも同じ趣旨の記述がある。上で見たようにOracle CloudのVPSでは通信用ポートを開けるのは手間であるし。

デフォルトではServerAliveCountIntervalの値は0になっていて,この機能は無効化されているので, /etc/ssh/ssh_config でこの値を15に変更する(Termuxでのディレクトリは正確には /data/data/com.termux/files/usr/etc/ssh )。ServerAliveCountMaxはデフォルトで3だそうなので,sshクライアントは45秒サーバと更新できなければ自分で落ちるはず。

Termuxのssh_configはもともと1行しかなく,上記を追加すると以下の内容になる(ServerAliveCountMaxの値も念のため設定)。

SendEnv LANG
# Yasuro -- the following 2 lines
ServerAliveInterval 15
ServerAliveCountMax 3

Autosshはsshコマンドのオプションもそのまま受け入れる。 “SSH tunnelling for fun and profit: Autossh” で使われているsshコマンドの-TオプションはDisable pseudo-terminal allocationするらしいのでこれも取り入れる。

以上をやってもSSHトンネル経由でリモートからAmigo 7xJPにsshログインした後,暫く経つと固まってしまう。何かしらキーを叩くと1分後ぐらいにsshは終了する。しかしプロセスリストを見るとその前のログイン時に起動されたとおぼわしき sshd, bash プロセスが残っていて(以下のような),いつまでもそれが終了されない。固まる前にいわば自主的に exit したような場合はこの問題は起こらない。

u0_a87    7954  0.0  0.2  10828  4676 ?        Ss    1970   0:00 /data/data/com.termux/files/usr/bin/sshd -R
u0_a87    7955  0.0  0.1   9652  3656 pts/0    Ss+   1970   0:00 /data/data/com.termux/files/usr/bin/bash -l

現時点でこの問題は解消していない。

Windows上でのSSHに使う鍵管理

SSH/OpenSSH/Keys – Community Help Wiki,” “SSH公開鍵認証で接続するまで – Qiita” にLinux環境での標準的な鍵管理が出ている。Windowsでは工夫が必要。ssh-copy-idは用意されてないし,SSHに使うキーを保存する標準的場所(ディレクトリ,フォルダ)が決まっているわけではないので,秘密鍵ファイルへのパスをを常に明示的にsshのコマンドラインオプシンとして与えなければならないのは不便。

なぜNgrokが使えないと判断したか

実はSSHトンネリングなど全く知らなかった。Termuxのwiki中の “Bypassing NAT – Termux Wiki” でNgrokというサービスを知った。それがLAN内のサービスを外部からアクセスできるようにするのに使用できるので好都合。無料プランでは「1つのオンラインNgrokプロセス(まで)」という制約があるが,この1つのプロセスで複数のトンネルが作成できる

無料でNgrokを使う場合セッションは最大8時間という制限があるとか,いやアカウントを作成していれば無料アカウントでもその制限は外れる,などの記載を見たが,真偽は確認していない。しかし,それよりももっと問題なのは以下(斜体は筆者による):

On the free plan, ngrok’s URLs are randomly generated and temporary. If you want to use the same URL every time, you need to upgrade to a paid plan so that you can use the subdomain option for a stable URL with HTTP or TLS tunnels and the remote-addr option for a stable address with TCP tunnels.

他人様がそのスマホからAmigo 7xJPをアクセスすることも前提にしているため,URLが変わってしまうのでは事実上使い物にならない。

Roll your own ngrok in 15 minutes” を読んで,実はインターネット上に自分がいじれるSSHサーバがあればSSHトンネリングを自分で実現できるとわかってそれが基本方針となった。その場合URLが変わるとかいった問題は起こらない。一説ではNgrokの元になったLocaltunnel ~ Expose yourself to the worldが現在のところ完全無料だが,外部からのアクセスのためのURLは毎回変わるので自分の用途には適さない。ちなみに,Ngrokと似たような機能を提供するGoによる実装を紹介する “Building your own Ngrok in 130 lines – DEV Community” の作者は自分が出発点となるアイディアを考え実装したと主張している。

以下Ngrokで行くことを前提に調査していたときのもの。未整理。HTTP上のトンネリングだけサポートしていると誤解していたときに書いたものもあるので注意。


Accessing localhost from Anywhere – SitePoint

How to install Ngrok in Termux best method in 2021 – Technical Sayan

MQTT Clientアプリはwebsocketを利用した通信にも対応している。Mi 体組成計2のデータをMQTTブローカにあげるのに使用を検討しているOpenScale SyncMQTT 3.1(5.xではなく)にしか対応しておらず,websocketにも対応していない。ただ,この通信は自宅LAN内でしか起こらないはずなので,ブローカのIPアドレスなりホスト名なりが固定でアクセスできる限り,素のMQTTの通信さえできれば用は足りる。

SSHであればWeb-based SSH – Wikipedia GitHub – shellinabox/shellinabox: Official-ish Fork of Shell In A Box はTermuxでインストールできる。

SSH接続をWebブラウザの純粋なHTTP上で実現する – nwtgck / Ryo Ota

Ngrok – secure introspectable tunnels to localhost Windows, MacOS, Linux用のバイナリが用意されている

Ngrokの公式クライアントはオープンソースではないのでTermuxは使用を推奨していないが,それを利用するレシピーは見当たる。

termux install ngrok – Google Search

ngrok – npm

@667/ngrok-dns – npm

node-red-contrib-ngrok (node) – Node-RED

Getting Started with Ngrok in Node-RED » Developer Content from Vonage ♥

local環境のNode-REDにngrokが頼もしすぎる – Qiita

Remote-RED for Node-RED Tutorial 1 – Remote Access – YouTube” は興味深いが,Node-REDに特化した解で,しかも遠隔に見えるのはダッシュボードのみ。