【Ubuntu20.04】ConoHa VPSでSSHポートフォワーディング(リモートフォワード)を実現

1.前置き

MCPCのIoTシステム技術検定中級の受験も終わり(追記:無事受かりました)、前からやろうと思っていた、Alexa(Echo Flex)による自宅照明の操作を実現したくなったので、今回はその準備を行うことにしました。
 
Alexaから自宅照明をつけるには、まず自宅サーバを作る必要がある。サーバで用意しておいたAPIの受信をトリガにして、赤外線モジュールを利用するようにすればいい。
そこらへんは昔人感センサで作った時にもまとめたので、それの通りにやれば問題ない。
engetu21.hatenablog.com
 
で、Alexaの方は自作スキルをPythonで作成し(これは今度まとめる)、その自作スキルから用意した自宅サーバAPIにHTTPを飛ばすようにしてあげればいい、となります。
→まとめました。
engetu21.hatenablog.com

 
で、自宅サーバの公開方法をあれこれと考え、結局採用しようと思ったのがVPSを経由したSSHポートフォワーディング(リモートフォワード)による接続。
今思うと、直接自宅サーバ公開したほうが手間なかったのでは?と思わなくないけど、ルータの設定が不要になるといった部分のメリットはある。
【追記】でも後述のようにVPSへのIPTABLES設定とかautosshとかが必要なので、手間はやっぱりかかった。

手軽にローカルサーバを簡単に展開するの手段として、ngrok(エングロック)がいいというのは調査して見つけたけど、どうも有料で利用しないとドメインころころ変わるみたいだし、だったらVPS借りてSSHポートフォワーディングできるようにしつつ、他の用途にも使えるようにした方がいいわ、となりました。
Alexaからのアクセス元IPアドレスが分かれば、それで接続元を絞り込むつもりだけど、そこは今後の調整。→どうやらIPアドレスはコロコロ変わるようなので、接続元を絞ることは不可能な模様。
 
SSHポートフォワーディングのイメージは以下の通り。

暫定の形として、インターネットに接続できるブラウザからConoHa VPSで提供されているホストかIPアドレス+ポート番号50001にアクセスする。
ConoHa VPSとラズパイ3は事前にSSHポートフォワーディングによるSSHトンネルができており、アクセスされた通信がラズパイ3のポート8080に転送される仕組み。
Alexaの自作スキルの準備が整えば、このブラウザ部分がEcho Flex(より正確にはその先のAlexaのサーバ)になる。
 
肝となるのは、ConoHa VPSとラズパイ3で事前にSSHトンネルを作っておくというところで、これはConoHa VPSSSHサーバが入っている前提であり、かつファイアーウォール設定などを設けておく必要がある。というわけで設定内容を以下に記載。

2.ConoHa VPSでの設定

2.1 SSHサーバの設定変更

$ sudo vi /etc/ssh/sshd_config
#GatewayPorts no

GatewayPorts yes

PasswordAuthentication yes

PasswordAuthentication no

【追記:どうも時間が経つとSSHトンネリングが切れるので、以下の設定を追加しておく】
#ClientAliveInterval 0

ClientAliveInterval 120

#ClientAliveCountMax 3

ClientAliveCountMax 3

GatewayPortsは今回の対応でyesにするのが必須です。いくつかのサイトを参考にしましたが、これをちゃんと書いてるところが少なく、繋がらない原因がわからず時間がかかりました。バインド設定の変更(要するに転送を許可)するかどうか、の設定らしい。
パスワードの認証は不要とします。そもそも公開鍵認証で接続するため、わざわざパスフレーズを入れなくていい。

2.2 ポートの開放

ConoHa VPSでは、ポータルでいくつかのポート設定ができますが、今回の対応にてこれは使用しないように(全て許可に変更)します。
また、OSはUbuntu20.04を使用しているため、マシン側でIPTABLESを使ってIPパケットフィルタリングによる通信制御をします。
 
まずはIPTABLESの設定を永続化(再起動しても同一の設定を受け継ぎ)するように、iptables-persistentをインストールします。

$ sudo apt install iptables-persistent

 
インストール後、IPTABLESを構築するシェルスクリプトを作ります。
IPTABLESコマンドを逐次叩いてもいいけど、シェルスクリプトを作って一気に構築できるようにしておいた方がいいです。
昔からシェルスクリプトで作ってたので今回もそうしてますが、Pythonで作っても問題ない。

