Azure IoT Hubを生MQTTS(mosquitto)やHTTP RESTで使う方法

デバイスSDKとか、デバイスExplorerとか、謎テクノロジーは嫌なんじゃー！という方にお送りする **Azure IoT Hubをmosquittoで使う方法** をご紹介します

## ここで解説すること、しないこと

* すること;
    * セキュリティトークン(SASトークン)を利用した mosquitto\_pub/\_sub によるメッセージの送受信
* しないこと;
    * X.509証明書を利用した送受信

## 必要なもの

* mosquitto コマンド (Ubuntuなら `apt install mosquitto-clients` で入ります)
* python(とか)
    * SASトークンを生成するためのヘルパコマンドを実行する

## 最終的な mosquitto\_pub/\_sub コマンド

下記の条件の場合における mosquitto_sub は後述の通りです
これを組み立てるのに必要な情報や手続きを解説します

* IoT Hub名 : `testing-iothub0`
* デバイス ID : `dev0`

```console
$ mosquitto_pub -d -q 1 --capath /etc/ssl/certs/ -V mqttv311 -p 8883 \
  -h testing-iothub0.azure-devices.net \
  -i dev0 \
  -u "testing-iothub0.azure-devices.net/dev0/api-version=2016-11-14" \
  -P "SharedAccessSignature sr=<snip>&skn=<snip>&sig=<snip>&se=1498222991" \
  -t "devices/dev0/messages/events/"
  -m '{"v":1}'
```

```console
$ mosquitto_sub -d -q 1 --capath /etc/ssl/certs/ -V mqttv311 -p 8883 \
  -h testing-iothub0.azure-devices.net \
  -i dev0 \
  -u "testing-iothub0.azure-devices.net/dev0/api-version=2016-11-14" \
  -P "SharedAccessSignature sr=<snip>&skn=<snip>&sig=<snip>&se=1498222991" \
  -t "devices/dev0/messages/devicebound/#"
```

## Azure IoT Hub を作成する

今回 `testing-iothub0` という名前の Azure IoT Hub を作成します

新規作成から IoT Hub を探します

