🖥️

ngrok代替としてのSSH Portforward

2024/04/23に公開

本記事は、2022年12月3日に公開済みの記事を移行して再掲載したものです。

はじめに

Qiita - フェズ Advent Calendar 2022 3日目の記事です。

2日目に引き続き、イノシシ対策エンジニア兼サーバーサイドエンジニアの中川が担当します。
イノシシ記事では一切コードを出さなかったので、イノシシ監視システムで便利に使っているTips周りを少しだけ書きたいと思います。

TL; DR

ローカル開発環境や自宅サーバー等をインターネットに公開する方法としての ssh portforward のやり方です。コマンドだけ確認したい方は「具体的なコマンド」まで読み飛ばしてください。

自宅サーバーの公開

枯れた技術で何一つ目新しい部分はありませんが「ngrok をよく使っていて、課金もしています」という方には、もしかしたら参考になる部分もあるかもしれません。

具体的に何をやったかというと、LAN上に構築した自宅サーバー群を ssh portforward を用いて、スマホや外出先からでもアクセスできるようにインターネット側に公開した、というお話です。

インターネットへの公開

例えば、ローカルで開発しているWebアプリを他のひとに見てもらうために、ngrok を使ってインターネットに公開した、というケースがあるかと思います。

ngrokは、インストールも使い始めるのも非常に簡単で、サクッとインターネット側にローカルのWebアプリを公開できます。

実際、以前は自分もよく利用していて、老人会的には「固定IP取ったり、ルーターに穴を空けたり」しないで済む【楽さ】に感動していました。

ただ、常時稼働するサーバーが再起動したりするケースなど「IPアドレスを固定したいな」「ドメインを固定したいな」という場面で、手間とコストがかかってきました。

はじめのうちは課金してたのですが、ngrokのダッシュボードに慣れないせいもあって、他の方法がないかと探してた時に見つけたのが、ssh portforward でした。

※そういえば、先日ようやく全てssh portforwardに切り替えてngrok解約したのですが、円安影響で使い始めた頃の1.5倍の価格になってて驚きました

ngrokを使わずに公開

「ngrokを使わずに」と書いたのですが、インターネット側で待ち受けるサーバーが必要になります。「結局外部サービス使うのかよ」と怒られそうですが、はい、使います。。。

でも、無料期間を有効活用したり出来ますし、やり方さえ知ってしまえば応用が効きまくるので、お勧めです。

すなわち、レンタルサーバー等がリバースプロキシとなる構成を作ります。

インターネット側で待ち受けるサーバーとしては、Amazon Lightsail がオススメです。
ポートフォワードするだけなのでサーバースペックはそれほど必要とせず、最低プラン の$3.5/month で十分。更には、最初の3ヶ月は無料枠で利用できます。

具体的なコマンド

で、実際にどのように実現するか、ですが

上図に示すようなportforwardをする場合は、次のようなコマンドになります。

ssh -fN <SVR> -R 60080:127.0.0.1:80

ちなみに -fN オプションの意味は下記の通りです

  • f: ssh先でコマンドを実行した後、バックグラウンドに潜るオプション
  • N: ssh先で何のコマンドも実行しない(ssh繋ぐだけ)オプション

もう1点、上記のようにPortforwardするポート(例:60080)を外部に公開する場合は、リバースプロキシ側(Lightsail側)の sshd 設定を以下のように変更する必要があります。※Ubuntuの例です

$ sudo vi /etc/ssh/sshd_config

  #GatewayPorts no
    ↓
  GatewayPorts yes

$ sudo /etc/init.d/ssh restart

この設定をLightsail側でした上で、sshで繋ぎます。

こうする事で、外部からLightsailサーバーの60080番ポートにアクセスすると、自宅サーバーの80番ポートに繋がるようになります。すなわち http://<SVR>:60080/ で外部から自宅Webサーバーにアクセス可能です。

安定運用のために

Lightsail側のOSのアップデートや公開したportのファイヤーウォール設定など、セキュリティ周りの作業は、適宜行ってください。


Lightsailのネットワーク設定にて。不要なポートを削除したり、IP制限したり。

必要であれば、GatewayPortsを使わず、Nginxなどのミドルウェアを経由させて、認証付きで転送するでも良いかもしれません。※実際、そういう使い方もしています

イノシシ監視システムでは、屋外設置のデバイス故か通信が不安定でsshセッションが切れたりするので、ちゃんとportforward出来てるかshellスクリプトで定期的にチェックしてたりします。また、デバイス起動時に自動でportforward開始するようにしたりもしてます。

まとめ

以上、ssh portforward での自宅サーバー公開方法の一例でした。

