4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AWS IoT Bootstrapping - CSR作成・証明書発行・MQTT接続 (秘密鍵はエッジで生成)

Posted at

#実現したいこと

  • IoTデバイスをできる限りセキュアに保ち、かつ運用も効率化したい。
    • 独自CAの運用管理は手間なのでできれば避けたい。
    • 秘密鍵を通信路に流したくない。
    • AWS IoT と接続してMQTT通信したい。

この実現手段として以下を考えます。

  • クライアントで鍵ペア、CSR生成を行う

  • REST経由で証明書を取得

    • Edge -> API GW -> Lambda -> AWS IoT
  • MQTT接続 & Publish

    • Edge -> AWS IoT

RSA鍵の生成、CSR作成

エッジ側(Mac)での操作です。ここではOpensslを使います。

openssl genrsa -out privatekey.pem 2048
openssl req -new -subj "/C=JP/ST=Tokyo/L=Meguro/O=Amazon Web Services Japan K.K./CN=AWS IoT Certificate" -key privatekey.pem -out cert.csr

Lambdaで証明書をリクエストするコードをかく

image.png

LambdaのExecution roleに、以下のようなポリシーをアタッチします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "iot:CreateCertificateFromCsr",
            "Resource": "*"
        }
    ]
}

Lambdaはこのような感じで、一旦CSRをハードコーディングしてテストします。

import json
import boto3

def lambda_handler(event, context):
    client = boto3.client('iot')
    response = client.create_certificate_from_csr(
        certificateSigningRequest='''-----BEGIN CERTIFICATE REQUEST-----
(CSR)
-----END CERTIFICATE REQUEST-----''',
        setAsActive=True
    )
    return {
        'statusCode': 200,
        'body': json.dumps(response)
    }

Responseは以下のようになっており、certificatePemが取れています。

{
  "statusCode": 200,
  "body": {
    "ResponseMetadata": {
      "RequestId": "d6fe4c43-004a-46c3-9a47-bab473a210ef",
      "HTTPStatusCode": 200,
      "HTTPHeaders": {
        "date": "Thu, 23 Jan 2020 06:09:32 GMT",
        "content-type": "application/json",
        "content-length": "1594",
        "connection": "keep-alive",
        "x-amzn-requestid": "d6fe4c43-004a-46c3-9a47-bab473a210ef",
        "access-control-allow-origin": "*",
        "x-amz-apigw-id": "GvXIZFqVtjMFnDw=",
        "x-amzn-trace-id": "Root=1-5e29389c-199aa1001fb6b8f098ea85b8"
      },
      "RetryAttempts": 0
    },
    "certificateArn": "arn:aws:iot:ap-northeast-1:aaa",
    "certificateId": "aaa",
    "certificatePem": "-----BEGIN CERTIFICATE-----(中略)-----END CERTIFICATE-----\n"
  }
}

csrをLambdaに渡す

クライアントからのリクエストに応じてHTTP経由でCSR取得可能にします。
まずは、API Gatewayの設定。

簡単のため、Beta機能ですが、HTTP APIを使用します。
image.png

image.png

POSTでCSRを送るように設定します。
image.png

これで、APIが作成されました。
Lambda側は、CSRを受け付けるように少し改変します。

import json
import boto3

def lambda_handler(event, context):
    csr = event['body']
    client = boto3.client('iot')
    response = client.create_certificate_from_csr(
        certificateSigningRequest=csr,
        setAsActive=True
    )
    return {
        'statusCode': 200,
        'body': json.dumps(response)
    }

PCにPostmanをインストールして、BodyにCSRを入れてAPIを叩きます。
image.png

レスポンスを確認すると、certificatePemが取れていることが分かりました。
これで、有効な証明書がゲットできました。

証明書にPolicyをアタッチ

証明書にPolicyをアタッチすることで、特定の操作をエッジデバイスに認可することができます。

AWS IoTで以下のようなポリシーを作っておきます。
名前は、getCertificateTestとしました。


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Publish"
      ],
      "Resource": "*"
    }
  ]
}

