Amazon Web Services ブログ

クライアントデバイスからのポート443でのTLSクライアント認証によるMQTTの実装方法(Python)

アプリケーション レイヤ プロトコル ネゴシエーション(Application Layer Negotiation:ALPN)は、TLSの拡張機能として、TLSサーバに接続しているクライアントがProtocolNameListという追加パラメータを渡すことを可能とします。ProtocolNameListは、クライアントが通信に使用したいアプリケーションプロトコルの優先順位付きリストです。
AWS IoT Coreでは、ALPN TLS extensionを使用して、ポート443でTLSクライアント認証を使用してMQTT経由でデバイスを接続できるようになりました。なぜこれが便利なのかについては、このブログ記事を参照してください。

方法1:PahoクライアントをOpenSSLを利用する場合

OpenSSLとmbedTLSを含む最も一般的なTLSは、ALPN をサポートしています。 この例では、Paho-mqttクライアントとOpenSSLライブラリを使用して、デバイスをAWS IoTエンドポイントに接続します。

前提条件

デバイスを接続する前に、PythonとOpenSSLのバージョンがALPN をサポートしているかを確認してください。

1. OpenSSLのバージョンチェックの方法

openssl version
OpenSSL 1.0.2k-fips 26 Jan 2017

2. Pythonのバージョン確認方法

python --version Python 2.7.13

3. Pythonが参照しているOpenSSLのバージョンチェック方法

python
>>> import ssl 
>>> print ssl.OPENSSL_VERSION
OpenSSL 1.0.2k-fips 26 Jan 2017

このサンプル・スクリプトでは、PahoをMQTTライブラリーとして使用してメッセージをパブリッシュします。 Paho-MQTTクライアントの最新の安定版は、Python Package Index(PyPi)で入手できます。 pipを使用してインストールします。

 

pip install paho-mqtt

 

接続された各デバイスには、メッセージブローカーまたはデバイスシャドウサービスにアクセスするための認証情報が必要です。 サンプルスクリプトでは、認証メカニズムとしてX.509証明書を使用して、AWS IoTエンドポイントに接続します。 AWS IoTコンソールまたはCLIを使用してAWS IoT証明書を作成できます。 詳細については、「AWS IoT開発者ガイド」の「AWS IoTデバイス証明書の作成と登録」および「AWS CLIコマンドリファレンス」の「create-keys-and-certificate」を参照してください。

以下のサンプルスクリプトは単純に、AWS IoTへの接続を行ない、メッセージパブリッシュを行っています。実行するには、以下をコピーしalpn_mqtt.pyなどとファイル名を付けて保存してください。

from __future__ import print_function
import sys
import ssl
import time
import datetime
import logging, traceback
import paho.mqtt.client as mqtt

IoT_protocol_name = "x-amzn-mqtt-ca"
aws_iot_endpoint = "AWS_IoT_ENDPOINT_HERE" # <random>.iot.<region>.amazonaws.com
url = "https://{}".format(aws_iot_endpoint)

ca = "YOUR/ROOT/CA/PATH" 
cert = "YOUR/DEVICE/CERT/PATH"
private = "YOUR/DEVICE/KEY/PATH"

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(log_format)
logger.addHandler(handler)

def ssl_alpn():
    try:
        #debug print opnessl version
        logger.info("open ssl version:{}".format(ssl.OPENSSL_VERSION))
        ssl_context = ssl.create_default_context()
        ssl_context.set_alpn_protocols([IoT_protocol_name])
        ssl_context.load_verify_locations(cafile=ca)
        ssl_context.load_cert_chain(certfile=cert, keyfile=private)

        return  ssl_context
    except Exception as e:
        print("exception ssl_alpn()")
        raise e

if __name__ == '__main__':
    topic = "test/date"
    try:
        mqttc = mqtt.Client()
        ssl_context= ssl_alpn()
        mqttc.tls_set_context(context=ssl_context)
        logger.info("start connect")
        mqttc.connect(aws_iot_endpoint, port=443)
        logger.info("connect success")
        mqttc.loop_start()

        while True:
            now = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
            logger.info("try to publish:{}".format(now))
            mqttc.publish(topic, now)
            time.sleep(1)

    except Exception as e:
        logger.error("exception main()")
        logger.error("e obj:{}".format(vars(e)))
        logger.error("message:{}".format(e.message))
        traceback.print_exc(file=sys.stdout)

 

Pythonスクリプトの実行

以下のコマンドを実行します。

python alpn_mqtt.py

2018-03-15 11:03:25,174 - root - INFO - start connect 
2018-03-15 11:03:25,254 - root - INFO - connect success 
2018-03-15 11:03:25,255 - root - INFO - published:<timestamp> 
2018-03-15 11:03:26,256 - root - INFO - published:<timestamp>

 

実行結果として “connect success”と”publish:<クライアントの送信時刻タイムスタンプ>”がコンソールに表示されていれば接続、パブリッシュともに成功しています。
もしエラーが表示されていれば、AWS IoTのエンドポイント情報や証明書情報などスクリプトの書き換え部分を確認して見てください。

 

AWS IoTでのメッセージ到達確認

AWS IoTがクライアントメッセージを受信しているかを確認するには、AWS IoTのコンソールにログインし、メニューの”Test”を選択し、Subscribeを選び、トピックに “test/data”を入力します。

Subscribeを設定したのちに、画面のようにクライアントからパブリッシュされたメッセージが毎秒間隔で表示されます。