![azure-iot-hub-0.png](https://qiita-image-store.s3.amazonaws.com/0/22754/50f644a7-fc0a-5326-ede8-07ec34cdf190.png)

必要事項を入力して、作成します。無料Tierを利用しましょう

![azure-iot-hub-1.png](https://qiita-image-store.s3.amazonaws.com/0/22754/28c8254c-08bc-4f40-0d24-3a1f993035ef.png)

作成は3分くらいで完了します

## IoT Hub デバイスを作成する

IoT Hub では登録されたデバイスから通信できるようになるため、デバイスの追加を行う必要があります
今回は `dev0` というデバイス名を持つデバイスを作成します

IoT Hub の管理ページから *Device Explorer* を選択し [追加] より `dev0` として保存します

![azure-iot-hub-2.png](https://qiita-image-store.s3.amazonaws.com/0/22754/0b01561f-b22f-bb5b-d19b-b54bf331e205.png)

## SASトークンを生成する

SASはShared Access Signatureの略で、文字列的には `SharedAccessSignature ...` から始まる文字列になります
ドキュメント中にでは「セキュリティトークン」や「トークン」とだけ書かれていたり、何気にややこしい奴ですが、実態は *SASトークン* です

これは手で生成するのが難しいので、生成するヘルパープログラムを用意したほうがいいでしょう
サンプルは [Azure IoT Hubセキュリティのページ](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-security) に掲載されています

下記コードは、上記で掲載されているPythonコードに、コマンドラインから使いやすいようにoptparseを加えたものです

```python
## generate_sas_token.py
from base64 import b64encode, b64decode
from hashlib import sha256
from time import time
from urllib import quote_plus, urlencode
from hmac import HMAC

def generate_sas_token(uri, key, policy_name, expiry=3600):
    ttl = time() + expiry
    sign_key = "%s\n%d" % ((quote_plus(uri)), int(ttl))
    print sign_key
    signature = b64encode(HMAC(b64decode(key), sign_key, sha256).digest())

    rawtoken = {
        'sr' :  uri,
        'sig': signature,
        'se' : str(int(ttl))
    }

    if policy_name is not None:
        rawtoken['skn'] = policy_name

    return 'SharedAccessSignature ' + urlencode(rawtoken)

from optparse import OptionParser
parser = OptionParser()
parser.add_option("-u", "--uri", dest="uri", help="URI", metavar="URI")
parser.add_option("-k", "--key", dest="key", help="KEY", metavar="KEY")
parser.add_option("-c", "--policy", dest="policy_name", help="Policy Name", metavar="POLICY_NAME")
parser.add_option("-a", "--age", dest="age", help="Age of Token (sec)", metavar="AGE", default=3600)
(options, args) = parser.parse_args()
print generate_sas_token(options.uri, options.key, options.policy_name, int(options.age))
```

```console
$ python generate_sas_token.py --help
Usage: generate_sas_token.py [options]

Options:
  -h, --help            show this help message and exit
  -u URI, --uri=URI     URI
  -k KEY, --key=KEY     KEY
  -c POLICY_NAME, --policy=POLICY_NAME
                        Policy Name
  -a AGE, --age=AGE     Age of Token (sec)

```

* `--uri`
  * リソース URI: `{IoT hub name}.azure-devices.net/devices/{device id}` という文字列。たとえば、 `testing-iothub0.azure-devices.net/devices/dev1`
* `--key`
  * 共有アクセスキー(署名キーとも書かれてる): 「デバイスの詳細」に記載された *プライマリ キー* (対称キーとも書かれてる)
* `--policy`
  * 共有アクセスポリシー名: デバイスの詳細に記載されたプライマリキーを利用する場合は、指定不要
* `--age`
  * 有効期限: 生成するトークンの有効期限を秒で指定。60 => 1分間有効なトークンが生成される。デフォルト3600秒

```console
$ python generate_sas_token.py \
  -u testing-iothub0.azure-devices.net/devices/dev0 \
  -k Z/yan+rwy0gqSQuKpPdCX7+w/F0HruLB+U4YjWAcz0w=
SharedAccessSignature sr=testing-iothub0.azure-devices.net%2Fdevices%2Fdev0&sig=rYvmWTuFt8YyTPDodLuYVLta962QCFLJJuo1SL4fB1w%3D&se=1498295991
```

### 共有アクセスキーの拾い方 ###

Device Explorer から当該デバイスを選ぶと表示されます

![azure-iot-hub-3.png](https://qiita-image-store.s3.amazonaws.com/0/22754/c967b5cf-4391-f1bc-a0c3-7e089c5304fa.png)

### "共有アクセスポリシー"に割り当てられた"共有アクセスキー"

本稿では、デバイス毎に割り当てられた"共有アクセスキー"を使いました
IoT Hubには *共有アクセスポリシーに割り当てられた"共有アクセスキー"* ってのもあります
**ややこしい！**

この *共有アクセスポリシーに割り当てられた"共有アクセスキー"* とは、作成したIoT Hub内すべてのデバイスすべてに同じようにアクセスできるマスターキー相当です
このマスターキー相当である *共有アクセスポリシーに割り当てられた"共有アクセスキー"* を利用する場合は `generate_sas_token` が以下のように変わります
（この場合、デバイス毎に割り当てられた共有アクセスキーは使用しません）

```console
$ python generate_sas_token.py \
  -u testing-iothub0.azure-devices.net/devices/dev0 \
  -k TEOLRugIC/ocpimvE05R3HSeSxhJ9FHNJI9KfdBCG/A= \
  -c device
SharedAccessSignature sr=testing-iothub0.azure-devices.net%2Fdevices%2Fdev0&skn=device&sig=%2Fip%2F9kwV4U1Mdo2BG%2BuNpO44D26sLjRtNcXOFu6n3gk%3D&se=1498293853
```

*device* という文字列は *共有アクセスポリシー* の中のポリシー名です。キーは詳細ページに表示されています

![azure-iot-hub-5.png](https://qiita-image-store.s3.amazonaws.com/0/22754/527331b5-73e9-da3d-b4e2-7fb866b203e4.png)

デバイス自体は作る(登録)する必要があるため、基本的にはデバイス毎の共有アクセスキーを使うのが良いでしょう

## mosquitto の実行 ##

おつかれさまでした、ここまでの作業で **ようやっと** mosquitto を実行することができます

* `--capath` はOSのCertificateファイルが格納されているディレクトリを書くようにしてください。Ubuntu ならば `/etc/ssl/cert` です
* IoT Hubは MQTT 3.1.1 接続のみサポートしているため `-V mqttv311` は必須です
* `-h` `-i` `-u``-P` `-t` それぞれについては詳細は [Azure IoT Hub の MQTTサポート / MQTTプロトコルの直接使用](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-mqtt-support#a-nameusing-the-mqtt-protocol-directlyamqtt-プロトコルの直接使用) を見てください


### メッセージ送信 ###

```console
$ mosquitto_pub -d -q 1 --capath /etc/ssl/certs/ -V mqttv311 -p 8883 \
  -h testing-iothub0.azure-devices.net \
  -i dev0 \
  -u "testing-iothub0.azure-devices.net/dev0/api-version=2016-11-14" \
  -P "SharedAccessSignature sr=<snip>&skn=<snip>&sig=<snip>&se=1498222991" \
  -t "devices/dev0/messages/events/"
  -m '{"v":1}'
```

### メッセージ受信 ###

```console
$ mosquitto_sub -d -q 1 --capath /etc/ssl/certs/ -V mqttv311 -p 8883 \
  -h testing-iothub0.azure-devices.net \
  -i dev0 \
  -u "testing-iothub0.azure-devices.net/dev0/api-version=2016-11-14" \
  -P "SharedAccessSignature sr=<snip>&skn=<snip>&sig=<snip>&se=1498222991" \
  -t "devices/dev0/messages/devicebound/#"
```

#### mosquitto\_sub しているクライアントに対して、クラウド側からメッセージpushのテスト ####

デバイスの詳細ページから Message Body を入力して [Send Message] します

![azure-iot-hub-4.png](https://qiita-image-store.s3.amazonaws.com/0/22754/1b5f0a84-ae39-75b5-5162-73bb21a0de72.png)

#### subscribeの様子 ####

```
Client dev0 sending CONNECT
Client dev0 received CONNACK
Client dev0 sending SUBSCRIBE (Mid: 1, Topic: devices/dev0/messages/devicebound/#, QoS: 1)
Client dev0 received SUBACK
Subscribed (mid: 1): 1
Client dev0 sending PINGREQ
Client dev0 received PINGRESP
Client dev0 received PUBLISH (d0, q1, r0, m2, 'devices/dev0/messages/devicebound/%24.to=%2Fdevices%2Fdev0%2Fmessages%2FdeviceBound', ... (4 bytes))
Client dev0 sending PUBACK (Mid: 2)
Hi!!
```

## Azure IoT Hubの注意点

### pub時のペイロードフォーマットはJSONのみ

どうやらそういうことらしいです

### MQTT同士でのpub/subはできない模様

*IoT Hub はパブリッシャーとサブスクライバー間の汎用メッセージング ブローカーではないため* って[書いてある](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-mqtt-support)とおり、mosquitto\_subでsubscribeしておきつつ、mosquitto\_pubをしても、メッセージの受信ができません
Azure IoT HubはMQTTブローカとして使うな！というMSさんのメッセージなのでしょう
（AWS IoTはできますね。。。）

ちなみにHTTP RESTでも同じ模様
AMQPSならできるっぽい (tcpdumpしたらamqpsだった)

### "共有アクセスキー" のセカンダリーキー

ストレージアカウントはセキュリティの観点からアクセスキーを一定期間でローテーションすることを目的として、プライマリとセカンダリのキーが発行されます
具体的な運用としては、アクセスに使うキーをプライマリからセカンダリに変更し、その間にプライマリのキーを再生成してから元に戻す（そのあとセカンダリも再生成する）といった形です

しかしIoT Hubのデバイスや共有アクセスポリシーに割り当てられた共有アクセスキーは再生成をする方法が無いようです
（少なくともWebコンソールからはできない）

なんのためにあるのか、いまいちわかりません。。。

## HTTP REST

HTTP RESTでも基本的には操作は同じです

メッセージ送信

```console
$ curl -v -X POST \
  -H "Authorization: SharedAccessSignature sr=ma2iot-aeM6Xeel.azure-devices.net%2Fdevices%2Fdev0&sig=qlbXcpKVzYU%2B4Yfs21Yr%2BeLxedv4a2EkoW3bQRhHnZQ%3D&se=1498435678" \
  -H "Content-Type: application/json"
  -d "event message!"
  "https://testing-iothub0.azure-devices.net/devices/dev0/messages/events/?api-version=2016-11-14"
```

メッセージ受信

```console
$ curl -v -X GET \
  -H "Authorization: SharedAccessSignature sr=ma2iot-aeM6Xeel.azure-devices.net%2Fdevices%2Fdev0&sig=qlbXcpKVzYU%2B4Yfs21Yr%2BeLxedv4a2EkoW3bQRhHnZQ%3D&se=1498435678" \
  "https://testing-iothub0.azure-devices.net/devices/dev0/messages/devicebound/?api-version=2016-11-14"
```

# あとがき

SASトークンが大変

# 参考資料;

* [MQTT プロトコルを使用した IoT Hub との通信](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-mqtt-support)
* [IoT Hub へのアクセスの制御](https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-security)
* [Connecting Mosquitto to the new Azure MQTT backend - stackoverflow](https://stackoverflow.com/questions/35863841/connecting-mosquitto-to-the-new-azure-mqtt-backend)