はじめに
今回は、リモート拠点にあるLinuxベースの機器で、リモート拠点側のグローバルIPアドレスの変更を検知し、Slackに通知してもらう方法について書きたいと思います。
システム概要図
簡単ですが、今回のシステム構成図です。
※この構成図はLucidchartを利用させてもらい作成しました。(無料枠)
Webサービスなのでもっさりした感はありましたが、UIが直観的でスムーズに作成することができました。
大まかなフローは以下の通りです。
- グローバルIP問い合わせ
- 通知(新しいグローバルIP)
- 確認
- 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行のみ。その他は追加場所が分かりやすいように記載しているだけです)
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アドレスとの比較"で実施しています。各情報はファイルに保存しているので、そのファイルの変数を定義しています。
curl http://xxx.secual-inc.com/remote_addr > $RESULT_NOW
"/remote_addr"は、上記nginx.confの"location /remote_addr {"のことですね。
こうすることでグローバルIPアドレスがcheck_globalip_now.txtに書き出されます。
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
現在の結果を次回の比較用として保存しています。
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の仕様を確認してください。
curl -s -X POST -d @$RESULT_JSON https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx
前述で構築したJSONをWebHook URLを呼び出して投稿(POST)しています。
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ルーターということもあり、ポートフォワードを採用しました。