リモート拠点のグローバルIPアドレスの変更を検知し通知する


はじめに

今回は、リモート拠点にあるLinuxベースの機器で、リモート拠点側のグローバルIPアドレスの変更を検知し、Slackに通知してもらう方法について書きたいと思います。

システム概要図

簡単ですが、今回のシステム構成図です。

※この構成図はLucidchartを利用させてもらい作成しました。(無料枠)
 Webサービスなのでもっさりした感はありましたが、UIが直観的でスムーズに作成することができました。

大まかなフローは以下の通りです。

  1. グローバルIP問い合わせ
  2. 通知(新しいグローバルIP)
  3. 確認
  4. SSH接続(ポートフォワード)

グローバルIP問い合わせ

グローバルIPアドレスの取得と変更を検出するスクリプトをcronで定期的に動かします。
今回はRaspberryPiを利用した場合の説明を記載します。
cron+シェルスクリプトが動くLinuxベースの機器であれば問題ありません。(Armadilloなど)

なお今回利用するNginxは、自社サービス向けに立てているWebサーバーを間借りして利用しています。
おそらく今回程度の利用(不特定多数の公開はしない or アクセス数が少ない)であればt2.microの無料枠でも実現可能かもしれません。

通知(新しいグローバルIP)

シェルスクリプトでグローバルIPアドレスの変更を検知した場合に、その情報をSlackに投稿します。
今回、これまた諸事情もあり旧式ではありますがWebHookの仕組みを利用します。(ここで紹介するのはWebHookのURLおよびcurlコマンド例だけで、詳細については省きます)

確認

Slackに投稿されると通知が来ますので、変わったことを知ることができます。

SSH接続(ポートフォワード)

ポートフォワードによって、グローバルIPアドレスベースでリモート拠点内のRaspberry Pi・各種デバイスにSSH接続します。(ここではそういう方式でSSH接続しますよという説明だけで、詳細については省きます)

詳細説明

このシステム構成の中で主に紹介するのは、

  • グローバルIPアドレスを取得するNginxの設定
  • グローバルIPアドレスをチェックし、変更を検知したら通知するシェルスクリプト

の2点です。
仰々しくシステム構成図まで作成しましたが、なんてことはない、手順①だけの説明でしたね。

グローバルIPアドレスを取得するNginxの設定

昨今、オフィスや拠点のグローバルIPアドレスを知るサービスはあまたあります。
今回はシェルスクリプト内から利用するので、curlなどのCLIで確認できるサービスになりますが、

httpbin.org/ip
ifconfig.io
inet-ip.info

などなど。
詳しく知りたい方は「curl グローバルIP 確認」などで検索してみてください。

私も実はちょっと前まで上記のサービスの一つを利用していました。

ですが応答が空になったことがあったり、またこの手のサービスは公開終了などが考えられましたので、自前で立てる方法はないかなと調べたら、なんとNginxの設定でサクッとできることが分かったので、今回はその方法を記載しようと思います。難しいスクリプトは作らずnginx.confに数行を追加するだけのシンプルなものです。

具体的な設定は以下の通りです。(後半の4行のみ。その他は追加場所が分かりやすいように記載しているだけです)