ちなみに、ngrok以外にも似たような使いやすいサービスが出ているようですし、ssh portforwardも言うほど手軽ではないかもしれないので、使い方にあったサービスや手段を選択すればよいと思います。

またローカルポートを外部に公開する事はセキュリティリスクに直結するため、ngrokにせよssh portforwardにせよ、それ以外にせよ、非常に慎重に運用するか、その辺の知見に慣れるまでは外部に公開する事はしないなどの判断も必要です。


番外編

ちゃんとportforward出来てるかshellスクリプトで定期的にチェックしてたりします

もしかしたら、↑この辺の具体的な内容が気になる方がおられるかもしれない為、雑なシェルスクリプトですが、おまけとして置いておきます。

portforward定期チェックスクリプト

▽crontab

* * * * * /usr/ino44/scripts/ssh_portforward_checker.sh >> /usr/ino44/logs/ssh_portforward_chekcker.log 2>&1

▽設定ファイル .portforward.list ※このファイルは、各サーバー毎に内容が異なる

80:61080
8081:61081

▽portfowardチェッカー /usr/ino44/scripts/ssh_portforward_checker.sh
※/usr/ino44 と書いていますがダミーです (実際は別の階層に置いてます)

#!/bin/bash

cd `dirname $0`

ports=()
while read PFWD; do
  ports+=( $PFWD )
done < <( cat .portforward.list )

date
for PFWD in ${ports[@]}; do
  echo -e "\t"$PFWD
  ports=(${PFWD//:/ })
  LOCAL=${ports[0]}
  REMOTE=${ports[1]}
  if [[ $REMOTE != '' ]]; then # ↓ IPアドレス 123.45.67.8 はダミー(LightsailのIPです)
    CNT=` ssh ubuntu@123.45.67.8 "netstat -na" | grep $REMOTE | grep '0.0.0.0:' | wc -l `
    if [ $CNT -eq 0 ]; then
      date
      echo -e "\t\tReconnect $REMOTE"
      PID=$(ps x | grep [:]$REMOTE | awk '{print $1}')
      for id in $PID ; do
        echo -e "\t\t\tkill $id"
        kill -9 $id
      done
      ssh -fN ubuntu@123.45.67.8 -R $REMOTE:0.0.0.0:$LOCAL
    fi
  fi
done
  • 上記シェルが何やってるのか

    屋外設置のデバイス故か通信が不安定でsshセッションが切れたりするので

    ↑この辺の事情があり、接続元(ラズパイ側)にsshのプロセスは残ってるのに、接続先(Lightsail側)ではプロセスが死んでるという状態が頻発して、ローカルではなくリモート側の接続状態を監視せねばならず、症状が発生していたら、まずローカルのプロセスを殺して接続し直す、のような処理をしてる感じです

ローカルフォワード

-R の他にも -L オプションで、手元の環境にフォワード設定を作ったり色々できます。

例えば下記のように、別のネットワークにある監視カメラサーバーから動画収集するためにトンネルを掘る時に使ったりしています。

crontab

@reboot /usr/ino44/scripts/admin/ssh_local_portforward.sh >> /usr/ino44/logs/ssh_local_portforward.txt 2>&1

起動時にトンネル開通 /usr/ino44/scripts/admin/ssh_local_portforward.sh
※ラズパイで作ったAP配下の監視カメラへのトンネルを掘る

#!/bin/bash

# 再起動直後の実行なので、名前解決が正常にできるように、3分待つ
# Raspberry Pi OSは、host名.local で名前解決できるようになっている
sleep 3m

# 例: ローカルポートから各監視カメラの 22 にアクセスポイント経由でトンネルを掘り、定期的にrsync
ssh -fN ino44ap.local -L 1022:ino44Camera1.local:22
ssh -fN ino44ap.local -L 2022:ino44Camera2.local:22
ssh -fN ino44ap.local -L 3022:ino44Camera3.local:22
ssh -fN ino44ap.local -L 4022:ino44Camera4.local:22


※ラズパイで作ったWi-Fi AP(アクセスポイント)経由で、異なるネットワークの端末へトンネルを掘る

定期的な rsync ※ino44Camera1のmp4を収集する例。こんな感じの処理を定期実行

rsync -av \
  --rsh="ssh -p 1022" \ # ポート 1022 (監視カメラ1 = ino44Camera1 へのトンネル )を使う
  --include="*/" \
  --include "*.mp4" \
  --exclude="*" \
  --remove-source-files \
  localhost:/var/www/html/videos/ /var/www/ino44/public/inocam1/videos/
フェズ開発ブログ

Discussion