$ sudo vi iptables.sh
#! /bin/bash
echo "### iptables_sh起動 ###"

# $IPTABLESパス
IPTABLES='/sbin/iptables'

#自宅のIPアドレス or あるんだったらドメイン
zitaku='hogehoge.com'

# 最初にすべてのルールをクリア
$IPTABLES -F # テーブル初期化
$IPTABLES -Z # チェーンを削除
$IPTABLES -X # パケットカウンタ・バイトカウンタをクリア
$IPTABLES -t nat -F #natテーブルを指定と初期化

# 内部から外に接続した通信に対する、外部からの応答アクセスを許可 #
$IPTABLES -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
#$IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# 自宅からの22番ポート(SSH)へのアクセスを許可 #
$IPTABLES -A INPUT -p tcp --dport 22 -s $zitaku -j ACCEPT
$IPTABLES -A OUTPUT -p tcp --sport 22 -d $zitaku -j ACCEPT

# 外部から50001番ポート接続許可 #
$IPTABLES -A INPUT -p tcp --dport 50001 -j ACCEPT
$IPTABLES -A OUTPUT -p tcp --sport 50001 -j ACCEPT

# 設定ルール外のINPUT/FORWARDアクセスはログを記録して破棄。OUTPUTは基本的に許可 #
$IPTABLES -A INPUT -j LOG --log-prefix '[iptables INPUT DROP] '
$IPTABLES -A INPUT -j DROP
$IPTABLES -A FORWARD -j LOG --log-prefix '[iptables FORWARD DROP] '
$IPTABLES -A FORWARD -j DROP
#$IPTABLES -A OUTPUT -j LOG --log-prefix '[iptables OUTPUT DROP] '
$IPTABLES -A OUTPUT -j ACCEPT

# 設定の反映 #
sudo /sbin/iptables-save

# 設定の永続化 #
sudo /etc/init.d/netfilter-persistent save

echo "### すべての設定完了 ###"

 
作成後、パーミッションを変更し、実行

$ chmod 755 iptahles.sh
$ sudo ./iptahles.sh

設定の反映を確認

$ sudo iptables -nL

 
マシン側でパケットフィルタリングをしたため、ConoHa VPSの設定をポータルで変更します。
接続許可ポートを「全て許可」に変更。

 

3.ラズパイ3での設定

設定というか以下のSSHコマンドを打ちます。

$ sudo ssh -l hoge xxxxxxxxxxx.budb.static.cnode.io -p 22 -i conoha_ssh_key.pem -R 50001:127.0.0.1:8080 -T -N

それぞれのオプションについては以下の通りです。

-l hoge:ConoHa VPSに接続するSSHのユーザ名
xxxxxxxxxxx.budb.static.cnode.io:ConoHa VPSのドメイン名。IPアドレスは固定らしいので、そちらを指定してもいい
-p 22:ConoHa VPSに接続するSSHのポート番号
-i conoha_ssh_key.pem:ConoHa VPSに接続するSSHの公開鍵(VPSを作る際に作ったものを指定)
-T:仮想端末の割り当てを禁止
-N:リモートコマンドを無効 (-T -NがないとConoHa VPSにSSHログインする感じになるので、これを抑止)
-R 50001:localhost:8080:リモートフォワードの設定。ConoHa VPSでポート50001に来たものをlocalhost(自分)の8080ポートに転送する設定。

このコマンドによって、常にSSHトンネルがラズパイ3とConoHa VPS間で構築されていることになります。
動かしっぱなしにする場合は、screenを利用したり、Pythonプログラムでコマンドを実行するように作り、Pythonプログラムは自動起動にしておく必要があります。
 
 
【2022/07/05追記】

4.autosshとsystemdによる自動化

どうやら常時接続するためにautosshなるものがあるらしい。なんて便利な。
ただ、autosshを利用していても、そのプロセスそのものがお亡くなりになってしまうこともあるよう(指定した回数分再接続に失敗したりするとautosshは停止するらしい)で、autosshをsystemdで管理することで完全に自動にすることができる模様。
参考:autosshをサービス化してSSH接続を強化 - RemoteRoom