nginx.conf
http {
    server {
        location / {
            root   D:/nginx/html;
            index  index.html index.htm;
        }

        location /remote_addr {
            default_type text/plain;
            return 200 "$remote_addr\n";
        }

これだけ。
この設定を間借りしているAmazon Linux 2上のNginxに追加します。

この設定は、

http://xxx.secual-inc.com/remote_addr

へのリクエストがあったらレスポンスBodyに$remote_addrの内容を返すだけの設定です。

グローバルIPアドレスをチェックし、変更を検知したら通知するシェルスクリプト

#!/bin/sh

WORKDIR="/public/toolz/check_globalip"
RESULT_NOW=$WORKDIR"/check_globalip_now.txt"
RESULT_PREV=$WORKDIR"/check_globalip_prev.txt"
RESULT_DIFF=$WORKDIR"/check_globalip_diff.txt"
RESULT_JSON=$WORKDIR"/check_globalip_json.txt"

LOCKFILE=$WORKDIR"/check_globalip.lock"
if [ -f $LOCKFILE ]; then
  exit 0
fi
trap "{ rm $LOCKFILE; exit 255; }" EXIT
touch $LOCKFILE

curl http://xxx.secual-inc.com/remote_addr > $RESULT_NOW

if [ -s $RESULT_PREV ]; then
  diff $RESULT_NOW $RESULT_PREV > $RESULT_DIFF
  if [ ! -s $RESULT_DIFF ]; then
    exit
  fi
fi

cat $RESULT_NOW > $RESULT_PREV

echo 'payload={"channel": "#service_checker", "username": "service checker", "text": "@here グローバルIP変更通知 \n```' > $RESULT_JSON
cat $RESULT_NOW | sed -z 's/\n/\\n/g' >> $RESULT_JSON
echo '```", "icon_emoji": ":guardsman:", "link_names" : true}' >> $RESULT_JSON

curl -s -X POST -d @$RESULT_JSON https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx

ロックファイルやtrapの話は、是非以下の記事を参照ください。

シェルスクリプト+ロックファイルで二重実行防止 - Qiita
https://qiita.com/SECUAL_masa/items/5b1b7e8c4ed13c420435

それ以外については、個別に説明していきます。

変数定義
WORKDIR="/public/toolz/check_globalip"
RESULT_NOW=$WORKDIR"/check_globalip_now.txt"
RESULT_PREV=$WORKDIR"/check_globalip_prev.txt"
RESULT_DIFF=$WORKDIR"/check_globalip_diff.txt"
RESULT_JSON=$WORKDIR"/check_globalip_json.txt"

今回のグローバルIPアドレス変更検知は、"今取得したIPアドレスと前回取得したIPアドレスとの比較"で実施しています。各情報はファイルに保存しているので、そのファイルの変数を定義しています。

グローバルIPアドレスの取得
curl http://xxx.secual-inc.com/remote_addr > $RESULT_NOW

"/remote_addr"は、上記nginx.confの"location /remote_addr {"のことですね。
こうすることでグローバルIPアドレスがcheck_globalip_now.txtに書き出されます。

新しく取得したIPと前回のIPとを比較
if [ -s $RESULT_PREV ]; then
  diff $RESULT_NOW $RESULT_PREV > $RESULT_DIFF
  if [ ! -s $RESULT_DIFF ]; then
    exit
  fi
fi

各ファイルに保存された情報をdiffで比較して、その結果をcheck_globalip_diff.txtに保存しています。

  • 比較結果が一致(前回と一緒)       → check_globalip_diff.txtのファイルサイズは0   -> 中断
  • 比較結果が一致(前回と異なる==変更有) → check_globalip_diff.txtのファイルサイズは0以外 -> 続行
次回用に保存
cat $RESULT_NOW > $RESULT_PREV

現在の結果を次回の比較用として保存しています。

Slack投稿用JSONデータの構築
echo 'payload={"channel": "#service_checker", "username": "service checker", "text": "@here グローバルIP変更通知 \n```' > $RESULT_JSON
cat $RESULT_NOW | sed -z 's/\n/\\n/g' >> $RESULT_JSON
echo '```", "icon_emoji": ":guardsman:", "link_names" : true}' >> $RESULT_JSON

Slackに投稿するJSONデータを構築しています。
JSONフォーマットについては、Slackの仕様を確認してください。

Slackへ投稿
curl -s -X POST -d @$RESULT_JSON https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx

前述で構築したJSONをWebHook URLを呼び出して投稿(POST)しています。

cron

あとはスクリプトをcronで指定して定期的に実行するようにします。

cron
0 */1 * * * /home/pi/tools/check_globalip/check_globalip.sh > /dev/null 2>&1

Slackへの通知

今回の仕組みで最終的に以下のようにSlackに投稿されます。

なぜこの仕組みが必要だったか?

一般的にリモート拠点のメンテナンスを行う場合はリモート拠点側のグローバルIPアドレスを固定にし、VPNを利用します。しかし今回は

  • 固定のIPアドレスが取得できない(グローバルIPアドレスはDHCP)
  • ルーターはコンシューマー向けのWiFiルーター

ということもあり、ポートフォワード(*1)によりリモート拠点内のデバイスにアクセスするようにしました。
そのためにリモート拠点側のグローバルIPアドレスが変わった場合はそのグローバルIPアドレスを知る必要がありました。

*1:YAMAHAのルーターなど、IPSecを利用したVPNで片方の拠点のグローバルIPアドレスがDHCPの場合でも利用可能な方法(設定)はありますが、今回はリモート拠点側のルーターがコンシューマー向けのWiFiルーターということもあり、ポートフォワードを採用しました。