もしクライアントがLinuxで動いていればtcpdumpコマンドで確認することも出来ます。

tcpdump port 443

 

方法2 AWS IoT Device SDK for Pythonを使う場合

 

Python用のAWS IoT Device SDKは、開発者がデバイスを使用してAWS IoTにアクセスするためのPythonスクリプトを作成することを可能にします。現時点では、ポート8883でTT over MQTTを、ポート443でWebSocketプロトコルでMQTTを選択できます。現時点では、ポート8883でTT over MQTTを、ポート443でWebSocketプロトコルでMQTTを選択できます。ポート443でのMQTTのサポートは、デフォルトでは提供されていません。そのために、この機能を有効にするためには、Device SDKを変更する必要があります。
Device SDKで構築されたOpenSSLライブラリはALPN拡張をサポートしているため、ポート443でのMQTT通信を可能にするには、SSLライブラリの設定方法を変更する必要があります。 この例では、ポート443のMQTTを介してAWS IoTエンドポイントに接続するためにDevice SDKで行う必要がある変更を示します。

Python用AWS IoT Device SDKは、変更されたPaho-MQTT Pythonクライアントライブラリの上に構築されています。
AWSIoTPythonSDK /core/protocol/paho フォルダー内のclient.pyファイルを変更します。
例:/usr/local/lib/python2.7/site-packages/AWSIoTPythonSDK/core/protocol/paho/client.py

変更内容は次のとおりです。

--- a/AWSIoTPythonSDK/core/protocol/paho/client.py
+++ b/AWSIoTPythonSDK/core/protocol/paho/client.py
@@ -787,15 +787,26 @@ class Client(object):
            self._ssl = SecuredWebSocketCore(rawSSL, self._host, self._port, self._AWSAccessKeyIDCustomConfig, self._AWSSecretAccessKeyCustomConfig, self._AWSSessionTokenCustomConfig)  # Overeride the _ssl socket
                 # self._ssl.enableDebug()
        else:
-           self._ssl = ssl.wrap_socket(
-               sock,
-               certfile=self._tls_certfile,
-               keyfile=self._tls_keyfile,
-               ca_certs=self._tls_ca_certs,
-               cert_reqs=self._tls_cert_reqs,
-               ssl_version=self._tls_version,
-               ciphers=self._tls_ciphers)
-
+           if self._port == 8883:
+               self._ssl = ssl.wrap_socket(
+                   sock,
+                   certfile=self._tls_certfile,
+                   keyfile=self._tls_keyfile,
+                   ca_certs=self._tls_ca_certs,
+                   cert_reqs=self._tls_cert_reqs,
+                   ssl_version=self._tls_version,
+                   ciphers=self._tls_ciphers)
+           else:
+               context = ssl.SSLContext(self._tls_version)
+               context.load_cert_chain(self._tls_certfile, self._tls_keyfile)
+               context.verify_mode = self._tls_cert_reqs
+               context.load_verify_locations(self._tls_ca_certs)
+               context.set_alpn_protocols(["x-amzn-mqtt-ca"])
+               
+               self._ssl = context.wrap_socket(sock, server_hostname=self._host, do_handshake_on_connect=False)
+                   
+               self._ssl.do_handshake()
+               
            if self._tls_insecure is False:
                if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 5):  # No IP host match before 3.5.x
                    self._tls_match_hostname()

変更を加えた後、AWS IoTへメッセージをパブリッシュするための単純なスクリプトを作成します。このスクリプトの名前をaws-iot-with-alpn.py とします。AWS IoT Device SDK for Pythonについての詳細はこちらをご参照ください。

 

# Import SDK packages
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient

# For certificate based connection
myMQTTClient = AWSIoTMQTTClient("myClientID")

# Configure the MQTT Client
myMQTTClient.configureEndpoint(<YOUR_AWS_IOT_ENDPOIT>, 443)
myMQTTClient.configureCredentials("YOUR/ROOT/CA/PATH", YOUR/DEVICE/KEY/PATH ", " YOUR/DEVICE/CERT/PATH ")
myMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
myMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
myMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
myMQTTClient.configureMQTTOperationTimeout(5)  # 5 sec

# Connect to AWS IoT endpoint and publish a message
myMQTTClient.connect()
print ("Connected to AWS IoT")
myMQTTClient.publish("alpn/devicesdk", "Hello over MQTT on port 443", 0)
myMQTTClient.disconnect()
 
       

スクリプトの実行

スクリプトを作成したら、以下のコマンドを実行します。

python aws-iot-with-alpn.py 
Connected to AWS IoT

”Connected”の表示がコンソールに表示されれば、AWS IoT Coreへの接続が成功し、メッセージのパブリッシュが成功した事となります。もしなにかエラーが表示された場合は、証明書、証明書に紐付けたポリシーでAWS IoTへの接続が許可されているかなどを確認してください。

 

Summray

本投稿では、AWS IoTへMQTTをport443で接続する2つのやり方を紹介しました。企業ファイアウォールでポート8883を開くのが難しいなどの制約がある場合にはHTTPS標準で使われるポート443を使用して、AWS IoTへメッセージ送信ができることになりました。

AWS IoT Coreに関する詳細情報などについては、AWS IoT Core開発者ガイドをご参照ください。

筆者:Rahul Sareen / 小梁川貴史
原文はこちら
翻訳はSA 小梁川が担当しました。