とりあえず、以下の順で準備していきます。

4.1 autosshのインストール

$ sudo apt update
$ sudo apt install autossh

4.2 systemdへの登録

以下のGitHubにあるソースを使います。
Systemd service for autossh · GitHub
 
以下のコマンドを打って、ファイルを取得すると共にファイルの格納も行います。
Githubでは「sudo tee /etc/default/autossh@example」となっていますが、ここは
「sudo tee /etc/default/host1」に変更して実行。

curl -sSL https://gist.githubusercontent.com/ttimasdf/ef739670ac5d627981c5695adf4c8f98/raw/autossh@host1 | \
sudo tee /etc/default/autossh@host1
curl -sSL https://gist.githubusercontent.com/ttimasdf/ef739670ac5d627981c5695adf4c8f98/raw/autossh@.service | \
sudo tee /etc/systemd/system/autossh@.service

/etc/systemd/system/autossh@.service
については、内容を変更する必要はなし。
 
/etc/default/autossh@host1については、以下のように中身を変更する。

$ sudo vi /etc/default/autossh@host1
 
以下の設定値を自分の環境に応じて変更。
TARGET_HOST=host1
FORWARDS=-R 50001:127.0.0.1:8080

 
※TARGET_HOSTは後述するsshのconfigファイルに設定するホスト名を指定します。
また、ポートフォワードは-L(と-g?)でもできるらしい?ですが、これはまぁ実施する(サーバの)場所によって異なるようなので、今回の場合は-Rに書き換えます。
詳しくはこちら。
混乱しがちな「SSHトンネルの確立方法」をイメージ図とセットでまとめたコマンド集 - GIGAZINE

 

4.3 SSHポートフォワーディング用のユーザを作成

GitHubの手順に則って以下のように実行します。

$ sudo useradd -g nogroup -s /bin/false -m tunnel
$ sudo -u tunnel mkdir -p ~tunnel/.ssh

 

4.4 ssh-cofigファイルの作成

最近のSSHでは、configファイルを作って、その中に接続先などを記載して楽できるらしい。autosshを実行するためにも必要なので作成します。

$ sudo -u tunnel vi ~tunnel/.ssh/config

Host host1
    HostName xxxxxxxxxxx.budb.static.cnode.io
    User hoge 
    Port 22
    IdentityFile ~tunnel/.ssh/conoha_ssh_key.pem

※Host行で設定した名称(ここではhost1)がsshコマンドの際に指定するホスト名になります。
※IdentityFile で秘密鍵ファイルを指定

秘密鍵については、権限を600にしておかないとパーミッションエラーになるので注意。

$ sudo chmod 600 ~tunnel/.ssh/conoha_ssh_key.pem

 

4.5 あらかじめsshログインを行う

known_hostsファイルが作られていないといけないので、手動で初回SSH公開鍵認証を行います。ちゃんと接続できるかの確認も含め、いずれにせよ実施は必須。

$ sudo -u tunnel ssh host1

※yesかnoを聞かれるのでyes

 

4.6 systemdにてautossh実行

/etc/default/autossh@host1
の名称でファイルを作ったため、今回は以下のように実行します。
startで実行。enableで永続化です。

$sudo systemctl enable autossh@host1.service
$sudo systemctl start autossh@host1.service

● autossh@host1.service - Keeps an ssh tunnel to host1 open
Loaded: loaded (/etc/systemd/system/autossh@.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2022-07-05 23:59:28 JST; 10min ago
Main PID: 9721 (autossh)
Tasks: 2 (limit: 2059)
CGroup: /system.slice/system-autossh.slice/autossh@host1.service
tq9721 /usr/lib/autossh/autossh -NT -o ExitOnForwardFailure=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=3 host1 -R 50001:127.0.0.1:8080
mq9724 /usr/bin/ssh -NT -o ExitOnForwardFailure=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=3 -R 50001:127.0.0.1:8080 host1

7月 05 23:59:28 raspberrypi systemd[1]: Started Keeps an ssh tunnel to host1 open.
7月 05 23:59:28 raspberrypi autossh[9721]: port set to 0, monitoring disabled
7月 05 23:59:28 raspberrypi autossh[9721]: starting ssh (count 1)
7月 05 23:59:28 raspberrypi autossh[9721]: ssh child pid is 9724