【小ネタ】AWS IoT(MQTT)へのアクセスでX.509証明書(秘密鍵)を使用しない場合もルート証明書は必須だった

2020.03.16

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

1 はじめに

CX事業本部の平内(SIN)です。

今回、AWS IoT(MQTT)に、AccessKeyIDSecretAccessKeyでアクセスして、少しハマってしまったので、覚書としてこれを記述しています。

結論から言うと、「X.509証明書と秘密鍵でアクセスしない場合でも、ルート証明書が必要」という事でした。ちなみに、この場合、MQTT over Websocket SigV4(ポート443)となります。

「当たり前だよ」って方には、未熟でほんとすいません。

2 X.509証明書ベース

AWS IoTのコンソールからモノ(Thing)を作成すると、1-Clickで証明書を発行することができます。

また、オンボードメニューから作成した場合にダウンロードできる接続キットは、X.509証明書(秘密鍵)を使用するものになっています。

AWSIoTMQTTClientを使用して、X.509証明書ベースでMQTTにアクセスする一般的なコードです。

AWSIoTMQTTClientconfigureCredentialsでルート証明、秘密鍵及び、X.509証明書を設定して使用します。

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient

clientId = "client_id"
endPoint = "xxxxx-ats.iot.us-east-1.amazonaws.com"
port = 8883
rootCA = "./root-CA.crt"
privateKey = "./sample-private.key"
certificate = "./sample-cert.pem"

TOPIC = "topic"

def main():
    client = AWSIoTMQTTClient(clientId)
    client.configureEndpoint(endPoint, port)
    client.configureCredentials(rootCA, privateKey, certificate)
    client.configureAutoReconnectBackoffTime(1, 32, 20)
    client.configureOfflinePublishQueueing(-1)
    client.configureDrainingFrequency(2)
    client.configureConnectDisconnectTimeout(10)
    client.configureMQTTOperationTimeout(5)
    client.connect()
    client.subscribe(TOPIC, 1, callback)
    while True:
        time.sleep(5)

3 MQTT over Websocket SigV4

そして、AccessKeyIDSecretAccessKeyでアクセスするパターンです。

認証情報をそのまま、デバイス側に置くことは推奨されていないので、Cosnitoのidentity poolを利用して、一時的な認証情報を取得しています。 AWSIoTMQTTClientconfigureIAMCredentialsでアクセスキー、シークレットキー、及び、トークンを設定して使用します。

そして、今回、ハマった、configureCredentials(rootCA)によるルート証明書の設定です。

import boto3
import time
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient

clientId = "client_id"
endPoint = "xxxxx-ats.iot.us-east-1.amazonaws.com"
port = 443 # Cognito経由の認証では、Websocket SigV4 しか使用できない
rootCA = "./root-CA.crt"
identityPoolId = 'ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

topic = "topic"

def getAuthentication(identityPoolId):
    client = boto3.client('cognito-identity', 'ap-northeast-1')
    res =  client.get_id(IdentityPoolId=identityPoolId)
    res = client.get_credentials_for_identity(IdentityId = res['IdentityId'])
    accessId = res['Credentials']['AccessKeyId']
    secretKey = res['Credentials']['SecretKey']
    token = res['Credentials']['SessionToken']
    return (accessId, secretKey, token)

def onSubscribe(client, userdata, message):
    print("message:{} topic:{}".format(message.payload, message.topic))

def main():

    accessId, secretKey, token = getAuthentication(identityPoolId) # 一時的な認証情報を取得
    client = AWSIoTMQTTClient(clientId, useWebsocket=True) # Websocket SigV4を利用
    client.configureIAMCredentials(accessId, secretKey, token)
    client.configureCredentials(rootCA) # ルート証明書の設定が必要
    client.configureEndpoint(endPoint, port)
    client.configureAutoReconnectBackoffTime(1, 32, 20)
    client.configureOfflinePublishQueueing(-1)
    client.configureDrainingFrequency(2)
    client.configureConnectDisconnectTimeout(10)
    client.configureMQTTOperationTimeout(5)
    client.connect()
    client.subscribe(TOPIC, 1, callback)
    while True:
        time.sleep(5)


main()

4 最後に

エッジデバイスからのMQTTは、X.509証明書ベースで利用するのが普通だと思うのですが、MQTT以外にもAWSの他のリソースへのアクセス要件があったので、Cognitoのidentity poolを置くんだったら、MQTTもそれでアクセスしちゃえと思って、今回の作業になりました。

configureCredentials()を使用しないので、勝手にルート証明書も必要ないと思い込んで、Connect出来なくて悩みました。