[AWS IoT] 証明書の作成・登録方法 Part4 JITR (Just In Time Regstration)

# はじめに
[前回の記事](https://qiita.com/tsugunao/items/1af283d71bc6a0aed4f2)ではJITPによる証明書の作成・登録方法を試してみました。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f38303032382f64306538663864612d366233332d626465302d643564332d3734356235626237633134662e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/9b450d6a-4cba-e878-371f-3d169e743253.png)
上の表の分類で、JITPと同じく、Amazon以外のCAで発行した証明書を用いて、デバイスからの初回アクセス時に証明書の発行と事前登録を行うJust In Time Registration(JITR）について今回は試してみました。JITRとJITPの主な違いは、JITPは事前に作成したテンプレートをもとにしてプロビジョニングを行うのに対して、JITRはLambda関数を用意してプロビジョニングを行う、という違いになります。そのため、JITRの手順の前半はJITPと共通になります。

# Just In Time Registration
それでは試してみたいと思います。

## CA証明書と検証証明書の作成 (ここは[JITPの手順](https://qiita.com/tsugunao/items/1af283d71bc6a0aed4f2)と同じです)
まず、CA証明書と検証証明書をを作成する必要があります。CA証明書はOpenSSLのコマンドで作成することができます。

CA証明書を作成

```
$ openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem
# 適宜入力
-----
Country Name (2 letter code) []:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) []:Tokyo
Organization Name (eg, company) []:MyOrg
Organizational Unit Name (eg, section) []:MyUnit
Common Name (eg, fully qualified host name) []:RootCA
Email Address []:hogehoge
```

AWS IoT registration code を取得

```
$ aws iot get-registration-code
{
    "registrationCode": "017102e221eb356519.....(中略)"
}
```

検証証明書のキーペアを作成

```
$ openssl genrsa -out verificationCert.key 2048
```

検証証明書のCSRを作成 （Common Nameに registration codeを入力）

```
$ openssl req -new -key verificationCert.key -out verificationCert.csr

Country Name (2 letter code) []:JP
State or Province Name (full name) []:JP
Locality Name (eg, city) []:Tokyo
Organization Name (eg, company) []:MyOrg
Organizational Unit Name (eg, section) []:MyOU
Common Name (eg, fully qualified host name) []:017102e221eb356519 ......(中略)
Email Address []:
```

CSRから検証証明書を作成

```
$ openssl x509 -req -in verificationCert.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out verificationCert.crt -days 500 -sha256
```
AWS IoT CoreにCA証明書を登録

```
aws iot register-ca-certificate --ca-certificate rootCA.pem --verification-cert verificationCert.crt
```
登録すると、CA証明書のCertificateIDが表示されるので、登録したCA証明書をACTIVEに設定します。

```
aws iot update-ca-certificate --certificate-id <CertificateID> --new-status ACTIVE
```

デバイス証明書の自動登録を有効化します。

```
aws iot update-ca-certificate --certificate-id <CertificateID> --new-auto-registration-status ENABLE
```
## IoT Ruleの設定とLambda関数の設定
JITRでは以下の図のように、IoT RuleとLambda関数を使用して証明書をACTIVEに設定し、Policyの作成、モノの作成と証明書のアタッチ等を行います。
![figure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/fc84f436-da83-ad81-0c6c-6fa4d1247a98.png)

IoTCoreではデバイス証明書の自動登録が有効化されると、新しいデバイスが初回接続された場合に、AWSの予約Topic ```$aws/events/certificates/registered/<caCertificateID>``` に対してMQTTメッセージがPublishされます。
このメッセージのフォーマットは

```json
{
  "certificateId": "certificateID",
  "caCertificateId": "caCertificateId",
  "timestamp": timestamp,
  "certificateStatus": "PENDING_ACTIVATION",
  "awsAccountId": "awsAccountId",
  "certificateRegistrationTimestamp": "certificateRegistrationTimestamp"
}
```

のようなフォーマットとなっています。このメッセージをルールエンジン経由でLambda関数に渡し、Lambda関数の中で証明書の有効化やPolicyの作成を行います。

まずはLambda関数を作成します。Lambdaのマネジメントコンソールから関数の作成をクリックし、Lambda関数を作成します。ここではPythonにて作成しました。

```py
import json
import boto3
import sys

IOT   = boto3.client('iot')
POLICY_NAME = 'My_IoT_Policy'
REGION = 'ap-northeast-1'
THINGNAME = 'MyTestThing0002'

def attach_Policy(awsID, CertID):
    CertificateARN = 'arn:aws:iot:'+ REGION + ':' + awsID + ':cert/' + CertID

    try:
        IOT.attach_principal_policy(
              policyName = POLICY_NAME,
              principal  = CertificateARN
            )

    except Exception as e:
        print(e.message)
        raise

def activate_Cert(ID):
    try:
        IOT.update_certificate(
              certificateId = ID,
              newStatus     = 'ACTIVE'
            )

    except Exception as e:
        print(e.message)
        raise

def create_Thing():
    try:
        IOT.create_thing(
             thingName = THINGNAME
            )
            
    except Exception as e:
        print(e)
        raise   
    
    
def attach_Thing( awsID, CertID):
    CertificateARN = 'arn:aws:iot:'+ REGION + ':' + awsID + ':cert/' + CertID
    try:
        IOT.attach_thing_principal(
            thingName=THINGNAME,
            principal=CertificateARN
            )
            
    except Exception as e:
        print(e.message)
        raise

def lambda_handler(event, context):
# just in time event structure
#{
#  "certificateId": "<certificateID>",
#  "caCertificateId": "<caCertificateId>",
#  "timestamp": "<timestamp>",
#  "certificateStatus": "PENDING_ACTIVATION",
#  "awsAccountId": "<awsAccountId>",
#  "certificateRegistrationTimestamp": "<certificateRegistrationTimestamp>"
#}

    awsID  = event['awsAccountId']
    certID = event['certificateId']
    caCert = event['caCertificateId']
    print("awsID:"+ awsID)
    print("certID:"+ certID)
    print("caCert:"+ caCert)
    try:
        print('attach_poclicy')
        attach_Policy(awsID, certID)
        print ('activate_Cert')
        activate_Cert(certID)
        print ('create_Thing')
        create_Thing()
        print ('attach_Thing')
        attach_Thing(awsID, certID)

    except Exception as e:
        print('Error on Lambda')
        raise
    
    return
    

```

すでに作成済みのデバイス用のPolicyがあったので、Policyを証明書にアタッチし、証明書をアクティベートし、モノを作成し、証明書をモノにアタッチしています。モノの名前やPolicyの名前は簡単のため決め打ちで行いました。本当であれば、デバイスから送られてくる情報からモノの名前がつけられるとよいのですが（JITPの場合にはCommon Nameにつけられましたよね。追記：証明書情報をパースしてCNを取り出すことが可能。[describe_certificate](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/acm.html#ACM.Client.describe_certificate)）MQTTで上がってくる情報にそのような情報が含まれていなかったので、そのようなことをやりたい場合には別途デバイスから取得する必要がありそうです。Lambda関数の実行のPermissionを管理するロールをIAMにて作成します。以下のようなPolicyを作成してロールにアタッチしました。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "iot:*",
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        }
    ]
}
```

次に、MQTTメッセージをLambdaに渡すIoT Ruleを作成します。IoT CoreのACT>ルールから作成を選択し、以下のルールクエリでルールを作成します。

```sql
SELECT * FROM '$aws/events/certificates/registered/<caCertificateID>'
```

アクションはLambda関数を呼び出し、を選択し先程作成したLambda関数を指定します。
![スクリーンショット 2021-07-28 10.02.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/b7f6aed4-bae2-693f-25da-81de0fb124c0.png)

これにて準備は完了です。


## デバイスからの接続
まずは、先程作成したCAを使ってデバイス証明書を作成します。
まず、キーを作成。

```
openssl genrsa -out deviceCert.key 2048
```

CSRを作成

```
openssl req -new -key deviceCert.key -out deviceCert.csr
```

プロンプトで以下のように適宜入力

```
-----
Country Name (2 letter code) []:JP
State or Province Name (full name) []:State
Locality Name (eg, city) []:Tokyo
Organization Name (eg, company) []:Org
Organizational Unit Name (eg, section) []:OU
Common Name (eg, fully qualified host name) []:CN
Email Address []:    
```

CSRからCAを使って証明書を作成

```
openssl x509 -req -in deviceCert.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out deviceCert.crt -days 365 -sha256
```

証明書とCAを連結します。

```
cat deviceCert.crt rootCA.pem > deviceCertAndCACert.crt
```

次に作成された証明書（deviceCertAndCACert.crt）、デバイスキー（deviceCert.key）を使ってアクセスしてみます。Amazonのサーバー認証用のCA証明書が必要となるので[こちら](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/server-authentication.html#server-authentication-certs)から事前にダウンロードしておきます。クライアントとしてmosquittoを使って接続しました。

初回の接続。

```
$ ./mosquitto_pub --cafile jitr_cert/root.cert --cert jitr_cert/deviceCertAndCACert.crt --key jitr_cert/deviceCert.key -h "<host name>-ats.iot.ap-northeast-1.amazonaws.com" -p 8883 -q 1 -d -t topic/test -m "Hello World" -i id01 --tls-version tlsv1.2
```

接続エラーとなります。

```
Client id01 sending CONNECT
Error: The connection was lost.
```

2回目の接続

```
./mosquitto_pub --cafile jitr_cert/root.cert --cert jitr_cert/deviceCertAndCACert.crt --key jitr_cert/deviceCert.key -h "<host name>-ats.iot.ap-northeast-1.amazonaws.com" -p 8883 -q 1 -d -t topic/test -m "Hello World" -i id01 --tls-version tlsv1.2
```

1回目の失敗時にデバイス証明書が登録されているため正しく接続されます。

```
Client id01 sending CONNECT
Client id01 received CONNACK (0)
Client id01 sending PUBLISH (d0, q1, r0, m1, 'topic/test', ... (11 bytes))
Client id01 received PUBACK (Mid: 1, RC:0)
Client id01 sending DISCONNECT
```

IoTのコンソールを確認すると、Lambdaで指定した名前でモノが作成され、証明書がアタッチさせていることが確認できます。
![スクリーンショット 2021-07-28 10.27.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/b6244182-2d53-d1dd-bd3d-18ee07ac9f4c.png)

CloudWatchにてLambda関数のLogを確認すると、正しく実行されたことが確認できます。
![スクリーンショット 2021-07-28 10.30.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80028/5f7b38e6-a39d-081e-28ca-998c0f0827c1.png)


# まとめ
Just In Time Registration（JITR）による証明書の作成・登録について試してみました。