SORACOM Airのメタデータでautossh


この記事は、IoTLT Advent Calendar 2016 の11日目の記事です。
※1日遅れの投稿になってしまってすみません…。

突然ですがみなさん、設置したIoTデバイスのメンテナンスにお困りではありませんか?
Linux搭載でSSHできればあとから何とでもなるので安心ですが、
都合良くグローバルIPを割り当てられないケースも多々あると思います。

今回、お仕事でOpenBlocks IoT+SORACOM Airを遠隔地に設置してメンテナンスが必要な案件に関わったので、
その際につくった簡単なシェルスクリプトをご紹介します。

soracom_autossh

GitHubで公開しています。

クラスメソッド様のSoracom Airで繋がったデバイスにリモートからSSHするを参考に、
記事の最後で言及されている「Soracom Air のユーザーデータでリモートサーバ情報を連携」を(データ構造は違いますが)実現しています。

使用方法はREADMEをご参照下さい。

やっていること

SORACOM Airのメタデータサービスでは、SIMの情報(Subscriber情報)とユーザデータ(任意の文字列を設定できる)が取得できます。
soracom_autosshでは、ユーザデータで「どのSIMが」「どのサーバに」「どの秘密鍵」でSSHポートフォワードするかという情報を持たせておきます。

ユーザデータのデータ構造は下記の通りです。
{SSHでログインしたいLinuxデバイスのSORACOM Air IMSI}は、imsis配下に複数指定可能です。

{
  "ssh":{
    "imsis":{
      "{SSHでログインしたいLinuxデバイスのSORACOM Air IMSI}":{
        "portForwardParam":"{任意のポート番号}:localhost:22 {リモートサーバのユーザー名}@{リモートサーバのグローバルIPアドレス}",
        "privateKey":"{リモートサーバのSSH秘密鍵}"
      }
    }
  }
}

まず、SIMの情報を取得してユーザデータと照らし合わせ、
自分が接続すべきSSH情報がユーザデータにあるかどうか調べます。

soracom_autossh.sh
# 自分のIMSIと該当するSSH情報を取得
IMSI=`curl -s http://metadata.soracom.io/v1/subscriber | jq .imsi`
if [ $? -ne 0 ]; then
  echo "failed to get own imsi"
  exit 1
fi

SSH_INFO=`curl -s http://metadata.soracom.io/v1/userdata | jq -r .ssh.imsis[${IMSI}]`
if [ $? -ne 0 ]; then
  echo "failed to get userdata"
  exit 1
fi

自分が接続すべきSSH情報が存在し、かつ前回取得した内容と異なる場合のみ、
秘密鍵をユーザデータからファイルに書き出し、autosshを起動します。

soracom_autossh.sh
if [ "$SSH_INFO" != "null" ]; then
  # 自分のSSH情報があれば、前回取得したSSH情報との差分チェック
  if diff -q $SSH_INFO_FILE $SSH_INFO_FILE_OLD > /dev/null 2>&1; then
    # do nothing
    echo "do nothing" 
  else
    # 前回取得したSSH情報との差分があれば、秘密鍵を書き出してautossh開始

    # autosshが起動済みなら終了
    exit_autossh
    delete_privatekey

    echo $SSH_INFO | jq -r .privateKey > $SSH_PRIVATEKEY_FILE
    chmod 600 $SSH_PRIVATEKEY_FILE
    AUTOSSH_PIDFILE=$AUTOSSH_PIDFILE \
                   autossh -M 0 -o StrictHostKeyChecking=no \
                   -o UserKnownHostsFile=/dev/null \
                   -o ServerAliveInterval=60 \
                   -o ServerAliveCountMax=3 \
                   -o ExitOnForwardFailure=yes \
                   -i $SSH_PRIVATEKEY_FILE \
                   -N \
                   -f \
                   -R `echo $SSH_INFO | jq -r .portForwardParam` &
    echo "started autossh"
  fi
else
  # 自分のSSH情報が無い場合、autosshが起動済みなら終了
  exit_autossh
  delete_privatekey
fi

soracom_autossh.shはcronなどでの定期実行を想定しており、
もし自分が接続すべきSSH情報が無い場合、起動したautosshを終了するようになっています。

実際の運用例

あらかじめsoracom_autossh.shをcronで5分間隔起動するようにしておき、通常時はユーザデータを空にします。
メンテナンスの必要性があるときだけSSHポートフォワード先となるAWS EC2のインスタンスを立ち上げ、
ユーザデータにメンテナンスしたいIoTデバイスとSSH情報を書いておき、IoTデバイスがSSHポートフォワードしてくるのを待ちます。

最後に

これでいつでもSSHできるので、何かあったときでも安心ですね。(トラブル起きないのが一番ですが…)