AWS IoTのDevice GatewayにHTTPプロトコルとクライアント認証でメッセージ送信する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWS IoTのDevice Gatewayは通信プロトコルとしてはHTTPMQTTに対応し、認証としてはAWS認証(IAMクレデンシャル、cognitoなど)とクライアント証明書に対応しています。

AWSのドキュメントでは

  • MQTTプロトコルクライアント認証
  • HTTPプロトコルAWS認証

を使ったサンプルをよく見かけ、これらの接続方法を紹介してきました。

実はHTTPプロトコルクライアント認証の組み合わせも使えます。

そこで今回は Linux/Mac上のcurlからクライアント認証でAWS IoT Device Gatewayにメッセージ送信する方法を紹介します。

  • AWS IoT Device Gatewayとクライアント認証を使った疎通確認をコマンドラインから行いたい
  • 疎通確認のためだけにMQTTクライアントはインストールしたくなく、curlのようにデフォルトでインストールされているプログラムですませたい

ようなケースで有効かと思います。

認証の準備

X.509クライアント証明書で認証するために、iot create-keys-and-certificateコマンドで新規に公開鍵のペアとX.509証明書を生成します。

$ aws iot create-keys-and-certificate \
  --set-as-active \
  --certificate-pem-outfile cert.pem \
  --public-key-outfile publicKey.pem \
  --private-key-outfile privateKey.pem
{
    "certificateArn": "arn:aws:iot:ap-northeast-1:123456789012:cert/6e16483d95f1e66d70c532ac287aedb73c72a726c38bae6de3a427b583778bdc",
    "certificatePem": "-----BEGIN CERTIFICATE-----\nMI...g==\n-----END CERTIFICATE-----\n",
    "keyPair": {
        "PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIB...QAB\n-----END PUBLIC KEY-----\n",
        "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMII...xDw==\n-----END RSA PRIVATE KEY-----\n"
    },
    "certificateId": "6e16483d95f1e66d70c532ac287aedb73c72a726c38bae6de3a427b583778bdc"
}
オプション 説明
--certificate-pem-outfile 証明書を保存するパス
--public-key-outfile 公開鍵を保存するパス
--private-key-outfile プライベート鍵を保存するパス

また、ルート CA もシマンテックサイトから取得します。

$ curl -o rootCA.pem -s https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem

AWS IoTポリシーの設定

AWS IoTの権限管理をするポリシーを作成します。 今回はDevice GatewayへのPublishのみを許可します。

$ cat iot-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}
$ aws iot create-policy --policy-name "IoTPubSub" --policy-document file://iot-policy.json
{
    "policyName": "IoTPubSub",
    "policyArn": "arn:aws:iot:ap-northeast-1:123456789012:policy/IoTPubSub",
    "policyDocument": "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"iot:Publish\"\n      ],\n      \"Resource\": [\n        \"*\"\n      ]\n    }\n  ]\n}\n",
    "policyVersionId": "1"
}
$ aws iot list-policies
{
    "policies": [
        {
            "policyName": "IoTPubSub",
            "policyArn": "arn:aws:iot:ap-northeast-1:123456789012:policy/IoTPubSub"
        }
    ]
}

作成したAWS IoTポリシーを証明書に紐付けます。 プリンシパルとなるのはaws iot create-keys-and-certificate コマンドを実行した時の certificateArn です。

$ aws iot attach-principal-policy \
  --principal "arn:aws:iot:ap-northeast-1:123456789012:cert/6e16483d95f1e66d70c532ac287aedb73c72a726c38bae6de3a427b583778bdc" \
  --policy-name "IoTPubSub"
$ aws iot list-principal-policies --principal "arn:aws:iot:ap-northeast-1:123456789012:cert/6e16483d95f1e66d70c532ac287aedb73c72a726c38bae6de3a427b583778bdc"
{
    "policies": [
        {
            "policyName": "IoTPubSub",
            "policyArn": "arn:aws:iot:ap-northeast-1:123456789012:policy/IoTPubSub"
        }
    ]
}

メッセージ送信

ここまでで準備が整いました。

最後に

  • Amazon Linux
  • Mac

それぞれから curl を使ってメッセージ送信します。

エンドポイントの確認