先程LambdaにAttachしたPolicyを編集し、AttachPolicyのActionを追加します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iot:CreateCertificateFromCsr",
                "iot:AttachPolicy"
            ],
            "Resource": "*"
        }
    ]
}

また、Lambdaの実装の最後に以下を追加します。


    certificateArn = response['certificateArn']
    
    client.attach_policy(
        policyName='getCertificateTest',
        target=certificateArn
    )

ここまで設定して、もう一度POSTを実行してみます。AWS IoTで証明書が追加されており、Policyもアタッチされていることがわかります。

image.png

クライアントからMQTTのPublishをしてみる

MQTTのPublishを行うためには、AWS IoT側で、エッジデバイスに対応するThingを登録し、Thingと証明書を紐付ける必要があります。
そこで、クライアント側からThingNameとCSRをJson形式で送るようにし、Lambda上でそのThingNameでThingの作成を行い、さらに作成した証明書を紐付けます。
まずはそれらに必要な権限をLambdaに与えるため、Policyを更新します。

            "Action": [
                "iot:CreateThing",
                "iot:AttachPolicy",
                "iot:AttachThingPrincipal",
                "iot:CreateCertificateFromCsr"
            ]

最終的にはLambdaはこうなりました。

import json
import boto3
import base64

def lambda_handler(event, context):

    body = json.loads(event['body'])
    client = boto3.client('iot')
    response = client.create_certificate_from_csr(
        certificateSigningRequest=body['csr'],
        setAsActive=True
    )
    
    certificatePem = response['certificatePem']
    certificateArn = response['certificateArn']
    
    client.attach_policy(
        policyName='getCertificateTest',
        target=certificateArn
    )
    
    response = client.create_thing(
        thingName=body['thingName']
    )
    
    response = client.attach_thing_principal(
        thingName=body['thingName'],
        principal=certificateArn
    )
    
    return {
        'statusCode': 200,
        'body': certificatePem
    }

クライアント側の実装では、Python のDevice SDKを使います。
https://github.com/aws/aws-iot-device-sdk-python

認証で必要なルート証明書を取得します。

curl https://www.amazontrust.com/repository/AmazonRootCA1.pem >> Amazon_Root_CA_1.pem

実行ごとに新たなThingを作成し、一発だけMQTTのPublishを行います。

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import requests
import time
import json

thingName = "SN-" + str(int(time.time()))

file = open('cert.csr', 'r')
csr = file.read()
file.close()

payload = {"thingName": thingName, "csr": csr}
header = {"content-type": "application/json"}

r = requests.post('https://aaa.execute-api.ap-northeast-1.amazonaws.com/getCertificate',
                  data=json.dumps(payload), headers=header)

file = open('cert', 'w')
file.write(r.content)
file.close()

myMQTTClient = AWSIoTMQTTClient(thingName)
myMQTTClient.configureEndpoint("aaa-ats.iot.ap-northeast-1.amazonaws.com", 8883)
myMQTTClient.configureCredentials("Amazon_Root_CA_1.pem", "privatekey.pem", "cert")

myMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
myMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
myMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
myMQTTClient.configureMQTTOperationTimeout(5)  # 5 sec

myMQTTClient.connect()
myMQTTClient.publish("certtest/{}".format(thingName), json.dumps({"message": "hello", "thingName": thingName}), 1)

time.sleep(10)

myMQTTClient.disconnect()

これでPythonのスクリプトを実行するとMQTTのメッセージがPublishされるようになります。
AWS IoTのTestを使って、Subscribeするとメッセージを受信しました。

image.png

補足

実際のユースケースだと、有効な鍵ペアや証明書がローカルに無い場合のみREST APIを叩く形になると思います。
CSRは、環境によって使えるライブラリを探す必要があるかもしれません。組み込み系だとmbed TLSでも作成できそうです。
https://tls.mbed.org/kb/how-to/generate-a-certificate-request-csr

注意点

作成した秘密鍵の保管方法は別途検討が必要です。

今回の方法はBootstrappingのあくまで1パターンに過ぎないです。実際にはセキュリティリスクアセスメントを実施し、ビジネスとセキュリティの両面から実現方法を検討することになると思います。

環境にあわせてREST APIの認証も別途考える必要があります。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?