次のコマンドでDevice Gatewayのエンドポイントを確認します。 このエンドポイントは AWS アカウントごとに存在します。

$ aws iot describe-endpoint
{
    "endpointAddress": "DUMMY.iot.ap-northeast-1.amazonaws.com"
}

URL は次のような形をしています。

https://ENDPOINT_ADDRESS:8443/topics/PATH/TO/TOPIC?qos=LEVEL

  • iot describe-endpointを実行した endpointAddressDUMMY.iot.ap-northeast-1.amazonaws.com
  • メッセージ送信するトピックが foo/bar
  • MQTTのサブスクライバー向けQoSが1

であれば

https://DUMMY.iot.ap-northeast-1.amazonaws.com:8443/topics/foo/bar?qos=1

となります。

HTTPSリクエスト時のポートは 8443 です。MQTTS向けの8883ではありませんので注意してください。

送信メッセージをPOSTで渡します。

Amazon Linux版

Amazon Linuxのcurlからは次のコマンドでメッセージ送信します。

$ curl -D - \
  --tlsv1.2 \
  -X POST \
  --cert ./cert.pem \
  --key ./privateKey.pem \
  --cacert ./rootCA.pem \
  https://DUMMY.iot.ap-northeast-1.amazonaws.com:8443/topics/test?qos=0 \
  -d @payload.json
HTTP/1.1 200 OK
content-type: application/json
content-length: 2
date: Sat, 23 Jan 2016 04:37:34 GMT
x-amzn-RequestId: e12424c2-1272-404b-b62b-1afbc8803358
connection: Keep-Alive

OK
オプション 説明
--tls1.2 AWS IoTのHTTP版Device GatewayはTLS1.2のみに対応しているため、明示的に指定します
--cert 証明書のパスです
--key プライベート鍵のパスです
--cacert ルート証明書のパスです
-d POSTするペイロードのパスを@で指定します

注意点としては、ファイルがカレントディレクトリにある場合、NSS証明書のニックネームとの衝突を避けるために、パスを./で始めるようにしてください。

Mac版

Macのcurlから Amazon Linux版と同じコマンドを実行すると

$ curl -D - \
  --tlsv1.2 \
  -X POST \
  --cert ./cert.pem \
  --key ./privateKey.pem \
  --cacert ./rootCA.pem \
  https://DUMMY.iot.ap-northeast-1.amazonaws.com:8443/topics/test?qos=0 \
  -d @payload.json
curl: (58) SSL: Can't load the certificate "./cert.pem" and its private key: OSStatus -25299

というエラーが発生します。

Mac版curlはSSL通信ライブラリにOpenSSLではなくSecureTransportが使われているのが原因です。 $ curl --versionを実行して確認して見ましょう。

$ curl --version
curl 7.43.0 (x86_64-apple-darwin14.0) libcurl/7.43.0 SecureTransport zlib/1.2.5
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz UnixSockets

解決策は次のページに記載されています。

Important note for curl users on OS X Yosemite 10.10

何通りか存在しますが、今回は2bの解決策、つまり、-E/--certオプションに証明書とプライベート鍵を含んだPKCS#12形式のファイルを指定します。

まずopensslコマンドで証明書とプライベート鍵をPKCS#12形式に変換します。

$ openssl pkcs12 -export -clcerts -in cert.pem -inkey privateKey.pem -out cert.p12
Enter Export Password:
Verifying - Enter Export Password:

次に -E/--cert オプションに作成したPKCS#12形式のファイルのパスとパスワードを -E ./PATH/TO/PKCS_12_ENCODED_FILE:YOUR_PASSWORD の形式で指定します。

$ curl -D - 
  --tlsv1.2 \
  -X POST \
  -E ./cert.p12:YOUR_PASSWORD \
  --cacert ./rootCA.pem \
  https://DUMMY.iot.ap-northeast-1.amazonaws.com:8443/topics/test\?qos\=0 \
  -d @payload.json
HTTP/1.1 200 OK
content-type: application/json
content-length: 2
date: Sat, 23 Jan 2016 04:55:29 GMT
x-amzn-RequestId: b4c843fa-2b25-4423-b154-e3efaef22d1d
connection: Keep-Alive

OK

メッセージ送信に成功しました。

参照